// https://syzkaller.appspot.com/bug?id=a36354d834da6632b84620883dfc162313b4e8f9 #define _GNU_SOURCE // 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 // } #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct usbip_header_basic { uint32_t command; uint32_t seqnum; uint32_t devid; uint32_t direction; uint32_t ep; } __attribute__((packed)); struct usbip_header_cmd_submit { uint32_t transfer_flags; int32_t transfer_buffer_length; int32_t start_frame; int32_t number_of_packets; int32_t interval; unsigned char setup[8]; } __attribute__((packed)); struct usbip_header_ret_submit { int32_t status; int32_t actual_length; int32_t start_frame; int32_t number_of_packets; int32_t error_count; } __attribute__((packed)); struct usbip_header { struct usbip_header_basic base; union { struct usbip_header_cmd_submit cmd_submit; struct usbip_header_ret_submit ret_submit; char padding[28]; } u; } __attribute__((packed)); struct my_usbdevfs_urb { unsigned char type; unsigned char endpoint; int status; unsigned int flags; void *buffer; int buffer_length; int actual_length; int start_frame; int number_of_packets; int error_count; unsigned int signr; void *usercontext; }; #define MY_USBDEVFS_SUBMITURB _IOR('U', 10, struct my_usbdevfs_urb) #define MY_USBDEVFS_REAPURB _IOW('U', 12, struct my_usbdevfs_urb *) #define MY_USBDEVFS_CLAIMINTERFACE _IOR('U', 15, unsigned int) uint8_t dev_desc[] = { 18, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0xcd, 0xab, 0x01, 0xef, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01 }; uint8_t conf_desc[] = { 9, 0x02, 32, 0x00, 1, 1, 0, 0x80, 0x32, 9, 0x04, 0, 0, 2, 0xff, 0xff, 0xff, 0, 7, 0x05, 0x81, 0x02, 0x00, 0x02, 0x00, 7, 0x05, 0x02, 0x02, 0x00, 0x02, 0x00 }; int send_exact(int fd, const void *buf, size_t len) { size_t sent = 0; while (sent < len) { int res = send(fd, (const char *)buf + sent, len - sent, MSG_NOSIGNAL); if (res < 0) { if (errno == EINTR) continue; if (errno == EPIPE || errno == ECONNRESET || errno == ENOTCONN) return -1; printf("[-] Failed to send: %s\n", strerror(errno)); exit(1); } if (res == 0) return -1; sent += res; } return 0; } void send_ret_submit(int fd, uint32_t seqnum, int status, int actual_length, void *data) { struct usbip_header ret_pdu; memset(&ret_pdu, 0, sizeof(ret_pdu)); ret_pdu.base.command = htonl(0x0003); ret_pdu.base.seqnum = htonl(seqnum); ret_pdu.u.ret_submit.status = htonl(status); ret_pdu.u.ret_submit.actual_length = htonl(actual_length); if (send_exact(fd, &ret_pdu, sizeof(ret_pdu)) < 0) return; if (actual_length > 0 && data) { send_exact(fd, data, actual_length); } } int handle_ep0(int fd, struct usbip_header *pdu) { uint32_t seqnum = ntohl(pdu->base.seqnum); uint32_t dir = ntohl(pdu->base.direction); int32_t len = ntohl(pdu->u.cmd_submit.transfer_buffer_length); unsigned char *setup = pdu->u.cmd_submit.setup; if (dir == 0 && len > 0) { char buf[4096]; int remaining = len; while (remaining > 0) { int to_read = remaining > sizeof(buf) ? sizeof(buf) : remaining; int r = recv(fd, buf, to_read, MSG_WAITALL); if (r <= 0) return -1; remaining -= r; } } int actual_len = 0; void *data = NULL; uint8_t bRequestType = setup[0]; uint8_t bRequest = setup[1]; uint16_t wValue = setup[2] | (setup[3] << 8); uint16_t wLength = setup[6] | (setup[7] << 8); if (bRequestType == 0x80 && bRequest == 0x06) { uint8_t desc_type = wValue >> 8; if (desc_type == 0x01) { data = dev_desc; actual_len = sizeof(dev_desc); } else if (desc_type == 0x02) { data = conf_desc; actual_len = sizeof(conf_desc); } if (actual_len > wLength) actual_len = wLength; if (actual_len > len) actual_len = len; } send_ret_submit(fd, seqnum, 0, actual_len, data); return 0; } void pin_thread(int cpu) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); } void set_thread_fifo(pid_t pid) { struct sched_param param; param.sched_priority = 50; sched_setscheduler(pid, SCHED_FIFO, ¶m); } void pin_and_boost_vhci_threads() { DIR *dir = opendir("/proc"); if (!dir) return; struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (ent->d_type == DT_DIR || ent->d_type == DT_UNKNOWN) { pid_t pid = atoi(ent->d_name); if (pid > 0) { char path[256]; snprintf(path, sizeof(path), "/proc/%d/comm", pid); FILE *f = fopen(path, "r"); if (f) { char comm[256]; if (fgets(comm, sizeof(comm), f)) { if (strncmp(comm, "vhci_tx", 7) == 0) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); sched_setaffinity(pid, sizeof(cpu_set_t), &cpuset); } else if (strncmp(comm, "vhci_rx", 7) == 0) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); sched_setaffinity(pid, sizeof(cpu_set_t), &cpuset); set_thread_fifo(pid); } } fclose(f); } } } } closedir(dir); } int find_usb_device(char *devpath) { DIR *dir = opendir("/sys/bus/usb/devices"); if (!dir) return 0; struct dirent *ent; int found = 0; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] == '.' || strchr(ent->d_name, ':')) continue; char path[256]; snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/idVendor", ent->d_name); FILE *f = fopen(path, "r"); if (!f) continue; char vendor[16] = {0}; if (!fgets(vendor, sizeof(vendor), f)) { fclose(f); continue; } fclose(f); snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/idProduct", ent->d_name); f = fopen(path, "r"); if (!f) continue; char product[16] = {0}; if (!fgets(product, sizeof(product), f)) { fclose(f); continue; } fclose(f); if (strncmp(vendor, "abcd", 4) == 0 && strncmp(product, "ef01", 4) == 0) { snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/busnum", ent->d_name); f = fopen(path, "r"); int busnum = 0; if (f) { if(fscanf(f, "%d", &busnum) != 1) busnum = 0; fclose(f); } snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/devnum", ent->d_name); f = fopen(path, "r"); int devnum = 0; if (f) { if(fscanf(f, "%d", &devnum) != 1) devnum = 0; fclose(f); } if (busnum > 0 && devnum > 0) { snprintf(devpath, 256, "/dev/bus/usb/%03d/%03d", busnum, devnum); found = 1; break; } } } closedir(dir); return found; } volatile int stop_flag = 0; volatile int device_ready = 0; void *userspace_thread(void *arg) { pin_thread(2); int first_iter = 1; while (!stop_flag) { if (!device_ready) { usleep(10000); continue; } char devpath[256]; if (!find_usb_device(devpath)) { usleep(10000); continue; } if (first_iter) printf("[+] find_usb_device successful: %s\n", devpath); int fd = open(devpath, O_RDWR); if (fd < 0) { if (errno == ENOENT || errno == EACCES) { usleep(10000); continue; } printf("[-] Failed to open usb device: %s\n", strerror(errno)); exit(1); } if (first_iter) printf("[+] open usb device successful.\n"); unsigned int intf = 0; int r = ioctl(fd, MY_USBDEVFS_CLAIMINTERFACE, &intf); if (r < 0) { if (errno == EBUSY || errno == ENODEV) { close(fd); usleep(10000); continue; } printf("[-] Failed to claim interface: %s\n", strerror(errno)); exit(1); } if (first_iter) printf("[+] claim interface successful.\n"); while (!stop_flag && device_ready) { struct my_usbdevfs_urb urb1, urb2; memset(&urb1, 0, sizeof(urb1)); urb1.type = 3; // BULK urb1.endpoint = 0x81; // EP 1 IN urb1.buffer = malloc(512); urb1.buffer_length = 512; memset(&urb2, 0, sizeof(urb2)); urb2.type = 3; // BULK urb2.endpoint = 0x81; // EP 1 IN urb2.buffer = malloc(512); urb2.buffer_length = 512; int r1 = ioctl(fd, MY_USBDEVFS_SUBMITURB, &urb1); int r2 = ioctl(fd, MY_USBDEVFS_SUBMITURB, &urb2); if (r1 < 0 || r2 < 0) { free(urb1.buffer); free(urb2.buffer); if (errno == ENODEV || errno == ENOENT || errno == ESHUTDOWN || errno == EPROTO) { break; } printf("[-] Failed to submit urb: %s\n", strerror(errno)); exit(1); } struct my_usbdevfs_urb *ret_urb = NULL; r = ioctl(fd, MY_USBDEVFS_REAPURB, &ret_urb); if (r < 0 && errno != ENODEV && errno != ENOENT && errno != ESHUTDOWN && errno != EPROTO) { printf("[-] Failed to reap urb: %s\n", strerror(errno)); exit(1); } r = ioctl(fd, MY_USBDEVFS_REAPURB, &ret_urb); if (r < 0 && errno != ENODEV && errno != ENOENT && errno != ESHUTDOWN && errno != EPROTO) { printf("[-] Failed to reap urb: %s\n", strerror(errno)); exit(1); } free(urb1.buffer); free(urb2.buffer); } close(fd); first_iter = 0; } return NULL; } void *fake_server_thread(void *arg) { pin_thread(1); int port = 0; int sv[2] = {-1, -1}; int current_delay = 5000; int first_iter = 1; while (!stop_flag) { int res = socketpair(AF_UNIX, SOCK_STREAM, 0, sv); if (res < 0) { printf("[-] Failed to socketpair: %s\n", strerror(errno)); exit(1); } if (first_iter) printf("[+] socketpair successful.\n"); char attach_cmd[256]; sprintf(attach_cmd, "%d %d 1 2", port, sv[0]); int fd = open("/sys/devices/platform/vhci_hcd.0/attach", O_WRONLY); if (fd < 0) { if (errno == ENOENT || errno == EBUSY) { close(sv[0]); close(sv[1]); usleep(100000); continue; } printf("[-] Failed to open attach: %s\n", strerror(errno)); exit(1); } if (first_iter) printf("[+] open attach successful.\n"); res = write(fd, attach_cmd, strlen(attach_cmd)); if (res < 0) { if (errno == EINVAL || errno == EBUSY) { close(fd); close(sv[0]); close(sv[1]); usleep(100000); continue; } printf("[-] Failed to write attach: %s\n", strerror(errno)); exit(1); } if (first_iter) printf("[+] write attach successful.\n"); close(fd); usleep(50000); // Wait for vhci threads to start pin_and_boost_vhci_threads(); device_ready = 1; uint32_t speculatively_replied_seqnum = 0; int has_speculative = 0; while (!stop_flag) { struct pollfd pfd = {sv[1], POLLIN, 0}; int r = poll(&pfd, 1, 1000); if (r < 0) { printf("[-] Failed to poll: %s\n", strerror(errno)); exit(1); } if (r > 0) { struct usbip_header pdu; int n = recv(sv[1], &pdu, sizeof(pdu), MSG_WAITALL); if (n < 0) { if (errno == ECONNRESET || errno == EPIPE || errno == ENOTCONN) break; printf("[-] Failed to recv: %s\n", strerror(errno)); exit(1); } if (n == sizeof(pdu)) { uint32_t seqnum = ntohl(pdu.base.seqnum); uint32_t ep = ntohl(pdu.base.ep); if (ep == 0) { handle_ep0(sv[1], &pdu); } else { if (has_speculative && seqnum == speculatively_replied_seqnum) { // We already replied to this URB speculatively. Ignore it. has_speculative = 0; } else { // Normal reply for URB 1 send_ret_submit(sv[1], seqnum, 0, 0, NULL); // Speculative reply for URB 2 for (volatile int i = 0; i < current_delay; i++) { asm volatile("": : :"memory"); } send_ret_submit(sv[1], seqnum + 1, 0, 0, NULL); speculatively_replied_seqnum = seqnum + 1; has_speculative = 1; // Decrease delay to find the exact UAF window current_delay -= 50; if (current_delay < 0) current_delay = 0; } } } else if (n <= 0) { // Connection dropped (speculative reply was too early) current_delay += 200; if (current_delay > 20000) current_delay = 20000; break; } } else if (r == 0) { // Timeout, unstick userspace break; } } device_ready = 0; fd = open("/sys/devices/platform/vhci_hcd.0/detach", O_WRONLY); if (fd >= 0) { char detach_cmd[16]; sprintf(detach_cmd, "%d", port); res = write(fd, detach_cmd, strlen(detach_cmd)); close(fd); } if (sv[0] >= 0) { close(sv[0]); sv[0] = -1; } if (sv[1] >= 0) { close(sv[1]); sv[1] = -1; } first_iter = 0; } return NULL; } int main() { if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { printf("[-] Failed to signal: %s\n", strerror(errno)); exit(1); } printf("[+] signal successful.\n"); SETUP_UNBUFFERED_IO(); srand(time(NULL)); pthread_t server_tid, user_tid; int res = pthread_create(&server_tid, NULL, fake_server_thread, NULL); if (res != 0) { printf("[-] Failed to pthread_create: %s\n", strerror(res)); exit(1); } printf("[+] pthread_create server successful.\n"); res = pthread_create(&user_tid, NULL, userspace_thread, NULL); if (res != 0) { printf("[-] Failed to pthread_create: %s\n", strerror(res)); exit(1); } printf("[+] pthread_create user successful.\n"); TIMER_START(start); while (TIMER_NOT_EXPIRED(start, 20.0)) { sleep(1); } stop_flag = 1; pthread_join(server_tid, NULL); pthread_join(user_tid, NULL); printf("[+] Finished.\n"); return 0; }