// https://syzkaller.appspot.com/bug?id=f198546cf3a9266ea5fe7502f956492abbe895f8 #define _GNU_SOURCE // 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 // } #include #include #include #include #include #include #include #include #include #include #include #define N_WORKERS 4 static volatile int stage = 0; static volatile int done[N_WORKERS] = {0}; static volatile int stop_workers = 0; static int g_fds[N_WORKERS] = {-1, -1, -1, -1}; static int g_ncpus = 1; static void* worker_fn(void* arg) { long id = (long)arg; PIN_TO_CPU((int)(id % g_ncpus)); while (1) { WAIT_ON(&stage, 1); if (stop_workers) break; int fd = g_fds[id]; if (fd >= 0) { for (int i = 0; i < 1500; i++) { pwrite(fd, "clear", 5, 0); if ((i % 100) == 0) sched_yield(); } } SIGNAL(&done[id], 1); WAIT_ON(&stage, 0); } return NULL; } static int setup_md(int num) { char name[32]; snprintf(name, sizeof(name), "md%d\n", num); int fd = open("/sys/module/md_mod/parameters/new_array", O_WRONLY); if (fd < 0) { printf("[-] setup_md: open new_array failed: %s\n", strerror(errno)); return -1; } ssize_t ret = write(fd, name, strlen(name)); int write_errno = errno; int res = close(fd); if (res < 0) { printf("[-] setup_md: close new_array fd failed: %s\n", strerror(errno)); return -1; } return (ret > 0 || write_errno == EEXIST) ? 0 : -1; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for sysfs_break_active_protection refcount bug...\n"); if (geteuid() != 0) { printf("[-] Warning: Process is not running as root (euid=%d)\n", geteuid()); } else { printf("[+] Running as root (euid=0)\n"); } for (int i = 0; i < 8; i++) { char devpath[32]; snprintf(devpath, sizeof(devpath), "/dev/md%d", i); int res = mknod(devpath, S_IFBLK | 0660, makedev(9, i)); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mknod %s: %s\n", devpath, strerror(errno)); exit(1); } } printf("[+] Verified /dev/md0../dev/md7 device nodes.\n"); const char *new_array_path = "/sys/module/md_mod/parameters/new_array"; int check_fd = open(new_array_path, O_WRONLY); if (check_fd < 0) { printf("[-] open %s failed: %s (attempting module autoload via /dev/md0)\n", new_array_path, strerror(errno)); int dev_fd = open("/dev/md0", O_RDONLY | O_NONBLOCK); if (dev_fd >= 0) { close(dev_fd); } check_fd = open(new_array_path, O_WRONLY); if (check_fd < 0) { printf("[-] Failed to open %s after autoload attempt: %s\n", new_array_path, strerror(errno)); exit(1); } } int res = close(check_fd); if (res < 0) { printf("[-] Failed to close check_fd: %s\n", strerror(errno)); exit(1); } printf("[+] Verified %s is accessible.\n", new_array_path); g_ncpus = (int)sysconf(_SC_NPROCESSORS_ONLN); if (g_ncpus < 1) g_ncpus = 1; printf("[+] Detected %d online CPU(s).\n", g_ncpus); pthread_t threads[N_WORKERS]; for (long i = 0; i < N_WORKERS; i++) { int err = pthread_create(&threads[i], NULL, worker_fn, (void*)i); if (err != 0) { printf("[-] Failed to pthread_create worker %ld: %s\n", i, strerror(err)); exit(1); } } printf("[+] Created %d worker threads successfully.\n", N_WORKERS); TIMER_START(timer); printf("[+] Starting race loop (up to 25.0 seconds)...\n"); int array_idx = 0; int loop_count = 0; while (TIMER_NOT_EXPIRED(timer, 25.0)) { int dev_num = array_idx % 8; array_idx++; setup_md(dev_num); char attr_path[64]; snprintf(attr_path, sizeof(attr_path), "/sys/block/md%d/md/array_state", dev_num); int ok = 1; for (int i = 0; i < N_WORKERS; i++) { g_fds[i] = open(attr_path, O_WRONLY); if (g_fds[i] < 0) { ok = 0; break; } } if (!ok) { for (int i = 0; i < N_WORKERS; i++) { if (g_fds[i] >= 0) { close(g_fds[i]); g_fds[i] = -1; } } usleep(500); continue; } for (int i = 0; i < N_WORKERS; i++) done[i] = 0; MB(); SIGNAL(&stage, 1); for (int i = 0; i < N_WORKERS; i++) WAIT_ON(&done[i], 1); SIGNAL(&stage, 0); for (int i = 0; i < N_WORKERS; i++) { if (g_fds[i] >= 0) { close(g_fds[i]); g_fds[i] = -1; } } loop_count++; if ((loop_count % 500) == 0) { printf("[+] Completed %d race iterations...\n", loop_count); } } printf("[+] Race loop finished after %d iterations.\n", loop_count); stop_workers = 1; MB(); SIGNAL(&stage, 1); for (int i = 0; i < N_WORKERS; i++) { int err = pthread_join(threads[i], NULL); if (err != 0) { printf("[-] Failed to pthread_join worker %d: %s\n", i, strerror(err)); exit(1); } } printf("[+] All worker threads joined successfully.\n"); sleep(1); printf("[+] Reproducer program finished.\n"); return 0; }