// https://syzkaller.appspot.com/bug?id=3be77b1054d3b5c466012a7e957fdb2b0b8adafd #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 // } // DVB ioctls and structs #define DMX_SET_PES_FILTER _IOW('o', 44, struct dmx_pes_filter_params) enum dmx_input { DMX_IN_FRONTEND, DMX_IN_DVR }; enum dmx_output { DMX_OUT_TAP, DMX_OUT_TS_TAP, DMX_OUT_TSDEMUX_TAP }; enum dmx_ts_pes { DMX_PES_AUDIO0, DMX_PES_VIDEO0, DMX_PES_TELETEXT0, DMX_PES_SUBTITLE0, DMX_PES_PCR0, DMX_PES_AUDIO1, DMX_PES_VIDEO1, DMX_PES_TELETEXT1, DMX_PES_SUBTITLE1, DMX_PES_PCR1, DMX_PES_AUDIO2, DMX_PES_VIDEO2, DMX_PES_TELETEXT2, DMX_PES_SUBTITLE2, DMX_PES_PCR2, DMX_PES_AUDIO3, DMX_PES_VIDEO3, DMX_PES_TELETEXT3, DMX_PES_SUBTITLE3, DMX_PES_PCR3, DMX_PES_OTHER }; struct dmx_pes_filter_params { uint16_t pid; enum dmx_input input; enum dmx_output output; enum dmx_ts_pes pes_type; uint32_t flags; }; #define DMX_IMMEDIATE_START 4 static int find_udc(char *udc_name, size_t max_len) { DIR *dir = opendir("/sys/class/udc"); if (!dir) return -1; struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] == '.') continue; strncpy(udc_name, ent->d_name, max_len); closedir(dir); return 0; } closedir(dir); return -1; } void* race_thread(void* arg) { TIMER_START(start); while (TIMER_NOT_EXPIRED(start, 10.0)) { for (int i = 0; i < 10; i++) { char path[256]; snprintf(path, sizeof(path), "/dev/dvb/adapter%d/demux0", i); int fd = open(path, O_RDWR); if (fd >= 0) { struct dmx_pes_filter_params pes; memset(&pes, 0, sizeof(pes)); pes.pid = 0x1000; pes.input = DMX_IN_FRONTEND; pes.output = DMX_OUT_TS_TAP; pes.pes_type = DMX_PES_OTHER; pes.flags = DMX_IMMEDIATE_START; ioctl(fd, DMX_SET_PES_FILTER, &pes); close(fd); } } } return NULL; } int main() { SETUP_UNBUFFERED_IO(); pthread_t th[4]; for (int i = 0; i < 4; i++) { pthread_create(&th[i], NULL, race_thread, NULL); } 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("[+] Opened /dev/raw-gadget\n"); char udc_name[256] = {0}; if (find_udc(udc_name, sizeof(udc_name)) < 0) { strcpy(udc_name, "dummy_udc.0"); // fallback } struct usb_raw_init init; memset(&init, 0, sizeof(init)); strcpy((char*)init.driver_name, "dummy_udc"); strcpy((char*)init.device_name, udc_name); init.speed = USB_SPEED_HIGH; if (ioctl(fd, USB_RAW_IOCTL_INIT, &init) < 0) { printf("[-] USB_RAW_IOCTL_INIT failed: %s\n", strerror(errno)); exit(1); } 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)); exit(1); } printf("[+] USB_RAW_IOCTL_RUN successful\n"); struct usb_device_descriptor dev_desc = { .bLength = sizeof(dev_desc), .bDescriptorType = USB_DT_DEVICE, .bcdUSB = htole16(0x0200), .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = htole16(0x2040), .idProduct = htole16(0xc604), .bcdDevice = htole16(0x0100), .iManufacturer = 0, .iProduct = 0, .iSerialNumber = 0, .bNumConfigurations = 1, }; struct { struct usb_config_descriptor config; struct usb_interface_descriptor interface; struct usb_endpoint_descriptor ep1; struct usb_endpoint_descriptor ep2; struct usb_endpoint_descriptor ep3; } __attribute__((packed)) conf_desc = { .config = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = USB_DT_CONFIG, .wTotalLength = htole16(sizeof(conf_desc)), .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, .bMaxPower = 50, }, .interface = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 3, .bInterfaceClass = 0xff, .bInterfaceSubClass = 0xff, .bInterfaceProtocol = 0xff, .iInterface = 0, }, .ep1 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x02, // bulk out .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, .ep2 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, // bulk in .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }, .ep3 = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x86, // isoc in .bmAttributes = USB_ENDPOINT_XFER_ISOC, .wMaxPacketSize = htole16(3072), .bInterval = 1, } }; struct usb_raw_event *event = malloc(sizeof(struct usb_raw_event) + 64); if (!event) { printf("[-] malloc failed\n"); exit(1); } TIMER_START(start); while (TIMER_NOT_EXPIRED(start, 10.0)) { event->length = 64; if (ioctl(fd, USB_RAW_IOCTL_EVENT_FETCH, event) < 0) { if (errno == EINTR || errno == EAGAIN) continue; break; } if (event->type == USB_RAW_EVENT_CONNECT) { printf("[+] USB_RAW_EVENT_CONNECT\n"); } else if (event->type == USB_RAW_EVENT_CONTROL) { struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)event->data; if (req->bRequestType == USB_DIR_IN && req->bRequest == USB_REQ_GET_DESCRIPTOR) { int desc_type = le16toh(req->wValue) >> 8; if (desc_type == USB_DT_DEVICE) { struct usb_raw_ep_io *io = malloc(sizeof(struct usb_raw_ep_io) + sizeof(dev_desc)); io->ep = 0; io->flags = 0; io->length = sizeof(dev_desc); if (io->length > le16toh(req->wLength)) io->length = le16toh(req->wLength); memcpy(io->data, &dev_desc, io->length); ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io); free(io); } else if (desc_type == USB_DT_CONFIG) { struct usb_raw_ep_io *io = malloc(sizeof(struct usb_raw_ep_io) + sizeof(conf_desc)); io->ep = 0; io->flags = 0; io->length = sizeof(conf_desc); if (io->length > le16toh(req->wLength)) io->length = le16toh(req->wLength); memcpy(io->data, &conf_desc, io->length); ioctl(fd, USB_RAW_IOCTL_EP0_WRITE, io); free(io); } else { ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0); } } else if (le16toh(req->wLength) == 0) { struct usb_raw_ep_io *io = malloc(sizeof(struct usb_raw_ep_io)); io->ep = 0; io->flags = 0; io->length = 0; int res = ioctl(fd, USB_RAW_IOCTL_EP0_READ, io); if (res < 0) { printf("[-] EP0_READ failed: %s\n", strerror(errno)); } free(io); if (req->bRequestType == 0 && req->bRequest == USB_REQ_SET_CONFIGURATION) { printf("[+] SET_CONFIGURATION received\n"); ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &conf_desc.ep1); ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &conf_desc.ep2); ioctl(fd, USB_RAW_IOCTL_EP_ENABLE, &conf_desc.ep3); } } else { ioctl(fd, USB_RAW_IOCTL_EP0_STALL, 0); } } } for (int i = 0; i < 4; i++) { pthread_join(th[i], NULL); } close(fd); free(event); return 0; }