// https://syzkaller.appspot.com/bug?id=0d979fefdf91a5796bb78aac5d3dcf9eccf583d6 #define _GNU_SOURCE #include #include #include #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 // } /* USB Device Descriptor (Hauppauge 126xxx ATSC+, VID: 0x2040, PID: 0xc600) */ static const uint8_t dev_desc[18] = { 18, /* bLength = 18 */ USB_DT_DEVICE, /* bDescriptorType = 1 */ 0x00, 0x02, /* bcdUSB = 2.00 */ 0x00, /* bDeviceClass */ 0x00, /* bDeviceSubClass */ 0x00, /* bDeviceProtocol */ 64, /* bMaxPacketSize0 = 64 */ 0x40, 0x20, /* idVendor = 0x2040 (Hauppauge) */ 0x00, 0xc6, /* idProduct = 0xc600 (ATSC+) */ 0x00, 0x01, /* bcdDevice = 1.00 */ 0x00, /* iManufacturer */ 0x00, /* iProduct */ 0x00, /* iSerialNumber */ 0x01 /* bNumConfigurations = 1 */ }; /* USB Configuration Descriptor (39 bytes total) matching dummy_udc High Speed endpoints */ static const uint8_t config_desc[39] = { /* Configuration Descriptor (9 bytes) */ 9, /* bLength */ USB_DT_CONFIG, /* bDescriptorType = 2 */ 39, 0, /* wTotalLength = 39 */ 1, /* bNumInterfaces = 1 */ 1, /* bConfigurationValue = 1 */ 0, /* iConfiguration */ 0x80, /* bmAttributes = bus-powered */ 250, /* bMaxPower = 500mA */ /* Interface Descriptor (9 bytes) */ 9, /* bLength */ USB_DT_INTERFACE, /* bDescriptorType = 4 */ 0, /* bInterfaceNumber = 0 */ 0, /* bAlternateSetting = 0 */ 3, /* bNumEndpoints = 3 */ 0xff, /* bInterfaceClass = Vendor Specific */ 0xff, /* bInterfaceSubClass = Vendor Specific */ 0xff, /* bInterfaceProtocol = Vendor Specific */ 0, /* iInterface */ /* Endpoint 1: Bulk OUT 0x02 (7 bytes) */ 7, /* bLength */ USB_DT_ENDPOINT, /* bDescriptorType = 5 */ 0x02, /* bEndpointAddress */ 0x02, /* bmAttributes = Bulk */ 0x00, 0x02, /* wMaxPacketSize = 512 */ 0, /* bInterval */ /* Endpoint 2: Bulk IN 0x81 (7 bytes) */ 7, /* bLength */ USB_DT_ENDPOINT, /* bDescriptorType = 5 */ 0x81, /* bEndpointAddress */ 0x02, /* bmAttributes = Bulk */ 0x00, 0x02, /* wMaxPacketSize = 512 */ 0, /* bInterval */ /* Endpoint 3: Iso IN 0x83 (7 bytes) */ 7, /* bLength */ USB_DT_ENDPOINT, /* bDescriptorType = 5 */ 0x83, /* bEndpointAddress */ 0x01, /* bmAttributes = Isochronous */ 0x00, 0x04, /* wMaxPacketSize = 1024 */ 1 /* bInterval = 1 */ }; /* USB String 0 Descriptor (Language ID: English US) */ static const uint8_t lang_desc[4] = { 4, USB_DT_STRING, 0x09, 0x04 }; /* Dummy String Descriptor */ static const uint8_t str_desc[10] = { 10, USB_DT_STRING, 'U', 0, 'S', 0, 'B', 0, '1', 0 }; static void ep0_write(int raw_fd, const void *data, uint32_t len) { struct { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[1024]; } io; memset(&io, 0, sizeof(io)); if (len > sizeof(io.data)) len = sizeof(io.data); io.length = len; if (data && len > 0) memcpy(io.data, data, len); int res = ioctl(raw_fd, USB_RAW_IOCTL_EP0_WRITE, &io); if (res < 0) { printf("[-] EP0_WRITE failed: %s\n", strerror(errno)); } } static void ep0_read(int raw_fd, uint32_t len) { struct { uint16_t ep; uint16_t flags; uint32_t length; uint8_t data[1024]; } io; memset(&io, 0, sizeof(io)); if (len > sizeof(io.data)) len = sizeof(io.data); io.length = len; int res = ioctl(raw_fd, USB_RAW_IOCTL_EP0_READ, &io); if (res < 0) { printf("[-] EP0_READ failed: %s\n", strerror(errno)); } } static volatile int g_stop = 0; static volatile int g_cycle_stop = 0; static int g_num_cpus = 1; static void *gadget_event_thread(void *arg) { int raw_fd = (int)(intptr_t)arg; PIN_TO_CPU(0); printf("[+] Gadget event thread running on CPU 0.\n"); while (!g_cycle_stop && !g_stop) { struct { uint32_t type; uint32_t length; uint8_t data[256]; } event; memset(&event, 0, sizeof(event)); event.length = sizeof(event.data); int res = ioctl(raw_fd, USB_RAW_IOCTL_EVENT_FETCH, &event); if (res < 0) { if (errno == EINTR) continue; if (g_cycle_stop || g_stop) break; printf("[-] USB_RAW_IOCTL_EVENT_FETCH failed: %s\n", strerror(errno)); break; } if (event.type == USB_RAW_EVENT_CONTROL) { struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)event.data; uint8_t req_type = ctrl->bRequestType; uint8_t req = ctrl->bRequest; uint16_t val = le16toh(ctrl->wValue); uint16_t len = le16toh(ctrl->wLength); if ((req_type & USB_TYPE_MASK) == USB_TYPE_STANDARD) { if (req == USB_REQ_GET_DESCRIPTOR) { uint8_t desc_type = val >> 8; uint8_t desc_idx = val & 0xff; if (desc_type == USB_DT_DEVICE) { uint32_t n = sizeof(dev_desc); if (n > len) n = len; ep0_write(raw_fd, dev_desc, n); } else if (desc_type == USB_DT_CONFIG) { uint32_t n = sizeof(config_desc); if (n > len) n = len; ep0_write(raw_fd, config_desc, n); } else if (desc_type == USB_DT_STRING) { if (desc_idx == 0) { uint32_t n = sizeof(lang_desc); if (n > len) n = len; ep0_write(raw_fd, lang_desc, n); } else { uint32_t n = sizeof(str_desc); if (n > len) n = len; ep0_write(raw_fd, str_desc, n); } } else { ioctl(raw_fd, USB_RAW_IOCTL_EP0_STALL, 0); } } else if (req == USB_REQ_SET_CONFIGURATION) { int ret = ioctl(raw_fd, USB_RAW_IOCTL_CONFIGURE, 0); if (ret < 0) { printf("[-] USB_RAW_IOCTL_CONFIGURE failed: %s\n", strerror(errno)); } ep0_read(raw_fd, 0); printf("[+] Handled USB_REQ_SET_CONFIGURATION; enabling endpoints...\n"); /* Enable Bulk OUT and Bulk IN endpoints to allow driver probing */ struct usb_endpoint_descriptor ep_bulk_out = { .bLength = 7, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x02, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }; struct usb_endpoint_descriptor ep_bulk_in = { .bLength = 7, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = htole16(512), .bInterval = 0, }; ioctl(raw_fd, USB_RAW_IOCTL_EP_ENABLE, &ep_bulk_out); ioctl(raw_fd, USB_RAW_IOCTL_EP_ENABLE, &ep_bulk_in); } else if (req == USB_REQ_SET_INTERFACE) { ep0_read(raw_fd, 0); printf("[+] Handled USB_REQ_SET_INTERFACE.\n"); } else if ((req_type & USB_DIR_IN) && len > 0) { uint8_t dummy[64] = {0}; uint32_t n = (len > sizeof(dummy)) ? sizeof(dummy) : len; ep0_write(raw_fd, dummy, n); } else { ep0_read(raw_fd, len); } } else { /* Vendor / Class requests */ if ((req_type & USB_DIR_IN) && len > 0) { uint8_t dummy[512] = {0}; uint32_t n = (len > sizeof(dummy)) ? sizeof(dummy) : len; ep0_write(raw_fd, dummy, n); } else { ep0_read(raw_fd, len); } } } } return NULL; } static void trigger_demux(int fd, const char *path) { struct dmx_pes_filter_params params; memset(¶ms, 0, sizeof(params)); params.pid = 0x100; params.input = DMX_IN_FRONTEND; params.output = DMX_OUT_TAP; params.pes_type = DMX_PES_OTHER; params.flags = DMX_IMMEDIATE_START; printf("[+] Successfully opened %s (fd=%d), issuing DMX_SET_PES_FILTER...\n", path, fd); int res = ioctl(fd, DMX_SET_PES_FILTER, ¶ms); if (res < 0) { printf("[-] DMX_SET_PES_FILTER on %s failed: %s\n", path, strerror(errno)); } else { printf("[+] DMX_SET_PES_FILTER on %s succeeded!\n", path); } ioctl(fd, DMX_START, 0); } static const char *cand_nodes[] = { "/dev/dvb/adapter0/demux0", "/dev/dvb/adapter1/demux0", "/dev/dvb/adapter2/demux0", "/dev/dvb/adapter0/demux4", "/dev/dvb/adapter1/demux4", "/tmp/dvb_demux_0", "/tmp/dvb_demux_1", "/tmp/dvb_demux_2", "/tmp/dvb_demux_3", "/tmp/dvb_demux_4", "/tmp/dvb_demux_5", "/tmp/dvb_demux_68", "/tmp/dvb_demux_132", "/tmp/dvb_demux_196" }; static void *demux_worker_fn(void *arg) { int thread_id = (int)(intptr_t)arg; PIN_TO_CPU((thread_id + 1) % g_num_cpus); while (!g_stop) { /* Attempt opening statically pre-created nodes */ for (size_t i = 0; i < sizeof(cand_nodes) / sizeof(cand_nodes[0]) && !g_stop; i++) { int fd = open(cand_nodes[i], O_RDWR | O_NONBLOCK); if (fd >= 0) { trigger_demux(fd, cand_nodes[i]); close(fd); } } /* Dynamically scan /dev/dvb/ directory */ DIR *dvb_dir = opendir("/dev/dvb"); if (dvb_dir) { struct dirent *d1; while ((d1 = readdir(dvb_dir)) != NULL && !g_stop) { if (d1->d_name[0] == '.') continue; char subpath[256]; snprintf(subpath, sizeof(subpath), "/dev/dvb/%s", d1->d_name); DIR *sub_dir = opendir(subpath); if (sub_dir) { struct dirent *d2; while ((d2 = readdir(sub_dir)) != NULL && !g_stop) { if (strstr(d2->d_name, "demux") != NULL) { char node_path[512]; snprintf(node_path, sizeof(node_path), "%s/%s", subpath, d2->d_name); int fd = open(node_path, O_RDWR | O_NONBLOCK); if (fd >= 0) { trigger_demux(fd, node_path); close(fd); } } } closedir(sub_dir); } } closedir(dvb_dir); } usleep(1000); } return NULL; } static int init_udc(int raw_fd, char *chosen_udc) { DIR *dir = opendir("/sys/class/udc"); if (!dir) { printf("[-] Failed to opendir /sys/class/udc: %s\n", strerror(errno)); return -1; } struct dirent *ent; int found = 0; while ((ent = readdir(dir)) != NULL) { if (ent->d_name[0] == '.') continue; printf("[*] Probing UDC: %s\n", ent->d_name); struct usb_raw_init init_arg; memset(&init_arg, 0, sizeof(init_arg)); strncpy((char *)init_arg.driver_name, "dummy_udc", sizeof(init_arg.driver_name) - 1); strncpy((char *)init_arg.device_name, ent->d_name, sizeof(init_arg.device_name) - 1); init_arg.speed = USB_SPEED_HIGH; int res = ioctl(raw_fd, USB_RAW_IOCTL_INIT, &init_arg); if (res == 0) { strncpy(chosen_udc, ent->d_name, 127); printf("[+] Successfully initialized raw-gadget on UDC: %s\n", chosen_udc); found = 1; break; } } closedir(dir); return found ? 0 : -1; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting standalone reproducer for mxl111sf_get_stream_config_atsc_mh...\n"); g_num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (g_num_cpus < 1) g_num_cpus = 1; printf("[+] Detected %d online CPUs.\n", g_num_cpus); PIN_TO_CPU(0); /* Step 1: Pre-create DVB device nodes in /dev/dvb and /tmp */ mkdir("/dev/dvb", 0755); mkdir("/dev/dvb/adapter0", 0755); mkdir("/dev/dvb/adapter1", 0755); mkdir("/dev/dvb/adapter2", 0755); mknod("/dev/dvb/adapter0/demux0", S_IFCHR | 0666, makedev(212, 4)); mknod("/dev/dvb/adapter1/demux0", S_IFCHR | 0666, makedev(212, 68)); mknod("/dev/dvb/adapter0/demux4", S_IFCHR | 0666, makedev(212, 4)); mknod("/dev/dvb/adapter1/demux4", S_IFCHR | 0666, makedev(212, 68)); /* Dynamic and static minor candidates: 0..5, 68, 132, 196 */ const int minors[] = { 0, 1, 2, 3, 4, 5, 68, 132, 196 }; for (size_t i = 0; i < sizeof(minors) / sizeof(minors[0]); i++) { char path[64]; snprintf(path, sizeof(path), "/tmp/dvb_demux_%d", minors[i]); unlink(path); int res = mknod(path, S_IFCHR | 0666, makedev(212, minors[i])); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mknod %s: %s\n", path, strerror(errno)); } } printf("[+] Device nodes pre-created successfully.\n"); /* Step 2: Spawn concurrent demux worker threads */ pthread_t workers[3]; for (int i = 0; i < 3; i++) { int res = pthread_create(&workers[i], NULL, demux_worker_fn, (void *)(intptr_t)i); if (res != 0) { printf("[-] Failed to pthread_create worker %d: %s\n", i, strerror(res)); exit(1); } } printf("[+] Spawned 3 demux worker threads successfully.\n"); /* Step 3: Run gadget emulation cycles */ TIMER_START(total_timer); int cycle = 0; while (TIMER_NOT_EXPIRED(total_timer, 25.0) && cycle < 6) { cycle++; printf("[*] Starting USB emulation cycle %d...\n", cycle); int raw_fd = open("/dev/raw-gadget", O_RDWR); if (raw_fd < 0) { printf("[-] Failed to open /dev/raw-gadget: %s\n", strerror(errno)); exit(1); } printf("[+] Opened /dev/raw-gadget successfully.\n"); char chosen_udc[128] = {0}; int res = init_udc(raw_fd, chosen_udc); if (res < 0) { printf("[-] Could not find an available UDC in /sys/class/udc\n"); close(raw_fd); exit(1); } res = ioctl(raw_fd, USB_RAW_IOCTL_RUN, 0); if (res < 0) { printf("[-] Failed to ioctl USB_RAW_IOCTL_RUN: %s\n", strerror(errno)); close(raw_fd); exit(1); } printf("[+] USB_RAW_IOCTL_RUN successful on %s\n", chosen_udc); g_cycle_stop = 0; pthread_t event_th; res = pthread_create(&event_th, NULL, gadget_event_thread, (void *)(intptr_t)raw_fd); if (res != 0) { printf("[-] Failed to pthread_create gadget_event_thread: %s\n", strerror(res)); close(raw_fd); exit(1); } /* Allow device enumeration and driver binding; race window occurs during probe */ printf("[*] Gadget active, waiting for driver binding and demux trigger...\n"); TIMER_START(cycle_timer); while (TIMER_NOT_EXPIRED(cycle_timer, 3.5)) { usleep(50000); } printf("[*] Ending cycle %d, closing raw-gadget...\n", cycle); g_cycle_stop = 1; res = close(raw_fd); if (res < 0) { printf("[-] Failed to close /dev/raw-gadget: %s\n", strerror(errno)); } else { printf("[+] Closed /dev/raw-gadget successfully.\n"); } pthread_join(event_th, NULL); /* Delay to allow asynchronous device cleanup before next cycle */ usleep(400000); } g_stop = 1; for (int i = 0; i < 3; i++) { pthread_join(workers[i], NULL); } printf("[+] Reproducer finished execution.\n"); return 0; }