// https://syzkaller.appspot.com/bug?id=79ed62a50a6b7b4d338d996b1cd47a9b0893e236 #define _GNU_SOURCE #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 // } #ifndef MNT_DETACH #define MNT_DETACH 2 #endif #define MAX_WORKERS 8 static const char *names[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49" }; #define BURST_COUNT (sizeof(names) / sizeof(names[0])) static int sub_dfds[MAX_WORKERS]; static char subpaths[MAX_WORKERS][512]; static pthread_t threads[MAX_WORKERS]; static atomic_int current_round; static atomic_int workers_done; static atomic_int stop_flag; static void pin_cpu(int cpu) { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(cpu, &mask); int res = sched_setaffinity(0, sizeof(mask), &mask); if (res < 0) { printf("[-] Failed sched_setaffinity to cpu %d: %s\n", cpu, strerror(errno)); exit(1); } } static void setup_ns(void) { if (geteuid() != 0) { printf("[*] Running as non-root (uid %u), setting up user and mount namespaces...\n", geteuid()); int res = unshare(CLONE_NEWUSER | CLONE_NEWNS); if (res < 0) { printf("[-] Failed to unshare(CLONE_NEWUSER | CLONE_NEWNS): %s\n", strerror(errno)); exit(1); } printf("[+] unshare(CLONE_NEWUSER | CLONE_NEWNS) successful.\n"); int fd = open("/proc/self/setgroups", O_WRONLY); if (fd >= 0) { res = write(fd, "deny", 4); if (res < 0) { printf("[-] Failed to write to /proc/self/setgroups: %s\n", strerror(errno)); close(fd); exit(1); } close(fd); printf("[+] write deny to /proc/self/setgroups successful.\n"); } fd = open("/proc/self/uid_map", O_WRONLY); if (fd < 0) { printf("[-] Failed to open /proc/self/uid_map: %s\n", strerror(errno)); exit(1); } char map[64]; int len = snprintf(map, sizeof(map), "0 %u 1\n", getuid()); res = write(fd, map, len); if (res < 0) { printf("[-] Failed to write to /proc/self/uid_map: %s\n", strerror(errno)); close(fd); exit(1); } close(fd); printf("[+] write /proc/self/uid_map successful.\n"); fd = open("/proc/self/gid_map", O_WRONLY); if (fd < 0) { printf("[-] Failed to open /proc/self/gid_map: %s\n", strerror(errno)); exit(1); } len = snprintf(map, sizeof(map), "0 %u 1\n", getgid()); res = write(fd, map, len); if (res < 0) { printf("[-] Failed to write to /proc/self/gid_map: %s\n", strerror(errno)); close(fd); exit(1); } close(fd); printf("[+] write /proc/self/gid_map successful.\n"); } else { printf("[*] Running as root (uid 0), setting up mount namespace...\n"); int res = unshare(CLONE_NEWNS); if (res < 0) { printf("[-] Failed to unshare(CLONE_NEWNS): %s\n", strerror(errno)); exit(1); } printf("[+] unshare(CLONE_NEWNS) successful.\n"); res = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL); if (res < 0) { printf("[-] Failed to mount MS_REC | MS_PRIVATE on /: %s\n", strerror(errno)); exit(1); } printf("[+] mount MS_REC | MS_PRIVATE on / successful.\n"); } } static void *mkdir_worker(void *arg) { int id = (int)(intptr_t)arg; int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (num_cpus < 1) num_cpus = 1; pin_cpu((id + 1) % num_cpus); int local_round = 1; while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) { while (atomic_load_explicit(¤t_round, memory_order_acquire) != local_round) { if (atomic_load_explicit(&stop_flag, memory_order_relaxed)) return NULL; __builtin_ia32_pause(); } int dfd = sub_dfds[id]; for (size_t i = 0; i < BURST_COUNT; i++) { int res = mkdirat(dfd, names[i], 0777); if (res < 0) { printf("[-] Failed mkdirat(%s) in worker %d, round %d: %s\n", names[i], id, local_round, strerror(errno)); exit(1); } } atomic_fetch_add_explicit(&workers_done, 1, memory_order_release); local_round++; } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Starting shmem_initxattrs race reproducer...\n"); setup_ns(); int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (num_cpus < 1) num_cpus = 1; pin_cpu(0); int num_workers = num_cpus - 1; if (num_workers < 1) num_workers = 1; if (num_workers > 3) num_workers = 3; printf("[+] Detected %d CPUs, configuring %d mkdir workers.\n", num_cpus, num_workers); char mount_dir_tmpl[] = "/tmp/shm_race_XXXXXX"; char *mount_dir = mkdtemp(mount_dir_tmpl); if (!mount_dir) { printf("[-] Failed mkdtemp: %s\n", strerror(errno)); exit(1); } printf("[+] mkdtemp successful: %s\n", mount_dir); for (int i = 0; i < num_workers; i++) { int res = snprintf(subpaths[i], sizeof(subpaths[i]), "%s/sub%d", mount_dir, i); if (res < 0 || res >= (int)sizeof(subpaths[i])) { printf("[-] Failed snprintf subpath %d\n", i); exit(1); } } atomic_store_explicit(¤t_round, 0, memory_order_relaxed); atomic_store_explicit(&workers_done, 0, memory_order_relaxed); atomic_store_explicit(&stop_flag, 0, memory_order_relaxed); for (int i = 0; i < num_workers; i++) { int res = pthread_create(&threads[i], NULL, mkdir_worker, (void *)(intptr_t)i); if (res != 0) { printf("[-] Failed pthread_create for worker %d: %s\n", i, strerror(res)); exit(1); } printf("[+] pthread_create for worker %d successful.\n", i); } TIMER_START(timer); printf("[+] Starting race loop for up to 20 seconds...\n"); int round = 1; for (; round <= 100000; round++) { if (!TIMER_NOT_EXPIRED(timer, 20.0)) break; int res = mount("none", mount_dir, "tmpfs", 0, "nr_inodes=100000"); if (res < 0) { printf("[-] Failed mount tmpfs in round %d: %s\n", round, strerror(errno)); exit(1); } for (int i = 0; i < num_workers; i++) { res = mkdir(subpaths[i], 0777); if (res < 0) { printf("[-] Failed mkdir %s in round %d: %s\n", subpaths[i], round, strerror(errno)); exit(1); } sub_dfds[i] = open(subpaths[i], O_RDONLY | O_DIRECTORY); if (sub_dfds[i] < 0) { printf("[-] Failed open %s in round %d: %s\n", subpaths[i], round, strerror(errno)); exit(1); } } atomic_store_explicit(&workers_done, 0, memory_order_relaxed); atomic_store_explicit(¤t_round, round, memory_order_release); int delay = (round % 64) * 30; for (volatile int j = 0; j < delay; j++) __builtin_ia32_pause(); res = mount("none", mount_dir, "tmpfs", MS_REMOUNT, "nr_inodes=0"); if (res < 0) { printf("[-] Failed remount tmpfs in round %d: %s\n", round, strerror(errno)); exit(1); } while (atomic_load_explicit(&workers_done, memory_order_acquire) < num_workers) { __builtin_ia32_pause(); } for (int i = 0; i < num_workers; i++) { res = close(sub_dfds[i]); if (res < 0) { printf("[-] Failed close sub_dfd[%d] in round %d: %s\n", i, round, strerror(errno)); exit(1); } sub_dfds[i] = -1; } res = umount2(mount_dir, MNT_DETACH); if (res < 0) { printf("[-] Failed umount2 in round %d: %s\n", round, strerror(errno)); exit(1); } if (round % 5000 == 0) { printf("[+] Completed %d race rounds...\n", round); } } printf("[+] Race loop finished (%d total rounds executed).\n", round - 1); atomic_store_explicit(&stop_flag, 1, memory_order_release); for (int i = 0; i < num_workers; i++) { int res = pthread_join(threads[i], NULL); if (res != 0) { printf("[-] Failed pthread_join for worker %d: %s\n", i, strerror(res)); exit(1); } printf("[+] pthread_join for worker %d successful.\n", i); } usleep(10000); int res = rmdir(mount_dir); if (res < 0) { printf("[-] Failed rmdir %s: %s\n", mount_dir, strerror(errno)); } else { printf("[+] rmdir %s successful.\n", mount_dir); } printf("[+] Reproducer finished.\n"); return 0; }