// https://syzkaller.appspot.com/bug?id=7349616606afa3c986c377792f7ccbf9daae1142 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Copyright 2026 syzkaller project authors. All rights reserved. // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. // IMPORTANT: Do not copy the macros or definitions below directly into your reproducer. // Instead, add the following line to your reproducer: // #include "race_toolkit.h" // --- Race Condition Toolkit --- // Macros and snippets for CPU pinning, memory barriers, and userfaultfd. #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Unbuffered I/O: Ensure logs are written immediately. #define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0) // CPU Pinning: Pin the current thread to a specific CPU core. #define PIN_TO_CPU(cpu) \ do { \ cpu_set_t mask; \ CPU_ZERO(&mask); \ CPU_SET(cpu, &mask); \ if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \ perror("sched_setaffinity"); \ } \ } while (0) // Memory Barrier: Ensure memory ordering. #define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST) // Spin-wait Barrier: Wait until a memory location has a specific value. // Best for tight race windows (low latency, no context switches). #define WAIT_ON(addr, val) \ do { \ while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \ ; \ } while (0) // Signal: Set a memory location to a specific value to release a WAIT_ON. #define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE) // --- Timing Primitives --- // Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps). static inline double timer_elapsed_sec(struct timespec* start) { struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) { perror("clock_gettime(CLOCK_MONOTONIC) elapsed"); exit(1); } return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9; } // Initialize a monotonic timer variable. #define TIMER_START(t) \ struct timespec t; \ if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \ perror("clock_gettime(CLOCK_MONOTONIC) start"); \ exit(1); \ } // Check if the elapsed time since 't' is less than 'sec' seconds. #define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec)) // Futex-based Event: Shared with syzkaller executor. // Best for general synchronization or longer waits to save CPU. typedef struct { int state; } event_t; static void event_init(event_t* ev) { ev->state = 0; } static void event_reset(event_t* ev) { ev->state = 0; } static void event_set(event_t* ev) { if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) { fprintf(stderr, "event already set\n"); exit(1); } __atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE); syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000); } static void event_wait(event_t* ev) { while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0); } // userfaultfd setup: Register a memory range for page fault handling. static int setup_uffd(void* addr, size_t len) { int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); if (uffd == -1) return -1; struct uffdio_api api = {.api = UFFD_API, .features = 0}; if (ioctl(uffd, UFFDIO_API, &api) == -1) { close(uffd); return -1; } struct uffdio_register reg = { .range = {.start = (uintptr_t)addr, .len = len}, .mode = UFFDIO_REGISTER_MODE_MISSING}; if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) { close(uffd); return -1; } return uffd; } // --- Guidance on Usage --- // 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead. // 2. Use event_t (futexes) for general coordination or when waiting for longer periods. // 3. Always use PIN_TO_CPU to increase race probability on multi-core systems. // 4. Use setup_uffd to register a memory range for page fault handling. This allows you to // pause a thread accessing that memory until you handle the fault, creating a reliable // and controllable race window. // 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed // immediately. This is essential for understanding the exact interleaving of events // when debugging race conditions. // 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL) // or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift. // Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED: // TIMER_START(start); // while (TIMER_NOT_EXPIRED(start, 10.0)) { // // Your race logic here // } #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, }; struct usb_raw_event { __u32 type; __u32 length; __u8 data[0]; }; struct usb_raw_ep_io { __u16 ep; __u16 flags; __u32 length; __u8 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) #define USB_RAW_IOCTL_EP_ENABLE _IOW('U', 5, struct usb_endpoint_descriptor) #define USB_RAW_IOCTL_EP_WRITE _IOW('U', 7, struct usb_raw_ep_io) #define USB_RAW_IOCTL_CONFIGURE _IO('U', 9) #define USB_RAW_IOCTL_EP0_STALL _IO('U', 12) #define USB_RAW_IOCTL_EP_SET_HALT _IOW('U', 13, __u32) struct usb_raw_event_ext { struct usb_raw_event inner; __u8 data[64]; }; struct usb_raw_ep_io_ext { struct usb_raw_ep_io inner; __u8 data[256]; }; struct usb_cdc_notification { __u8 bmRequestType; __u8 bNotificationType; __le16 wValue; __le16 wIndex; __le16 wLength; } __attribute__ ((packed)); struct usb_device_descriptor dev_desc = { .bLength = sizeof(dev_desc), .bDescriptorType = USB_DT_DEVICE, .bDeviceClass = 2, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, }; struct usb_cdc_header_desc { __u8 bLength; __u8 bDescriptorType; __u8 bDescriptorSubType; __le16 bcdCDC; } __attribute__((packed)); struct { struct usb_config_descriptor conf; struct usb_interface_descriptor intf; struct usb_cdc_header_desc cdc_hdr; struct usb_endpoint_descriptor ep_in; } __attribute__((packed)) conf_desc = { .conf = { .bLength = sizeof(conf_desc.conf), .bDescriptorType = USB_DT_CONFIG, .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, .bMaxPower = 50, }, .intf = { .bLength = sizeof(conf_desc.intf), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 1, .bInterfaceClass = 2, // USB_CLASS_COMM .bInterfaceSubClass = 9, // USB_CDC_SUBCLASS_DMM .bInterfaceProtocol = 0, .iInterface = 0, }, .cdc_hdr = { .bLength = sizeof(conf_desc.cdc_hdr), .bDescriptorType = 0x24, // USB_DT_CS_INTERFACE .bDescriptorSubType = 0x00, // USB_CDC_HEADER_TYPE }, .ep_in = { .bLength = sizeof(conf_desc.ep_in), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, // EP1 IN .bmAttributes = USB_ENDPOINT_XFER_INT, .bInterval = 1, }, }; void init_descriptors() { dev_desc.bcdUSB = htole16(0x0200); dev_desc.idVendor = htole16(0x0525); dev_desc.idProduct = htole16(0xa4a7); dev_desc.bcdDevice = htole16(0x0100); conf_desc.conf.wTotalLength = htole16(sizeof(conf_desc)); conf_desc.cdc_hdr.bcdCDC = htole16(0x0110); conf_desc.ep_in.wMaxPacketSize = htole16(64); } int fd_raw; int ep_in = -1; event_t gadget_ready; event_t read_blocked; event_t resolve_uffd; event_t notif2_sent; void setup_gadget() { init_descriptors(); fd_raw = open("/dev/raw-gadget", O_RDWR); if (fd_raw < 0) { perror("open /dev/raw-gadget"); exit(1); } struct usb_raw_init init = { .driver_name = "dummy_udc", .device_name = "dummy_udc.0", .speed = USB_SPEED_HIGH, }; if (ioctl(fd_raw, USB_RAW_IOCTL_INIT, &init) < 0) { perror("USB_RAW_IOCTL_INIT"); exit(1); } if (ioctl(fd_raw, USB_RAW_IOCTL_RUN, 0UL) < 0) { perror("USB_RAW_IOCTL_RUN"); exit(1); } } void *gadget_thread(void *arg) { setup_gadget(); printf("[+] Gadget initialized and running.\n"); while (1) { struct usb_raw_event_ext event; event.inner.type = 0; event.inner.length = sizeof(event.data); if (ioctl(fd_raw, USB_RAW_IOCTL_EVENT_FETCH, &event) < 0) { if (errno == EINTR) continue; perror("USB_RAW_IOCTL_EVENT_FETCH"); break; } if (event.inner.type == USB_RAW_EVENT_CONTROL) { struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)event.data; struct usb_raw_ep_io_ext io; io.inner.ep = 0; io.inner.flags = 0; io.inner.length = 0; if (req->bRequestType & USB_DIR_IN) { if (req->bRequest == USB_REQ_GET_DESCRIPTOR) { if (req->wValue == (USB_DT_DEVICE << 8)) { io.inner.length = sizeof(dev_desc); if (io.inner.length > req->wLength) io.inner.length = req->wLength; memcpy(io.data, &dev_desc, io.inner.length); ioctl(fd_raw, USB_RAW_IOCTL_EP0_WRITE, &io); } else if (req->wValue == (USB_DT_CONFIG << 8)) { io.inner.length = sizeof(conf_desc); if (io.inner.length > req->wLength) io.inner.length = req->wLength; memcpy(io.data, &conf_desc, io.inner.length); ioctl(fd_raw, USB_RAW_IOCTL_EP0_WRITE, &io); } else { ioctl(fd_raw, USB_RAW_IOCTL_EP0_STALL, 0UL); } } else if (req->bRequestType == 0xA1 && req->bRequest == 0x01) { static int resp_count = 0; if (resp_count == 0) { printf("[+] Received first Control Read (0xA1 0x01), waiting for Notif 2...\n"); event_wait(¬if2_sent); usleep(100000); // Give host time to process Notif 2 printf("[+] Responding to first Control Read.\n"); io.inner.length = 10; memset(io.data, 'A', 10); if (io.inner.length > req->wLength) io.inner.length = req->wLength; ioctl(fd_raw, USB_RAW_IOCTL_EP0_WRITE, &io); resp_count++; } else { printf("[+] Received subsequent Control Read (0xA1 0x01), ignoring to hang URB.\n"); // Do nothing, let the host's URB hang active indefinitely } } else { ioctl(fd_raw, USB_RAW_IOCTL_EP0_STALL, 0UL); } } else { // OUT request or zero-length if (req->bRequest == USB_REQ_SET_CONFIGURATION) { io.inner.length = req->wLength; ioctl(fd_raw, USB_RAW_IOCTL_EP0_READ, &io); ioctl(fd_raw, USB_RAW_IOCTL_CONFIGURE, 0UL); ep_in = ioctl(fd_raw, USB_RAW_IOCTL_EP_ENABLE, &conf_desc.ep_in); if (ep_in >= 0) { printf("[+] Endpoint EP1 IN enabled (index %d).\n", ep_in); if (!__atomic_load_n(&gadget_ready.state, __ATOMIC_ACQUIRE)) event_set(&gadget_ready); } else { printf("[-] Failed to enable EP1 IN\n"); } } else { io.inner.length = req->wLength; ioctl(fd_raw, USB_RAW_IOCTL_EP0_READ, &io); } } } } return NULL; } void *ep1_thread(void *arg) { event_wait(&gadget_ready); usleep(200000); // Wait for host to fully enumerate and submit int urb struct usb_cdc_notification notif = { .bmRequestType = 0xA1, .bNotificationType = 0x01, // USB_CDC_NOTIFY_RESPONSE_AVAILABLE .wValue = 0, .wIndex = 0, .wLength = 0, }; struct usb_raw_ep_io_ext io_ep1; io_ep1.inner.ep = ep_in; io_ep1.inner.flags = 0; io_ep1.inner.length = sizeof(notif); memcpy(io_ep1.data, ¬if, sizeof(notif)); printf("[+] Sending first notification on EP1...\n"); if (ioctl(fd_raw, USB_RAW_IOCTL_EP_WRITE, &io_ep1) < 0) { perror("USB_RAW_IOCTL_EP_WRITE 1"); } printf("[+] Sending second notification on EP1...\n"); if (ioctl(fd_raw, USB_RAW_IOCTL_EP_WRITE, &io_ep1) < 0) { perror("USB_RAW_IOCTL_EP_WRITE 2"); } printf("[+] Second notification sent.\n"); event_set(¬if2_sent); printf("[+] Waiting for read() to block in userfaultfd...\n"); event_wait(&read_blocked); printf("[+] Sending STALL on EP1 to trigger wdm_rxwork...\n"); if (ioctl(fd_raw, USB_RAW_IOCTL_EP_SET_HALT, (unsigned long)ep_in) < 0) { perror("USB_RAW_IOCTL_EP_SET_HALT"); } // Wait for wdm_rxwork to start and submit the URB usleep(200000); printf("[+] Resolving userfaultfd to finish read()...\n"); event_set(&resolve_uffd); return NULL; } void *uffd_thread(void *arg) { int uffd = (int)(long)arg; struct uffd_msg msg; struct pollfd pollfd; pollfd.fd = uffd; pollfd.events = POLLIN; if (poll(&pollfd, 1, -1) < 0) { perror("poll"); exit(1); } if (read(uffd, &msg, sizeof(msg)) != sizeof(msg)) { perror("read uffd"); exit(1); } if (msg.event == UFFD_EVENT_PAGEFAULT) { printf("[+] Page fault detected in read() buffer.\n"); event_set(&read_blocked); event_wait(&resolve_uffd); struct uffdio_copy copy; char *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (page == MAP_FAILED) { perror("mmap page"); exit(1); } memset(page, 0, 4096); copy.src = (uintptr_t)page; copy.dst = msg.arg.pagefault.address & ~(4095); copy.len = 4096; copy.mode = 0; copy.copy = 0; if (ioctl(uffd, UFFDIO_COPY, ©) < 0) { perror("UFFDIO_COPY"); } printf("[+] Page fault resolved.\n"); } return NULL; } int main() { SETUP_UNBUFFERED_IO(); event_init(&gadget_ready); event_init(&read_blocked); event_init(&resolve_uffd); event_init(¬if2_sent); pthread_t tid_gadget, tid_ep1, tid_uffd; pthread_create(&tid_gadget, NULL, gadget_thread, NULL); pthread_create(&tid_ep1, NULL, ep1_thread, NULL); event_wait(&gadget_ready); printf("[+] Waiting for /dev/cdc-wdmX to appear...\n"); usleep(500000); char dev_path[64] = "/dev/cdc-wdm0"; int fd_wdm = -1; for (int i = 0; i < 10; i++) { sprintf(dev_path, "/dev/cdc-wdm%d", i); fd_wdm = open(dev_path, O_RDWR); if (fd_wdm >= 0) { printf("[+] Opened %s\n", dev_path); break; } } if (fd_wdm < 0) { sleep(1); for (int i = 0; i < 10; i++) { sprintf(dev_path, "/dev/cdc-wdm%d", i); fd_wdm = open(dev_path, O_RDWR); if (fd_wdm >= 0) { printf("[+] Opened %s\n", dev_path); break; } } if (fd_wdm < 0) { fprintf(stderr, "[-] Failed to open cdc-wdm device\n"); exit(1); } } void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (addr == MAP_FAILED) { perror("mmap"); exit(1); } int uffd = setup_uffd(addr, 4096); if (uffd < 0) { fprintf(stderr, "[-] Failed to setup uffd\n"); exit(1); } pthread_create(&tid_uffd, NULL, uffd_thread, (void *)(long)uffd); printf("[+] Calling read() on %s...\n", dev_path); int ret = read(fd_wdm, addr, 10); if (ret < 0) { perror("read fd_wdm"); } else { printf("[+] read() returned %d\n", ret); } // Wait a bit for the WARNING to be printed sleep(2); printf("[+] Done.\n"); return 0; }