// https://syzkaller.appspot.com/bug?id=0a557b5640efcff9b933c25e4f03079c5fd9dec7 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() #include #endif #undef htole16 #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN #define htole16(x) ((uint16_t)((((uint16_t)(x) & 0x00ff) << 8) | (((uint16_t)(x) & 0xff00) >> 8))) #else #define htole16(x) ((uint16_t)(x)) #endif #ifndef USB_RAW_IOCTL_INIT #define UDC_NAME_LENGTH_MAX 128 struct usb_raw_init { __u8 driver_name[UDC_NAME_LENGTH_MAX]; __u8 device_name[UDC_NAME_LENGTH_MAX]; __u8 speed; }; enum usb_raw_event_type { USB_RAW_EVENT_INVALID = 0, USB_RAW_EVENT_CONNECT = 1, USB_RAW_EVENT_CONTROL = 2, USB_RAW_EVENT_SUSPEND = 3, USB_RAW_EVENT_RESUME = 4, USB_RAW_EVENT_RESET = 5, USB_RAW_EVENT_DISCONNECT = 6, }; struct usb_raw_event { __u32 type; __u32 length; __u8 data[]; }; struct usb_raw_ep_io { __u16 ep; __u16 flags; __u32 length; __u8 data[]; }; #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) #define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor) #define USB_RAW_IOCTL_CONFIGURE _IO('U', 9) #endif #ifndef USB_SPEED_HIGH #define USB_SPEED_HIGH 3 #endif struct usb_endpoint_descriptor_7 { __u8 bLength; __u8 bDescriptorType; __u8 bEndpointAddress; __u8 bmAttributes; __le16 wMaxPacketSize; __u8 bInterval; } __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(0x17ef), /* Lenovo AX88772B supported by asix.ko */ .idProduct = htole16(0x7203), .bcdDevice = htole16(0x0200), .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, }; static const struct { struct usb_config_descriptor config; struct usb_interface_descriptor intf; struct usb_endpoint_descriptor_7 ep_in; struct usb_endpoint_descriptor_7 ep_out; } __attribute__((packed)) config_bundle = { .config = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = USB_DT_CONFIG, .wTotalLength = htole16(sizeof(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 = 0xff, .bInterfaceSubClass = 0xff, .bInterfaceProtocol = 0x00, .iInterface = 0, }, .ep_in = { .bLength = sizeof(struct usb_endpoint_descriptor_7), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x82, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, .ep_out = { .bLength = sizeof(struct usb_endpoint_descriptor_7), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x01, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, }; static volatile sig_atomic_t alarm_fired = 0; static void sigalrm_handler(int sig) { (void)sig; alarm_fired = 1; } static int ep0_write(int fd, const void *data, uint32_t len) { struct { struct usb_raw_ep_io io; uint8_t buf[512]; } req; memset(&req, 0, sizeof(req)); req.io.ep = 0; req.io.flags = 0; req.io.length = len > sizeof(req.buf) ? sizeof(req.buf) : len; if (req.io.length && data) memcpy(req.buf, data, req.io.length); int res = ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, &req); if (res < 0) { printf("[-] ioctl USB_RAW_IOCTL_EP0_WRITE error: %s\n", strerror(errno)); } return res; } static int ep0_read(int fd, uint32_t len) { struct { struct usb_raw_ep_io io; uint8_t buf[512]; } req; memset(&req, 0, sizeof(req)); req.io.ep = 0; req.io.flags = 0; req.io.length = len > sizeof(req.buf) ? sizeof(req.buf) : len; int res = ioctl(fd, USB_RAW_IOCTL_EP0_READ, &req); if (res < 0) { printf("[-] ioctl USB_RAW_IOCTL_EP0_READ error: %s\n", strerror(errno)); } return res; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for suspicious RCU usage in phy_detach_internal...\n"); struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigalrm_handler; int res = sigaction(SIGALRM, &sa, NULL); if (res < 0) { printf("[-] Failed to set sigaction for SIGALRM: %s\n", strerror(errno)); exit(1); } printf("[+] sigaction for SIGALRM successful.\n"); alarm(15); 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"); struct dirent *ent; char udc_dev[UDC_NAME_LENGTH_MAX] = {0}; char udc_drv[UDC_NAME_LENGTH_MAX] = {0}; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] == '.') continue; strncpy(udc_dev, ent->d_name, sizeof(udc_dev) - 1); strncpy(udc_drv, ent->d_name, sizeof(udc_drv) - 1); char *dot = strchr(udc_drv, '.'); if (dot) *dot = '\0'; break; } 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_dev[0] == '\0') { printf("[-] No UDC found under /sys/class/udc\n"); exit(1); } printf("[+] Found UDC: device '%s', driver '%s'\n", udc_dev, udc_drv); int fd = open("/dev/raw-gadget", O_RDWR); if (fd < 0) { printf("[-] Failed to open /dev/raw-gadget: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/raw-gadget successful.\n"); struct usb_raw_init init; memset(&init, 0, sizeof(init)); strncpy((char *)init.driver_name, udc_drv, sizeof(init.driver_name) - 1); strncpy((char *)init.device_name, udc_dev, sizeof(init.device_name) - 1); init.speed = USB_SPEED_HIGH; res = ioctl(fd, USB_RAW_IOCTL_INIT, &init); if (res < 0) { printf("[-] Failed to ioctl USB_RAW_IOCTL_INIT: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl USB_RAW_IOCTL_INIT successful.\n"); res = ioctl(fd, USB_RAW_IOCTL_RUN, 0); if (res < 0) { printf("[-] Failed to ioctl USB_RAW_IOCTL_RUN: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl USB_RAW_IOCTL_RUN successful.\n"); bool bmsr_seen = false; bool configured = false; int post_trigger_count = 0; for (int iter = 0; iter < 300 && !alarm_fired; iter++) { struct { struct usb_raw_event event; uint8_t data[256]; } ev; memset(&ev, 0, sizeof(ev)); ev.event.length = sizeof(ev.data); res = ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, &ev); if (res < 0) { if (errno == EINTR) { printf("[+] ioctl USB_RAW_IOCTL_EVENT_FETCH interrupted by signal.\n"); break; } printf("[-] Failed to ioctl USB_RAW_IOCTL_EVENT_FETCH: %s\n", strerror(errno)); break; } if (ev.event.type != USB_RAW_EVENT_CONTROL) { printf("[*] Fetched non-control event type: %u\n", ev.event.type); continue; } struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)ev.event.data; uint16_t wLength = le16toh(ctrl->wLength); uint16_t wValue = le16toh(ctrl->wValue); uint16_t wIndex = le16toh(ctrl->wIndex); printf("[*] Control request: type=0x%02x, req=0x%02x, val=0x%04x, idx=0x%04x, len=%u\n", ctrl->bRequestType, ctrl->bRequest, wValue, wIndex, wLength); if ((ctrl->bRequestType & USB_DIR_IN) && wLength > 0) { uint8_t resp[64] = {0}; uint32_t resp_len = 0; if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR) { uint8_t desc_type = wValue >> 8; if (desc_type == USB_DT_DEVICE) { resp_len = sizeof(dev_desc) < wLength ? sizeof(dev_desc) : wLength; memcpy(resp, &dev_desc, resp_len); printf("[+] Returning Device Descriptor (%u bytes).\n", resp_len); } else if (desc_type == USB_DT_CONFIG) { resp_len = sizeof(config_bundle) < wLength ? sizeof(config_bundle) : wLength; memcpy(resp, &config_bundle, resp_len); printf("[+] Returning Config Bundle (%u bytes).\n", resp_len); } } else { resp_len = wLength < sizeof(resp) ? wLength : sizeof(resp); } } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { switch (ctrl->bRequest) { case 0x0b: /* AX_CMD_READ_EEPROM */ /* Return valid locally-administered unicast MAC bytes */ resp[0] = 0x02; resp[1] = (uint8_t)(wValue & 0xff); resp_len = 2 < wLength ? 2 : wLength; printf("[+] Handled AX_CMD_READ_EEPROM (offset 0x%04x)\n", wValue); break; case 0x13: /* AX_CMD_READ_NODE_ID */ resp[0] = 0x02; resp[1] = 0x11; resp[2] = 0x22; resp[3] = 0x33; resp[4] = 0x44; resp[5] = 0x55; resp_len = 6 < wLength ? 6 : wLength; printf("[+] Handled AX_CMD_READ_NODE_ID\n"); break; case 0x19: /* AX_CMD_READ_PHY_ID */ /* Internal PHY address is 0x10 (16) */ resp[0] = 0x10; resp[1] = 0x10; resp_len = 2 < wLength ? 2 : wLength; printf("[+] Handled AX_CMD_READ_PHY_ID (PHY address 0x10)\n"); break; case 0x09: /* AX_CMD_STATMNGSTS_REG */ /* AX_AX88772B_CHIPCODE (0x20) | AX_HOST_EN (0x01) = 0x21 */ resp[0] = 0x21; resp_len = 1 < wLength ? 1 : wLength; printf("[+] Handled AX_CMD_STATMNGSTS_REG (chipcode 0x20 | host_en 0x01)\n"); break; case 0x07: /* AX_CMD_READ_MII_REG */ if (wIndex == 2) { /* MII_PHYSID1 */ resp[0] = 0x00; resp[1] = 0x00; printf("[+] Handled AX_CMD_READ_MII_REG (MII_PHYSID1 = 0x0000)\n"); } else if (wIndex == 3) { /* MII_PHYSID2 -> PHY ID 0x00000123 (binds genphy) */ resp[0] = 0x23; resp[1] = 0x01; printf("[+] Handled AX_CMD_READ_MII_REG (MII_PHYSID2 = 0x0123)\n"); } else if (wIndex == 1) { /* MII_BMSR = 0x0000 -> zero capabilities triggers phylink validation failure */ resp[0] = 0x00; resp[1] = 0x00; printf("[+] Handled AX_CMD_READ_MII_REG (MII_BMSR = 0x0000) -> Triggering phylink failure!\n"); bmsr_seen = true; alarm(3); } else { /* MII_BMCR (0) = 0x0000 -> reset poll completes */ resp[0] = 0x00; resp[1] = 0x00; printf("[+] Handled AX_CMD_READ_MII_REG (reg %u = 0x0000)\n", wIndex); } resp_len = 2 < wLength ? 2 : wLength; break; default: resp_len = wLength < sizeof(resp) ? wLength : sizeof(resp); printf("[*] Default vendor IN request 0x%02x, returning %u zero bytes\n", ctrl->bRequest, resp_len); break; } } else { resp_len = wLength < sizeof(resp) ? wLength : sizeof(resp); } ep0_write(fd, resp, resp_len); } else { /* OUT request or 0-length IN request */ ep0_read(fd, wLength); if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD && ctrl->bRequest == USB_REQ_SET_CONFIGURATION && !configured) { configured = true; printf("[*] Standard USB_REQ_SET_CONFIGURATION received. Configuring endpoints...\n"); res = ioctl(fd, USB_RAW_IOCTL_CONFIGURE, 0); if (res < 0) { printf("[-] ioctl USB_RAW_IOCTL_CONFIGURE failed: %s\n", strerror(errno)); } else { printf("[+] ioctl USB_RAW_IOCTL_CONFIGURE successful.\n"); } struct usb_endpoint_descriptor ep_in_desc; memset(&ep_in_desc, 0, sizeof(ep_in_desc)); memcpy(&ep_in_desc, &config_bundle.ep_in, sizeof(config_bundle.ep_in)); res = ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &ep_in_desc); if (res < 0) { printf("[-] ioctl USB_RAW_IOCTL_EP_ENABLE (ep_in) failed: %s\n", strerror(errno)); } else { printf("[+] ioctl USB_RAW_IOCTL_EP_ENABLE (ep_in) successful (handle %d).\n", res); } struct usb_endpoint_descriptor ep_out_desc; memset(&ep_out_desc, 0, sizeof(ep_out_desc)); memcpy(&ep_out_desc, &config_bundle.ep_out, sizeof(config_bundle.ep_out)); res = ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &ep_out_desc); if (res < 0) { printf("[-] ioctl USB_RAW_IOCTL_EP_ENABLE (ep_out) failed: %s\n", strerror(errno)); } else { printf("[+] ioctl USB_RAW_IOCTL_EP_ENABLE (ep_out) successful (handle %d).\n", res); } } } if (bmsr_seen) { if (++post_trigger_count > 30) { printf("[+] Served sufficient post-trigger events (%d). Finishing loop.\n", post_trigger_count); break; } } } alarm(0); printf("[+] Waiting 2 seconds for kernel asynchronous workqueue to flush splat...\n"); sleep(2); res = close(fd); if (res < 0) { printf("[-] Failed to close /dev/raw-gadget: %s\n", strerror(errno)); exit(1); } printf("[+] close /dev/raw-gadget successful.\n"); printf("[+] Reproducer run completed successfully.\n"); return 0; }