// https://syzkaller.appspot.com/bug?id=0836074c4dad9211e0e23b28316b8caeabab0921 #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 #include #include #include #include #include #include #include #include #include #include #ifndef NBD_MAJOR #define NBD_MAJOR 43 #endif #ifndef NBD_SET_SOCK #define NBD_SET_SOCK _IO(0xab, 0) #endif #ifndef NBD_DO_IT #define NBD_DO_IT _IO(0xab, 3) #endif #ifndef NBD_CLEAR_SOCK #define NBD_CLEAR_SOCK _IO(0xab, 4) #endif #ifndef NBD_DISCONNECT #define NBD_DISCONNECT _IO(0xab, 8) #endif #ifndef SOL_TCP #define SOL_TCP 6 #endif #ifndef TCP_REPAIR #define TCP_REPAIR 19 #endif #ifndef TCP_REPAIR_ON #define TCP_REPAIR_ON 1 #endif #define TOTAL_DEVS 11 #define NUM_RECLASSIFIERS 5 /* Devices nbd0 through nbd4 */ #define NUM_RUNNERS 6 /* Devices nbd5 through nbd10 */ static int shared_sock = -1; static volatile bool running = true; static int num_cpus = 1; static int open_nbd_device(int idx) { char path[32]; snprintf(path, sizeof(path), "/dev/nbd%d", idx); int fd = open(path, O_RDWR); if (fd < 0 && errno == ENOENT) { printf("[*] %s does not exist, attempting mknod...\n", path); int res = mknod(path, S_IFBLK | 0660, makedev(NBD_MAJOR, idx)); if (res < 0) { printf("[-] Failed to mknod %s: %s\n", path, strerror(errno)); return -1; } printf("[+] mknod %s successful.\n", path); fd = open(path, O_RDWR); } if (fd < 0) { printf("[-] Failed to open %s: %s\n", path, strerror(errno)); return -1; } printf("[+] open %s successful (fd=%d).\n", path, fd); return fd; } static int setup_tcp_socket(void) { int srv = socket(AF_INET, SOCK_STREAM, 0); if (srv < 0) { printf("[-] Failed to socket(AF_INET, SOCK_STREAM, 0) for server: %s\n", strerror(errno)); exit(1); } printf("[+] Server socket created.\n"); int opt = 1; int res = setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (res < 0) { printf("[-] Failed to setsockopt(SO_REUSEADDR): %s\n", strerror(errno)); exit(1); } printf("[+] setsockopt(SO_REUSEADDR) successful.\n"); struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sin.sin_port = 0; res = bind(srv, (struct sockaddr*)&sin, sizeof(sin)); if (res < 0) { printf("[-] Failed to bind server socket: %s\n", strerror(errno)); exit(1); } printf("[+] Server socket bound to 127.0.0.1.\n"); socklen_t slen = sizeof(sin); res = getsockname(srv, (struct sockaddr*)&sin, &slen); if (res < 0) { printf("[-] Failed to getsockname: %s\n", strerror(errno)); exit(1); } printf("[+] getsockname successful (port=%d).\n", ntohs(sin.sin_port)); res = listen(srv, 10); if (res < 0) { printf("[-] Failed to listen: %s\n", strerror(errno)); exit(1); } printf("[+] Server socket listening.\n"); int cli = socket(AF_INET, SOCK_STREAM, 0); if (cli < 0) { printf("[-] Failed to socket(AF_INET, SOCK_STREAM, 0) for client: %s\n", strerror(errno)); exit(1); } printf("[+] Client socket created.\n"); res = connect(cli, (struct sockaddr*)&sin, sizeof(sin)); if (res < 0) { printf("[-] Failed to connect client socket: %s\n", strerror(errno)); exit(1); } printf("[+] Client socket connected.\n"); int acc = accept(srv, NULL, NULL); if (acc < 0) { printf("[-] Failed to accept connection: %s\n", strerror(errno)); exit(1); } printf("[+] Accepted client connection on server.\n"); res = close(srv); if (res < 0) { printf("[-] Failed to close srv socket: %s\n", strerror(errno)); exit(1); } printf("[+] Server listening socket closed.\n"); res = close(acc); if (res < 0) { printf("[-] Failed to close acc socket: %s\n", strerror(errno)); exit(1); } printf("[+] Accepted peer socket closed (peer EOF).\n"); int repair = TCP_REPAIR_ON; res = setsockopt(cli, SOL_TCP, TCP_REPAIR, &repair, sizeof(repair)); if (res < 0) { printf("[-] Failed to setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON): %s\n", strerror(errno)); exit(1); } printf("[+] setsockopt(SOL_TCP, TCP_REPAIR, TCP_REPAIR_ON) successful.\n"); return cli; } /* Devices nbd5 through nbd10: Continuously trigger recv_work and inet_shutdown */ static void* runner_worker(void* arg) { long dev_idx = (long)arg; PIN_TO_CPU((int)(dev_idx % num_cpus)); char path[32]; snprintf(path, sizeof(path), "/dev/nbd%ld", dev_idx); while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { int fd = open(path, O_RDWR); if (fd < 0) { usleep(1000); continue; } ioctl(fd, NBD_SET_SOCK, shared_sock); ioctl(fd, NBD_DO_IT, 0); ioctl(fd, NBD_CLEAR_SOCK, 0); close(fd); } return NULL; } /* Devices nbd0 through nbd4: Continuously invoke nbd_reclassify_socket -> spin_lock_init */ static void* reclassifier_worker(void* arg) { long dev_idx = (long)arg; PIN_TO_CPU((int)(dev_idx % num_cpus)); char path[32]; snprintf(path, sizeof(path), "/dev/nbd%ld", dev_idx); while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { int fd = open(path, O_RDWR); if (fd < 0) { usleep(1000); continue; } for (int k = 0; k < 20 && __atomic_load_n(&running, __ATOMIC_RELAXED); k++) { ioctl(fd, NBD_SET_SOCK, shared_sock); ioctl(fd, NBD_CLEAR_SOCK, 0); } close(fd); } return NULL; } /* Disconnector thread: race NBD_DISCONNECT and direct user-space shutdown */ static void* disconnector_worker(void* arg) { (void)arg; PIN_TO_CPU((int)(TOTAL_DEVS % num_cpus)); while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { for (int i = 0; i < TOTAL_DEVS; i++) { char path[32]; snprintf(path, sizeof(path), "/dev/nbd%d", i); int fd = open(path, O_RDWR); if (fd >= 0) { ioctl(fd, NBD_DISCONNECT, 0); close(fd); } } shutdown(shared_sock, SHUT_RDWR); usleep(1000); } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for BUG: spinlock already unlocked in inet_shutdown...\n"); num_cpus = (int)sysconf(_SC_NPROCESSORS_ONLN); if (num_cpus < 1) num_cpus = 1; printf("[+] Detected %d online CPUs.\n", num_cpus); /* Step 1: Pre-open / mknod all NBD devices (nbd0..nbd10) to verify availability */ for (int i = 0; i < TOTAL_DEVS; i++) { int fd = open_nbd_device(i); if (fd < 0) { printf("[-] Failed to open/mknod /dev/nbd%d\n", i); exit(1); } int res = ioctl(fd, NBD_CLEAR_SOCK, 0); if (res < 0) { printf("[-] Failed to ioctl(NBD_CLEAR_SOCK) on /dev/nbd%d: %s\n", i, strerror(errno)); exit(1); } res = close(fd); if (res < 0) { printf("[-] Failed to close /dev/nbd%d: %s\n", i, strerror(errno)); exit(1); } } printf("[+] All %d NBD devices initialized and verified.\n", TOTAL_DEVS); /* Step 2: Set up shared TCP socket with TCP_REPAIR enabled */ shared_sock = setup_tcp_socket(); if (shared_sock < 0) { printf("[-] Failed to setup TCP socket: %s\n", strerror(errno)); exit(1); } printf("[+] Shared TCP socket ready (fd=%d).\n", shared_sock); /* Step 3: Launch worker threads */ pthread_t threads[TOTAL_DEVS + 1]; /* Start reclassifier threads on devices 0..4 */ for (long i = 0; i < NUM_RECLASSIFIERS; i++) { int res = pthread_create(&threads[i], NULL, reclassifier_worker, (void*)i); if (res != 0) { printf("[-] Failed to pthread_create reclassifier %ld: %s\n", i, strerror(res)); exit(1); } } printf("[+] Started %d reclassifier threads on devices 0..%d.\n", NUM_RECLASSIFIERS, NUM_RECLASSIFIERS - 1); /* Start runner threads on devices 5..10 */ for (long i = 0; i < NUM_RUNNERS; i++) { long dev_idx = NUM_RECLASSIFIERS + i; int res = pthread_create(&threads[dev_idx], NULL, runner_worker, (void*)dev_idx); if (res != 0) { printf("[-] Failed to pthread_create runner %ld: %s\n", dev_idx, strerror(res)); exit(1); } } printf("[+] Started %d runner threads on devices %d..%d.\n", NUM_RUNNERS, NUM_RECLASSIFIERS, TOTAL_DEVS - 1); /* Start disconnector thread */ int res = pthread_create(&threads[TOTAL_DEVS], NULL, disconnector_worker, NULL); if (res != 0) { printf("[-] Failed to pthread_create disconnector thread: %s\n", strerror(res)); exit(1); } printf("[+] Started disconnector thread.\n"); printf("[*] Racing NBD_SET_SOCK against NBD_DO_IT (recv_work) and inet_shutdown...\n"); TIMER_START(start); while (TIMER_NOT_EXPIRED(start, 25.0) && __atomic_load_n(&running, __ATOMIC_RELAXED)) { usleep(200000); } printf("[*] Race window completed, stopping threads...\n"); __atomic_store_n(&running, false, __ATOMIC_RELAXED); for (int i = 0; i <= TOTAL_DEVS; i++) { pthread_join(threads[i], NULL); } printf("[+] All worker threads stopped.\n"); for (int i = 0; i < TOTAL_DEVS; i++) { char path[32]; snprintf(path, sizeof(path), "/dev/nbd%d", i); int fd = open(path, O_RDWR); if (fd >= 0) { ioctl(fd, NBD_CLEAR_SOCK, 0); close(fd); } } res = close(shared_sock); if (res < 0) { printf("[-] Failed to close shared_sock: %s\n", strerror(errno)); exit(1); } printf("[+] Closed shared_sock successfully.\n"); printf("[+] Reproducer finished cleanly.\n"); return 0; }