// https://syzkaller.appspot.com/bug?id=4383f618da77d7caee6212ce8921a61f92ba95d7 #define _GNU_SOURCE #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 GADGET_FUNC_PATH "/sys/kernel/config/usb_gadget/g1/functions/tcm.usb0" #define TARGET_TPGT_PATH "/sys/kernel/config/target/usb_gadget/naa.5001405000000000/tpgt_1" static volatile int running = 1; static volatile unsigned long gadget_iters = 0; static volatile unsigned long target_iters = 0; static void *gadget_worker(void *arg) { int cpu = (int)(intptr_t)arg; PIN_TO_CPU(cpu); while (running) { mkdir(GADGET_FUNC_PATH, 0777); rmdir(GADGET_FUNC_PATH); __atomic_fetch_add(&gadget_iters, 1, __ATOMIC_RELAXED); } return NULL; } static void *target_worker(void *arg) { int cpu = (int)(intptr_t)arg; PIN_TO_CPU(cpu); while (running) { int res = mkdir(TARGET_TPGT_PATH, 0777); if (res == 0 || errno == EEXIST) { rmdir(TARGET_TPGT_PATH); } __atomic_fetch_add(&target_iters, 1, __ATOMIC_RELAXED); } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Starting reproducer for configfs_depend_item_unlocked GPF...\n"); /* Initial cleanup of any stale artifacts from prior runs */ (void)rmdir(TARGET_TPGT_PATH); (void)rmdir("/sys/kernel/config/target/usb_gadget/naa.5001405000000000"); (void)rmdir("/sys/kernel/config/target/usb_gadget"); (void)rmdir(GADGET_FUNC_PATH); (void)rmdir("/sys/kernel/config/usb_gadget/g1"); /* Step 1: Ensure /sys/kernel/config mount point exists */ int res = mkdir("/sys/kernel/config", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config: %s\n", strerror(errno)); exit(1); } printf("[+] Directory /sys/kernel/config ready.\n"); /* Step 2: Ensure configfs is mounted */ res = mount("none", "/sys/kernel/config", "configfs", 0, NULL); if (res < 0) { if (errno == EBUSY) { printf("[+] configfs is already mounted on /sys/kernel/config.\n"); } else { printf("[-] Failed to mount configfs: %s\n", strerror(errno)); exit(1); } } else { printf("[+] Successfully mounted configfs on /sys/kernel/config.\n"); } /* Step 3: Create USB gadget 'g1' */ res = mkdir("/sys/kernel/config/usb_gadget/g1", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/usb_gadget/g1: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/usb_gadget/g1 successful.\n"); /* Step 4: Create tcm.usb0 function to load module and register fabric */ res = mkdir(GADGET_FUNC_PATH, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", GADGET_FUNC_PATH, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", GADGET_FUNC_PATH); /* Step 5: Create target fabric group usb_gadget */ res = mkdir("/sys/kernel/config/target/usb_gadget", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/target/usb_gadget: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/target/usb_gadget successful.\n"); /* Step 6: Create target WWN under target/usb_gadget */ res = mkdir("/sys/kernel/config/target/usb_gadget/naa.5001405000000000", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/target/usb_gadget/naa.5001405000000000: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/config/target/usb_gadget/naa.5001405000000000 successful.\n"); /* Step 7: Remove initial tcm.usb0 so racing starts from clean unlinked state */ res = rmdir(GADGET_FUNC_PATH); if (res < 0 && errno != ENOENT) { printf("[-] Failed to rmdir %s: %s\n", GADGET_FUNC_PATH, strerror(errno)); exit(1); } printf("[+] rmdir %s successful, setup complete.\n", GADGET_FUNC_PATH); /* Step 8: Spawn workers pinned across available CPU cores */ int ncpus = sysconf(_SC_NPROCESSORS_ONLN); if (ncpus < 1) ncpus = 1; printf("[+] Detected %d online CPUs.\n", ncpus); int num_target_threads = (ncpus > 1) ? (ncpus - 1) : 1; if (num_target_threads > 16) num_target_threads = 16; pthread_t gadget_tid; pthread_t target_tids[16]; res = pthread_create(&gadget_tid, NULL, gadget_worker, (void *)(intptr_t)0); if (res != 0) { printf("[-] Failed to create gadget worker thread: %s\n", strerror(res)); exit(1); } printf("[+] Created gadget worker thread pinned to CPU 0.\n"); for (int i = 0; i < num_target_threads; i++) { int cpu = (ncpus > 1) ? ((i + 1) % ncpus) : 0; res = pthread_create(&target_tids[i], NULL, target_worker, (void *)(intptr_t)cpu); if (res != 0) { printf("[-] Failed to create target worker thread %d: %s\n", i, strerror(res)); exit(1); } printf("[+] Created target worker thread %d pinned to CPU %d.\n", i, cpu); } /* Step 9: Run racing loop */ printf("[+] Racing started. Triggering race between gadget function and target TPG...\n"); TIMER_START(start); int last_sec = 0; while (TIMER_NOT_EXPIRED(start, 35.0)) { usleep(100000); int elapsed = (int)timer_elapsed_sec(&start); if (elapsed >= last_sec + 5) { last_sec = elapsed; printf("[+] Race in progress: %d s elapsed (gadget iters: %lu, target iters: %lu)...\n", elapsed, gadget_iters, target_iters); } } printf("[+] Race duration expired. Stopping worker threads...\n"); running = 0; pthread_join(gadget_tid, NULL); for (int i = 0; i < num_target_threads; i++) { pthread_join(target_tids[i], NULL); } printf("[+] All worker threads stopped.\n"); /* Step 10: Final cleanup */ (void)rmdir(TARGET_TPGT_PATH); (void)rmdir("/sys/kernel/config/target/usb_gadget/naa.5001405000000000"); (void)rmdir("/sys/kernel/config/target/usb_gadget"); (void)rmdir(GADGET_FUNC_PATH); (void)rmdir("/sys/kernel/config/usb_gadget/g1"); printf("[+] Teardown complete. Program completed without crash.\n"); return 0; }