// https://syzkaller.appspot.com/bug?id=90a6711ac078f98157c90f22766b0b3b4c612468 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef htole16 #if defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && (__BYTE_ORDER == __BIG_ENDIAN) #define htole16(x) (((uint16_t)(x) >> 8) | (((uint16_t)(x) & 0xff) << 8)) #else #define htole16(x) (x) #endif #define UDC_NAME_MAX 64 /* HID class descriptor structures */ struct hid_class_descriptor { uint8_t bDescriptorType; uint16_t wDescriptorLength; } __attribute__((packed)); struct hid_descriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdHID; uint8_t bCountryCode; uint8_t bNumDescriptors; struct hid_class_descriptor rpt_desc; } __attribute__((packed)); /* USB Report Descriptor declaring Buttons and HID++ short/long vendor reports */ static const uint8_t hid_report_desc[] = { 0x05, 0x01, /* Usage Page (Generic Desktop) */ 0x09, 0x04, /* Usage (Joystick) */ 0xa1, 0x01, /* Collection (Application) */ 0x85, 0x01, /* Report ID (1) */ 0x05, 0x09, /* Usage Page (Button) */ 0x19, 0x01, /* Usage Minimum (Button 1) */ 0x29, 0x08, /* Usage Maximum (Button 8) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x75, 0x01, /* Report Size (1) */ 0x95, 0x08, /* Report Count (8) */ 0x81, 0x02, /* Input (Data, Var, Abs) */ 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined 0xFF00) */ 0x09, 0x01, /* Usage (Vendor 1) */ 0x85, 0x10, /* Report ID (0x10) - HID++ Short */ 0x09, 0x01, /* Usage (Vendor 1) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x26, 0xff, 0x00, /* Logical Maximum (255) */ 0x75, 0x08, /* Report Size (8) */ 0x95, 0x06, /* Report Count (6) */ 0x81, 0x02, /* Input (Data, Var, Abs) */ 0x09, 0x01, /* Usage (Vendor 1) */ 0x91, 0x02, /* Output (Data, Var, Abs) */ 0x85, 0x11, /* Report ID (0x11) - HID++ Long */ 0x09, 0x02, /* Usage (Vendor 2) */ 0x95, 0x13, /* Report Count (19) */ 0x81, 0x02, /* Input (Data, Var, Abs) */ 0x09, 0x02, /* Usage (Vendor 2) */ 0x91, 0x02, /* Output (Data, Var, Abs) */ 0xc0 /* End Collection */ }; /* Full Configuration Descriptor bundle */ struct usb_config_bundle { struct usb_config_descriptor config; struct usb_interface_descriptor intf; struct hid_descriptor hid; struct usb_endpoint_descriptor ep_in; struct usb_endpoint_descriptor ep_out; } __attribute__((packed)); static const 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(0x046d), /* Logitech */ .idProduct = htole16(0xc262), /* G920 Wheel */ .bcdDevice = htole16(0x9602), /* Firmware Version 2 */ .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, }; static const struct usb_config_bundle config_bundle = { .config = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = USB_DT_CONFIG, .wTotalLength = htole16(sizeof(struct usb_config_bundle)), .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0x80, .bMaxPower = 50, }, .intf = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 2, .bInterfaceClass = 0x03, /* HID */ .bInterfaceSubClass = 0x00, .bInterfaceProtocol = 0x00, .iInterface = 0, }, .hid = { .bLength = 9, .bDescriptorType = 0x21, /* HID_DT_HID */ .bcdHID = htole16(0x0111), .bCountryCode = 0, .bNumDescriptors = 1, .rpt_desc = { .bDescriptorType = 0x22, /* HID_DT_REPORT */ .wDescriptorLength = htole16(sizeof(hid_report_desc)), }, }, .ep_in = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, /* EP 1 IN */ .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = htole16(64), .bInterval = 1, }, .ep_out = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x02, /* EP 2 OUT */ .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = htole16(64), .bInterval = 1, }, }; static volatile int running = 1; static volatile int configured = 0; static volatile int ep_in_handle = -1; static volatile int ep_out_handle = -1; static int raw_fd = -1; static int ep0_write(const void *data, uint32_t len) { struct { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[512]; } io; memset(&io, 0, sizeof(io)); io.ep = 0; io.flags = 0; io.length = len; if (len > 0 && data) { memcpy(io.data, data, len); } return ioctl(raw_fd, USB_RAW_IOCTL_EP0_WRITE, &io); } static int ep0_read(uint32_t len) { struct { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[512]; } io; memset(&io, 0, sizeof(io)); io.ep = 0; io.flags = 0; io.length = len; return ioctl(raw_fd, USB_RAW_IOCTL_EP0_READ, &io); } static void handle_ep0_control(struct usb_ctrlrequest *ctrl) { if ((ctrl->bRequestType & 0x80) == USB_DIR_IN) { if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR) { uint8_t desc_type = ctrl->wValue >> 8; uint8_t desc_idx = ctrl->wValue & 0xff; if (desc_type == USB_DT_DEVICE) { uint16_t len = sizeof(dev_desc); if (len > ctrl->wLength) len = ctrl->wLength; ep0_write(&dev_desc, len); return; } else if (desc_type == USB_DT_CONFIG) { uint16_t len = sizeof(config_bundle); if (len > ctrl->wLength) len = ctrl->wLength; ep0_write(&config_bundle, len); return; } else if (desc_type == 0x22) { /* HID Report */ uint16_t len = sizeof(hid_report_desc); if (len > ctrl->wLength) len = ctrl->wLength; ep0_write(hid_report_desc, len); return; } else if (desc_type == USB_DT_STRING) { if (desc_idx == 0) { uint8_t lang_desc[] = { 4, 3, 0x09, 0x04 }; uint16_t len = sizeof(lang_desc); if (len > ctrl->wLength) len = ctrl->wLength; ep0_write(lang_desc, len); return; } } } if (ctrl->wLength > 0) { ioctl(raw_fd, USB_RAW_IOCTL_EP0_STALL, 0); } else { ep0_read(0); } } else { /* Control OUT */ if (ctrl->bRequest == USB_REQ_SET_CONFIGURATION) { if (ep_in_handle < 0) { int in_ep = ioctl(raw_fd, USB_RAW_IOCTL_EP_ENABLE, &config_bundle.ep_in); if (in_ep >= 0) ep_in_handle = in_ep; } if (ep_out_handle < 0) { int out_ep = ioctl(raw_fd, USB_RAW_IOCTL_EP_ENABLE, &config_bundle.ep_out); if (out_ep >= 0) ep_out_handle = out_ep; } ioctl(raw_fd, USB_RAW_IOCTL_CONFIGURE, 0); configured = 1; ep0_read(0); return; } ep0_read(ctrl->wLength); } } static void *ep0_loop(void *arg) { (void)arg; struct { uint32_t type; uint32_t length; uint8_t data[sizeof(struct usb_ctrlrequest)]; } event; while (running) { memset(&event, 0, sizeof(event)); event.length = sizeof(event.data); int ret = ioctl(raw_fd, USB_RAW_IOCTL_EVENT_FETCH, &event); if (ret < 0) { if (errno == EINTR) continue; if (!running) break; usleep(1000); continue; } if (event.type == USB_RAW_EVENT_CONTROL) { struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)event.data; handle_ep0_control(ctrl); } } return NULL; } static void *ep_io_loop(void *arg) { (void)arg; struct { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[64]; } read_io; struct { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[64]; } write_io; while (running) { if (!configured || ep_out_handle < 0 || ep_in_handle < 0) { usleep(5000); continue; } memset(&read_io, 0, sizeof(read_io)); read_io.ep = ep_out_handle; read_io.flags = 0; read_io.length = sizeof(read_io.data); int n = ioctl(raw_fd, USB_RAW_IOCTL_EP_READ, &read_io); if (n < 0) { if (!running) break; usleep(2000); continue; } if (n >= 4) { memset(&write_io, 0, sizeof(write_io)); write_io.ep = ep_in_handle; write_io.flags = 0; write_io.length = (read_io.data[0] == 0x10) ? 7 : 20; write_io.data[0] = read_io.data[0]; write_io.data[1] = 0xff; /* Device index */ write_io.data[2] = read_io.data[2]; /* Feature index */ write_io.data[3] = read_io.data[3]; /* Function index */ write_io.data[4] = 0x02; /* Value 2: satisfies feature_index!=0 and num_effects=1 */ ioctl(raw_fd, USB_RAW_IOCTL_EP_WRITE, &write_io); } } return NULL; } static int set_fail_nth(int val) { char path[64]; snprintf(path, sizeof(path), "/proc/thread-self/fail-nth"); int fd = open(path, O_WRONLY); if (fd < 0) { snprintf(path, sizeof(path), "/proc/self/task/%ld/fail-nth", (long)syscall(SYS_gettid)); fd = open(path, O_WRONLY); } if (fd < 0) { snprintf(path, sizeof(path), "/proc/self/fail-nth"); fd = open(path, O_WRONLY); } if (fd < 0) return -1; char buf[32]; int len = snprintf(buf, sizeof(buf), "%d", val); ssize_t written = write(fd, buf, len); close(fd); return (written == len) ? 0 : -1; } static int find_hidpp_device(char *out_name, size_t maxlen) { DIR *dir = opendir("/sys/bus/hid/drivers/logitech-hidpp-device"); if (dir) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; if (strcmp(entry->d_name, "bind") == 0) continue; if (strcmp(entry->d_name, "unbind") == 0) continue; if (strcmp(entry->d_name, "module") == 0) continue; if (strcmp(entry->d_name, "uevent") == 0) continue; if (strstr(entry->d_name, "046D:C262") || strstr(entry->d_name, "046d:c262")) { strncpy(out_name, entry->d_name, maxlen - 1); out_name[maxlen - 1] = '\0'; closedir(dir); return 0; } } closedir(dir); } dir = opendir("/sys/bus/hid/devices"); if (dir) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] == '.') continue; if (strstr(entry->d_name, "046D:C262") || strstr(entry->d_name, "046d:c262")) { strncpy(out_name, entry->d_name, maxlen - 1); out_name[maxlen - 1] = '\0'; closedir(dir); return 0; } } closedir(dir); } return -1; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Reproducer for general protection fault in input_ff_flush\n"); /* 1. Discover UDC device */ DIR *dir = opendir("/sys/class/udc"); if (!dir) { printf("[-] Failed to opendir /sys/class/udc: %s\n", strerror(errno)); exit(1); } printf("[+] opendir /sys/class/udc successful.\n"); char udc_name[UDC_NAME_MAX] = {0}; struct dirent *entry; while ((entry = readdir(dir)) != NULL) { if (entry->d_name[0] != '.') { strncpy(udc_name, entry->d_name, sizeof(udc_name) - 1); break; } } int res = closedir(dir); if (res < 0) { printf("[-] Failed to closedir /sys/class/udc: %s\n", strerror(errno)); exit(1); } printf("[+] closedir /sys/class/udc successful.\n"); if (udc_name[0] == '\0') { printf("[-] No UDC device found in /sys/class/udc\n"); exit(1); } printf("[+] Using UDC device: %s\n", udc_name); /* 2. Open /dev/raw-gadget */ raw_fd = open("/dev/raw-gadget", O_RDWR); if (raw_fd < 0) { printf("[-] Failed to open /dev/raw-gadget: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/raw-gadget successful (fd=%d).\n", raw_fd); /* 3. Initialize raw-gadget */ struct usb_raw_init raw_init; memset(&raw_init, 0, sizeof(raw_init)); strncpy((char *)raw_init.driver_name, "dummy_udc", sizeof(raw_init.driver_name) - 1); strncpy((char *)raw_init.device_name, udc_name, sizeof(raw_init.device_name) - 1); raw_init.speed = USB_SPEED_FULL; res = ioctl(raw_fd, USB_RAW_IOCTL_INIT, &raw_init); if (res < 0) { printf("[-] Failed to USB_RAW_IOCTL_INIT: %s\n", strerror(errno)); exit(1); } printf("[+] USB_RAW_IOCTL_INIT successful.\n"); /* 4. Start USB emulation threads */ pthread_t th_ep0, th_ep_io; res = pthread_create(&th_ep0, NULL, ep0_loop, NULL); if (res != 0) { printf("[-] Failed to pthread_create ep0_loop: %s\n", strerror(res)); exit(1); } printf("[+] pthread_create ep0_loop successful.\n"); res = pthread_create(&th_ep_io, NULL, ep_io_loop, NULL); if (res != 0) { printf("[-] Failed to pthread_create ep_io_loop: %s\n", strerror(res)); exit(1); } printf("[+] pthread_create ep_io_loop successful.\n"); /* 5. Run USB gadget */ res = ioctl(raw_fd, USB_RAW_IOCTL_RUN, 0); if (res < 0) { printf("[-] Failed to USB_RAW_IOCTL_RUN: %s\n", strerror(errno)); exit(1); } printf("[+] USB_RAW_IOCTL_RUN successful.\n"); /* 6. Wait for initial device enumeration */ printf("[*] Waiting for Logitech G920 to enumerate and bind...\n"); char dev_id[128] = {0}; for (int i = 0; i < 50; i++) { usleep(100000); if (find_hidpp_device(dev_id, sizeof(dev_id)) == 0) { break; } } if (dev_id[0] == '\0') { printf("[-] Failed to find Logitech G920 device in sysfs within 5s\n"); exit(1); } printf("[+] Found Logitech G920 device: %s\n", dev_id); /* 7. Perform unbind/rebind loop with fault injection */ const char *unbind_path = "/sys/bus/hid/drivers/logitech-hidpp-device/unbind"; const char *bind_path = "/sys/bus/hid/drivers/logitech-hidpp-device/bind"; printf("[*] Starting deterministic fault injection rebind loop (1..100)...\n"); for (int fail_n = 1; fail_n <= 100; fail_n++) { /* Unbind current instance */ int ufd = open(unbind_path, O_WRONLY); if (ufd >= 0) { write(ufd, dev_id, strlen(dev_id)); close(ufd); } usleep(20000); /* Set fail-nth for probe in this thread */ set_fail_nth(fail_n); /* Rebind synchronously */ int bfd = open(bind_path, O_WRONLY); int bres = -1; if (bfd >= 0) { bres = write(bfd, dev_id, strlen(dev_id)); close(bfd); } /* Clear fail-nth */ set_fail_nth(0); if (bres > 0) { printf("[+] fail_n=%d: bind succeeded, scanning /dev/input to trigger bug...\n", fail_n); usleep(30000); DIR *idir = opendir("/dev/input"); if (idir) { struct dirent *de; while ((de = readdir(idir)) != NULL) { if (strncmp(de->d_name, "event", 5) == 0) { char path[64]; snprintf(path, sizeof(path), "/dev/input/%s", de->d_name); int ev_fd = open(path, O_RDWR | O_NONBLOCK); if (ev_fd >= 0) { printf("[*] Opened %s, closing now (should crash if ff teardown leaked flush)...\n", path); close(ev_fd); printf("[*] Closed %s.\n", path); } } } closedir(idir); } } else { /* Expected when fail-nth hits early probe allocations */ if (fail_n % 10 == 0) { printf("[*] Tested fail_n=%d\n", fail_n); } } usleep(10000); } printf("[*] Reproducer loop finished.\n"); running = 0; close(raw_fd); return 0; }