// https://syzkaller.appspot.com/bug?id=8b27acc89069e0e6bd0c2c309fd0853fc624d961 #define _GNU_SOURCE #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 NUM_ZOMBIES 40 struct sync_data { atomic_int sync_state; atomic_int forks_started; atomic_int total_forks; }; int main() { SETUP_UNBUFFERED_IO(); printf("[+] Starting KCSAN race reproducer...\n"); // Pin Main to CPU 0 cpu_set_t set; CPU_ZERO(&set); CPU_SET(0, &set); if (sched_setaffinity(0, sizeof(set), &set) < 0) { printf("[-] Failed to sched_setaffinity main: %s\n", strerror(errno)); exit(1); } printf("[+] sched_setaffinity main successful.\n"); // Allocate shared memory for sync_data struct sync_data *sync = mmap(NULL, sizeof(struct sync_data), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (sync == MAP_FAILED) { printf("[-] Failed to mmap: %s\n", strerror(errno)); exit(1); } printf("[+] mmap successful.\n"); TIMER_START(start); int iterations = 0; while (TIMER_NOT_EXPIRED(start, 10.0)) { pid_t z_before[NUM_ZOMBIES]; pid_t z_after[NUM_ZOMBIES]; pid_t forker; atomic_store(&sync->sync_state, 0); atomic_store(&sync->forks_started, 0); atomic_store(&sync->total_forks, 0); // Create z_before array int before_count = 0; for (int i = 0; i < NUM_ZOMBIES; i++) { pid_t pid = fork(); if (pid < 0) { if (errno == EAGAIN) break; printf("[-] Failed to fork z_before: %s\n", strerror(errno)); exit(1); } if (pid == 0) exit(0); z_before[before_count++] = pid; // Wait for it to become a zombie WITHOUT reaping it siginfo_t infop; memset(&infop, 0, sizeof(infop)); if (waitid(P_PID, pid, &infop, WEXITED | WNOWAIT) < 0) { printf("[-] Failed to waitid z_before: %s\n", strerror(errno)); exit(1); } } // Create Forker forker = fork(); if (forker < 0) { printf("[-] Failed to fork forker: %s\n", strerror(errno)); exit(1); } if (forker == 0) { // Pin Forker to CPU 1 cpu_set_t fset; CPU_ZERO(&fset); CPU_SET(1, &fset); if (sched_setaffinity(0, sizeof(fset), &fset) < 0) { printf("[-] Failed to sched_setaffinity forker: %s\n", strerror(errno)); exit(1); } // Signal ready atomic_store(&sync->sync_state, 1); // Wait for Main to say GO while (atomic_load(&sync->sync_state) != 2) {} int total = atomic_load(&sync->total_forks); // Acknowledge GO and start forking immediately atomic_store(&sync->sync_state, 3); for (int k = 0; k < total; k++) { atomic_store(&sync->forks_started, k + 1); pid_t child = fork(); if (child == 0) exit(0); } // Reap all children after the loop while (wait(NULL) > 0); exit(0); } // Create z_after array int after_count = 0; for (int i = 0; i < NUM_ZOMBIES; i++) { pid_t pid = fork(); if (pid < 0) { if (errno == EAGAIN) break; printf("[-] Failed to fork z_after: %s\n", strerror(errno)); exit(1); } if (pid == 0) exit(0); z_after[after_count++] = pid; // Wait for it to become a zombie WITHOUT reaping it siginfo_t infop; memset(&infop, 0, sizeof(infop)); if (waitid(P_PID, pid, &infop, WEXITED | WNOWAIT) < 0) { printf("[-] Failed to waitid z_after: %s\n", strerror(errno)); exit(1); } } // Wait for Forker to be ready while (atomic_load(&sync->sync_state) != 1) {} atomic_store(&sync->total_forks, before_count + after_count); // Signal Forker to GO atomic_store(&sync->sync_state, 2); // Wait for Forker to acknowledge while (atomic_load(&sync->sync_state) != 3) {} // CRITICAL REAPING ORDER with 1-to-1 synchronization: // Reap z_before in REVERSE order to continuously update Forker->tasks.prev for (int i = 0; i < before_count; i++) { while (atomic_load(&sync->forks_started) <= i) {} for (volatile int d = 0; d < 50; d++) {} // Tiny delay to let fork enter kernel if (waitpid(z_before[before_count - 1 - i], NULL, 0) < 0) { printf("[-] Failed to waitpid z_before: %s\n", strerror(errno)); exit(1); } } // Reap z_after in FORWARD order to continuously update Forker->tasks.next for (int i = 0; i < after_count; i++) { while (atomic_load(&sync->forks_started) <= before_count + i) {} for (volatile int d = 0; d < 50; d++) {} // Tiny delay to let fork enter kernel if (waitpid(z_after[i], NULL, 0) < 0) { printf("[-] Failed to waitpid z_after: %s\n", strerror(errno)); exit(1); } } // Wait for Forker to finish if (waitpid(forker, NULL, 0) < 0) { printf("[-] Failed to waitpid forker: %s\n", strerror(errno)); exit(1); } iterations++; if (iterations % 100 == 0) { printf("[+] Completed %d iterations.\n", iterations); } } printf("[+] Reproducer finished. Iterations: %d\n", iterations); return 0; }