// https://syzkaller.appspot.com/bug?id=5073831ae6fda2a5f8332c1075c7474f260a49f7 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef htole16 #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ #define htole16(x) ((uint16_t)((((uint16_t)(x) & 0xff) << 8) | (((uint16_t)(x) >> 8) & 0xff))) #else #define htole16(x) ((uint16_t)(x)) #endif #define ELAN_VENDOR_ID 0x2201 #define VUB300_PRODUCT_ID 0x012c static volatile int g_running = 1; static volatile int g_clock_set = 0; static volatile int g_got_port_status = 0; static struct usb_device_descriptor dev_desc = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = USB_DT_DEVICE, .bcdUSB = htole16(0x0200), .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = htole16(ELAN_VENDOR_ID), .idProduct = htole16(VUB300_PRODUCT_ID), .bcdDevice = htole16(0x0100), .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, }; struct full_config { struct usb_config_descriptor config; struct usb_interface_descriptor iface; struct usb_endpoint_descriptor ep[4]; } __attribute__((packed)); static struct full_config conf_desc = { .config = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = USB_DT_CONFIG, .wTotalLength = htole16(sizeof(struct full_config)), .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0x80, .bMaxPower = 50, }, .iface = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 4, .bInterfaceClass = 0xff, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = 0, }, .ep[0] = { /* cmnd_res_ep: bulk IN */ .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, .ep[1] = { /* cmnd_out_ep: bulk OUT */ .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x02, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, .ep[2] = { /* data_inp_ep: bulk IN */ .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x86, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, .ep[3] = { /* data_out_ep: bulk OUT */ .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x07, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, }; static void sig_handler(int sig) { (void)sig; } static int ep0_write(int fd, const void *data, uint32_t len) { char buf[sizeof(struct usb_raw_ep_io) + 64]; if (len > 64) len = 64; struct usb_raw_ep_io *io = (struct usb_raw_ep_io *)buf; io->ep = 0; io->flags = 0; io->length = len; if (len && data) memcpy(io->data, data, len); int res = ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io); if (res < 0) { printf("[-] Failed ioctl USB_RAW_IOCTL_EP0_WRITE: %s\n", strerror(errno)); } return res; } static int ep0_read(int fd, void *data, uint32_t len) { char buf[sizeof(struct usb_raw_ep_io) + 64]; if (len > 64) len = 64; struct usb_raw_ep_io *io = (struct usb_raw_ep_io *)buf; io->ep = 0; io->flags = 0; io->length = len; int res = ioctl(fd, USB_RAW_IOCTL_EP0_READ, io); if (res < 0) { printf("[-] Failed ioctl USB_RAW_IOCTL_EP0_READ: %s\n", strerror(errno)); } else if (data && len) { memcpy(data, io->data, (uint32_t)res < len ? (uint32_t)res : len); } return res; } static void handle_control_request(int fd, const void *data) { const struct usb_ctrlrequest *ctrl = (const struct usb_ctrlrequest *)data; uint16_t wLength = le16toh(ctrl->wLength); uint16_t wValue = le16toh(ctrl->wValue); uint8_t req_type = ctrl->bRequestType & USB_TYPE_MASK; if (req_type == USB_TYPE_STANDARD) { if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR) { uint8_t dt = wValue >> 8; if (dt == USB_DT_DEVICE) { uint32_t len = wLength < sizeof(dev_desc) ? wLength : sizeof(dev_desc); ep0_write(fd, &dev_desc, len); } else if (dt == USB_DT_CONFIG) { uint32_t len = wLength < sizeof(conf_desc) ? wLength : sizeof(conf_desc); ep0_write(fd, &conf_desc, len); } else { ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0); } } else if (ctrl->bRequest == USB_REQ_SET_CONFIGURATION) { ep0_read(fd, NULL, 0); for (int i = 0; i < 4; i++) { int res = ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &conf_desc.ep[i]); if (res < 0) { printf("[-] Failed ioctl USB_RAW_IOCTL_EP_ENABLE for ep %d: %s\n", i, strerror(errno)); } } int res = ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0); if (res < 0) { printf("[-] Failed ioctl USB_RAW_IOCTL_CONFIGURE: %s\n", strerror(errno)); } else { printf("[+] USB device set to CONFIGURED state.\n"); } } else { if ((ctrl->bRequestType & USB_DIR_IN) && wLength > 0) { char dummy[64] = {0}; ep0_write(fd, dummy, wLength < sizeof(dummy) ? wLength : sizeof(dummy)); } else { char dummy[64] = {0}; ep0_read(fd, dummy, wLength < sizeof(dummy) ? wLength : sizeof(dummy)); } } } else if (req_type == USB_TYPE_VENDOR) { if ((ctrl->bRequestType & USB_DIR_IN) && wLength > 0) { if (ctrl->bRequest == 1) { /* GET_HC_INF0 */ struct { uint8_t size; uint16_t ver; uint8_t ports; } __attribute__((packed)) hc = {4, 0, 1}; ep0_write(fd, &hc, wLength < sizeof(hc) ? wLength : sizeof(hc)); printf("[+] Responded to GET_HC_INF0 (request 1)\n"); } else if (ctrl->bRequest == 0) { /* GET_SYSTEM_PORT_STATUS */ uint8_t status[15] = {0}; ep0_write(fd, status, wLength < sizeof(status) ? wLength : sizeof(status)); printf("[+] Responded to GET_SYSTEM_PORT_STATUS (request 0)\n"); g_got_port_status = 1; } else { char dummy[64] = {0}; ep0_write(fd, dummy, wLength < sizeof(dummy) ? wLength : sizeof(dummy)); } } else { char dummy[64] = {0}; ep0_read(fd, dummy, wLength < sizeof(dummy) ? wLength : sizeof(dummy)); printf("[+] Responded to Vendor OUT request %u (wLength=%u)\n", ctrl->bRequest, wLength); if (ctrl->bRequest == 11) { /* SET_CLOCK_SPEED */ g_clock_set = 1; } } } else { if ((ctrl->bRequestType & USB_DIR_IN) && wLength > 0) { char dummy[64] = {0}; ep0_write(fd, dummy, wLength < sizeof(dummy) ? wLength : sizeof(dummy)); } else { char dummy[64] = {0}; ep0_read(fd, dummy, wLength < sizeof(dummy) ? wLength : sizeof(dummy)); } } } static void *gadget_thread(void *arg) { int fd = *(int *)arg; char event_buf[512]; while (g_running) { struct usb_raw_event *event = (struct usb_raw_event *)event_buf; event->type = 0; event->length = sizeof(event_buf) - sizeof(*event); int res = ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event); if (res < 0) { if (errno == EINTR && g_running) continue; break; } if (event->type == USB_RAW_EVENT_CONTROL) { handle_control_request(fd, event->data); } } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting VUB300 inactivity timer invalid sleeping context reproducer...\n"); struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sa.sa_flags = 0; /* No SA_RESTART so blocking ioctl returns -EINTR */ sigemptyset(&sa.sa_mask); int res = sigaction(SIGUSR1, &sa, NULL); if (res < 0) { printf("[-] Failed sigaction(SIGUSR1): %s\n", strerror(errno)); exit(1); } printf("[+] sigaction(SIGUSR1) successful.\n"); int fd = open("/dev/raw-gadget", O_RDWR); if (fd < 0) { printf("[-] Failed open(/dev/raw-gadget): %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/raw-gadget successful.\n"); DIR *dir = opendir("/sys/class/udc"); if (!dir) { printf("[-] Failed opendir(/sys/class/udc): %s\n", strerror(errno)); close(fd); exit(1); } printf("[+] opendir /sys/class/udc successful.\n"); struct dirent *de; int bound = 0; while ((de = readdir(dir)) != NULL) { if (de->d_name[0] == '.') continue; struct usb_raw_init init; memset(&init, 0, sizeof(init)); strncpy((char *)init.device_name, de->d_name, sizeof(init.device_name) - 1); char *dot = strrchr(de->d_name, '.'); if (dot) memcpy(init.driver_name, de->d_name, dot - de->d_name); else strncpy((char *)init.driver_name, de->d_name, sizeof(init.driver_name) - 1); init.speed = USB_SPEED_HIGH; res = ioctl(fd, USB_RAW_IOCTL_INIT, &init); if (res == 0) { printf("[+] Bound to UDC: device=%s, driver=%s\n", init.device_name, init.driver_name); bound = 1; break; } else { printf("[-] ioctl USB_RAW_IOCTL_INIT failed for %s: %s\n", de->d_name, strerror(errno)); } } res = closedir(dir); if (res < 0) { printf("[-] Failed closedir(/sys/class/udc): %s\n", strerror(errno)); close(fd); exit(1); } printf("[+] closedir /sys/class/udc successful.\n"); if (!bound) { printf("[-] Failed to bind to any UDC in /sys/class/udc\n"); close(fd); exit(1); } res = ioctl(fd, USB_RAW_IOCTL_RUN, 0); if (res < 0) { printf("[-] Failed ioctl USB_RAW_IOCTL_RUN: %s\n", strerror(errno)); close(fd); exit(1); } printf("[+] ioctl USB_RAW_IOCTL_RUN successful.\n"); pthread_t thread; res = pthread_create(&thread, NULL, gadget_thread, &fd); if (res != 0) { printf("[-] Failed pthread_create: %s\n", strerror(res)); close(fd); exit(1); } printf("[+] pthread_create successful.\n"); printf("[*] Waiting for VUB300 device enumeration and probe...\n"); for (int i = 0; i < 30; i++) { usleep(100000); /* 100 ms */ if (g_clock_set) { printf("[+] VUB300 probe completed (clock speed set).\n"); usleep(300000); /* 300 ms delay for mmc_add_host to finish returning */ break; } } if (!g_clock_set && g_got_port_status) { printf("[+] VUB300 port status handled, waiting 300 ms...\n"); usleep(300000); } printf("[*] Stopping gadget thread...\n"); g_running = 0; res = pthread_kill(thread, SIGUSR1); if (res != 0) { printf("[-] Failed pthread_kill: %s\n", strerror(res)); } else { printf("[+] pthread_kill SIGUSR1 successful.\n"); } res = pthread_join(thread, NULL); if (res != 0) { printf("[-] Failed pthread_join: %s\n", strerror(res)); } else { printf("[+] pthread_join successful.\n"); } printf("[*] Closing raw-gadget fd to trigger USB disconnect...\n"); res = close(fd); if (res < 0) { printf("[-] Failed close(/dev/raw-gadget): %s\n", strerror(errno)); exit(1); } printf("[+] close /dev/raw-gadget successful.\n"); printf("[*] Waiting for vub300 inactivity timer to expire in softirq context...\n"); sleep(4); printf("[+] Reproduction attempt completed.\n"); return 0; }