DF-1116 / harness.c
/* * DF-1116 userspace harness — integer divide-by-zero primitive. * * The kernel bug: * sys/bus/firewire/fwdma.c:159 fwdma_malloc_multiseg() * ssize = rounddown(PAGE_SIZE, esize); // expands to (PAGE_SIZE/esize)*esize * with esize == 0 (passed unvalidated from FW_SSTBUF ioctl via fwdev.c:504-509 * bcopy -> fwdev_allocbuf -> fwdma_malloc_multiseg) this is integer divide by * zero -> kernel #DE trap -> panic at trap.c:1101. * * This harness reproduces the arithmetic primitive in userspace (SIGFPE on x86 * is the user-mode analogue of the kernel #DE panic). It proves the bug is real * and deterministic; it cannot fire on this audit guest because no FireWire * controller is present (the driver never attaches -> /dev/fwN.M never created). * * Build: cc -O2 -o harness harness.c * Run: ./harness * Expected: "SIGFPE: integer divide by zero" (demonstrates the primitive). */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <stdint.h> #define PAGE_SIZE 4096u #define rounddown(x, y) (((x) / (y)) * (y)) /* sys/sys/param.h:400 */ #define roundup2(x, y) (((x) + ((y) - 1)) & (~((y) - 1))) static void fpe_hdl(int s, siginfo_t *si, void *uc) { (void)s; (void)si; (void)uc; /* x86 #DE does not advance RIP, so returning would re-trap forever. * Report and exit in-handler. Use stderr (unbuffered) since _exit * does not flush stdout. */ fflush(stdout); fputs("\nSIGFPE: integer divide by zero -- primitive reproduced.\n", stderr); fputs("In kernel context this is a #DE trap -> panic " "(sys/platform/pc64/x86_64/trap.c:1101).\n", stderr); _exit(0); } int main(void) { struct sigaction sa = {0}; sa.sa_sigaction = fpe_hdl; sa.sa_flags = SA_SIGINFO; sigaction(SIGFPE, &sa, NULL); /* Simulate the FW_SSTBUF ioctl with b->psize == 0 (no validation). * Volatile prevents the compiler from constant-folding the div-by-zero * (which is UB and would not trap at -O2). We want the real #DE. */ volatile unsigned int user_psize_v = 0; unsigned int user_psize = user_psize_v; /* fwdev.c:107 b->psize = roundup2(b->psize, sizeof(u_int32_t)) */ unsigned int psize_after_roundup = roundup2(user_psize, sizeof(uint32_t)); printf("user psize = %u\n", user_psize); printf("after roundup2(_, 4) = %u (0 stays 0 -> the bug seed)\n", psize_after_roundup); /* fwdma.c:145-159 fwdma_malloc_multiseg(fc, _, esize=psize, _, _) */ int esize_v = (int)psize_after_roundup; volatile int vesize = esize_v; /* prevent constant-fold of /0 */ int esize = vesize; printf("esize passed to fwdma_malloc_multiseg = %d\n", esize); if (esize > (int)PAGE_SIZE) { printf("(not taken: esize==0)\n"); } else { /* ssize = rounddown(PAGE_SIZE, esize) -> (4096 / 0) * 0 -> #DE -> SIGFPE */ unsigned int ssize = rounddown(PAGE_SIZE, esize); printf("rounddown(PAGE_SIZE, esize) = %u (UNREACHED)\n", ssize); } /* should not reach here -- handler _exit()s */ printf("\nERROR: expected SIGFPE but did not get one.\n"); return 1; } |