// https://syzkaller.appspot.com/bug?id=bf6e251ead219cb559b63a12112f359a4516713b #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 // } struct sw_sync_create_fence_data { __u32 value; char name[32]; __s32 fence; }; #define SW_SYNC_IOC_MAGIC 'W' #define SW_SYNC_IOC_CREATE_FENCE _IOWR(SW_SYNC_IOC_MAGIC, 0, struct sw_sync_create_fence_data) #define SW_SYNC_IOC_INC _IOW(SW_SYNC_IOC_MAGIC, 1, __u32) #define BATCH_SIZE 4 #define ROUNDS_PER_EPOCH 1000 #define NUM_EPOCHS 100 static atomic_uint g_cur = 0; static atomic_uint g_target = 0; static atomic_int g_stop = 0; static int g_num_cpus = 1; static void *signaler_thread(void *arg) { int timeline_fd = (int)(intptr_t)arg; if (g_num_cpus > 1) { PIN_TO_CPU(1); } uint32_t round = 0; while (!atomic_load_explicit(&g_stop, memory_order_relaxed)) { while (atomic_load_explicit(&g_target, memory_order_acquire) == atomic_load_explicit(&g_cur, memory_order_relaxed)) { if (atomic_load_explicit(&g_stop, memory_order_relaxed)) return NULL; } uint32_t target = atomic_load_explicit(&g_target, memory_order_acquire); /* Sweep delay across arrival window: ~0 to 9 microseconds */ int delay = (round % 128) * 30; for (volatile int d = 0; d < delay; d++) ; if ((round & 1) == 0) { /* Option A: Paced individual increments */ while (atomic_load_explicit(&g_cur, memory_order_relaxed) < target) { __u32 inc = 1; int res = ioctl(timeline_fd, SW_SYNC_IOC_INC, &inc); if (res < 0) { printf("[-] Failed ioctl(SW_SYNC_IOC_INC): %s\n", strerror(errno)); exit(1); } atomic_fetch_add_explicit(&g_cur, 1, memory_order_release); for (volatile int d = 0; d < 100; d++) ; } } else { /* Option B: Batch increment of all fences at once */ uint32_t cur_val = atomic_load_explicit(&g_cur, memory_order_relaxed); if (target > cur_val) { __u32 diff = target - cur_val; int res = ioctl(timeline_fd, SW_SYNC_IOC_INC, &diff); if (res < 0) { printf("[-] Failed ioctl(SW_SYNC_IOC_INC): %s\n", strerror(errno)); exit(1); } atomic_store_explicit(&g_cur, target, memory_order_release); } } round++; } return NULL; } int main(void) { SETUP_UNBUFFERED_IO(); printf("[+] Starting sw_sync use-after-free bug reproducer...\n"); const char *debugfs_path = "/sys/kernel/debug"; const char *sw_sync_path = "/sys/kernel/debug/sync/sw_sync"; if (access(sw_sync_path, F_OK) != 0) { printf("[*] %s not accessible, mounting debugfs at %s\n", sw_sync_path, debugfs_path); mkdir(debugfs_path, 0755); int mres = mount("none", debugfs_path, "debugfs", 0, NULL); if (mres < 0 && errno != EBUSY) { printf("[-] Failed to mount debugfs at %s: %s\n", debugfs_path, strerror(errno)); exit(1); } printf("[+] Mounted debugfs at %s\n", debugfs_path); } g_num_cpus = sysconf(_SC_NPROCESSORS_ONLN); printf("[+] Detected %d online CPU(s)\n", g_num_cpus); if (g_num_cpus > 1) { PIN_TO_CPU(0); printf("[+] Main thread pinned to CPU 0\n"); } printf("[+] Running %d epochs of %d rounds (%d fences/round)...\n", NUM_EPOCHS, ROUNDS_PER_EPOCH, BATCH_SIZE); for (int epoch = 0; epoch < NUM_EPOCHS; epoch++) { int timeline_fd = open(sw_sync_path, O_RDWR); if (timeline_fd < 0) { printf("[-] Failed to open %s: %s\n", sw_sync_path, strerror(errno)); exit(1); } atomic_store_explicit(&g_cur, 0, memory_order_relaxed); atomic_store_explicit(&g_target, 0, memory_order_relaxed); atomic_store_explicit(&g_stop, 0, memory_order_relaxed); pthread_t th; int pres = pthread_create(&th, NULL, signaler_thread, (void *)(intptr_t)timeline_fd); if (pres != 0) { printf("[-] Failed to create signaler pthread: %s\n", strerror(pres)); exit(1); } for (int r = 0; r < ROUNDS_PER_EPOCH; r++) { uint32_t cur = atomic_load_explicit(&g_cur, memory_order_relaxed); int fds[BATCH_SIZE]; for (int i = 0; i < BATCH_SIZE; i++) { struct sw_sync_create_fence_data data; memset(&data, 0, sizeof(data)); data.value = cur + 1 + i; strncpy(data.name, "fence", sizeof(data.name) - 1); data.fence = -1; int res = ioctl(timeline_fd, SW_SYNC_IOC_CREATE_FENCE, &data); if (res < 0) { printf("[-] Failed SW_SYNC_IOC_CREATE_FENCE: %s\n", strerror(errno)); exit(1); } fds[i] = data.fence; } /* Release signaler to race with closes */ atomic_store_explicit(&g_target, cur + BATCH_SIZE, memory_order_release); /* Rapid consecutive closes */ for (int i = 0; i < BATCH_SIZE; i++) { int res = close(fds[i]); if (res < 0) { printf("[-] Failed close(fds[%d]): %s\n", i, strerror(errno)); exit(1); } } /* Wait for signaler to complete this round's increments */ while (atomic_load_explicit(&g_cur, memory_order_acquire) < cur + BATCH_SIZE) ; } atomic_store_explicit(&g_stop, 1, memory_order_release); pres = pthread_join(th, NULL); if (pres != 0) { printf("[-] Failed to join signaler pthread: %s\n", strerror(pres)); exit(1); } int cres = close(timeline_fd); if (cres < 0) { printf("[-] Failed to close timeline_fd: %s\n", strerror(errno)); exit(1); } if ((epoch + 1) % 10 == 0 || epoch == 0) { printf("[+] Completed epoch %d/%d (%d rounds)\n", epoch + 1, NUM_EPOCHS, (epoch + 1) * ROUNDS_PER_EPOCH); } } printf("[+] All epochs finished. Reproducer completed successfully.\n"); return 0; }