// https://syzkaller.appspot.com/bug?id=7349616606afa3c986c377792f7ccbf9daae1142 #define _GNU_SOURCE #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 // } void write_file(const char *path, const char *val, int exit_on_fail) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] open(%s) failed: %s\n", path, strerror(errno)); if (exit_on_fail) exit(1); return; } if (write(fd, val, strlen(val)) < 0) { printf("[-] write(%s) failed: %s\n", path, strerror(errno)); if (exit_on_fail) exit(1); } close(fd); printf("[+] Wrote %s to %s\n", val, path); } void *sender_thread(void *arg) { int cpu = (int)(long)arg; PIN_TO_CPU(cpu); int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { printf("[-] socket failed: %s\n", strerror(errno)); exit(1); } if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "lo", 2) < 0) { printf("[-] SO_BINDTODEVICE failed: %s\n", strerror(errno)); } struct in_addr localInterface; localInterface.s_addr = inet_addr("127.0.0.1"); if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &localInterface, sizeof(localInterface)) < 0) { printf("[-] IP_MULTICAST_IF failed: %s\n", strerror(errno)); } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = inet_addr("224.0.0.1"); char buf[64] = "hello"; // Use sendmmsg to flood packets as fast as possible struct mmsghdr msgs[32]; struct iovec iovecs[32]; for (int i = 0; i < 32; i++) { iovecs[i].iov_base = buf; iovecs[i].iov_len = sizeof(buf); msgs[i].msg_hdr.msg_name = &addr; msgs[i].msg_hdr.msg_namelen = sizeof(addr); msgs[i].msg_hdr.msg_iov = &iovecs[i]; msgs[i].msg_hdr.msg_iovlen = 1; msgs[i].msg_hdr.msg_control = NULL; msgs[i].msg_hdr.msg_controllen = 0; msgs[i].msg_hdr.msg_flags = 0; } while (1) { sendmmsg(sock, msgs, 32, 0); } return NULL; } int main() { SETUP_UNBUFFERED_IO(); // 1. Set massive quotas to prevent process_backlog from yielding write_file("/proc/sys/net/core/dev_weight", "2000000000", 1); write_file("/proc/sys/net/core/netdev_max_backlog", "100000", 1); // 2. Enable RPS on lo to forward all packets to CPU 0 write_file("/sys/class/net/lo/queues/rx-0/rps_cpus", "1", 0); // 3. Bring lo up and assign IP int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { printf("[-] socket failed: %s\n", strerror(errno)); exit(1); } struct ifreq ifr; strncpy(ifr.ifr_name, "lo", IFNAMSIZ); if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) { printf("[-] ioctl(SIOCGIFFLAGS) failed: %s\n", strerror(errno)); exit(1); } ifr.ifr_flags |= IFF_UP | IFF_RUNNING | IFF_MULTICAST; if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) { printf("[-] ioctl(SIOCSIFFLAGS) failed: %s\n", strerror(errno)); exit(1); } // Assign 127.0.0.1 to lo (usually already assigned, but just in case) struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr; sin->sin_family = AF_INET; sin->sin_addr.s_addr = inet_addr("127.0.0.1"); ioctl(sock, SIOCSIFADDR, &ifr); close(sock); printf("[+] Brought lo up\n"); // 4. Create multiple multicast receivers to amplify RX work // This ensures CPU 0 (RX) is much slower than CPU 1 (TX), keeping the backlog queue permanently non-empty. printf("[+] Creating multicast receivers...\n"); for (int i = 0; i < 100; i++) { int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) continue; int opt = 1; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = inet_addr("224.0.0.1"); if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(s); continue; } struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1"); mreq.imr_interface.s_addr = inet_addr("127.0.0.1"); setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); } // 5. Spawn threads on other CPUs to flood CPU 0's backlog int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); int num_threads = 4; printf("[+] Spawning %d sender threads\n", num_threads); for (int i = 0; i < num_threads; i++) { pthread_t t; int target_cpu = num_cpus > 1 ? (i % (num_cpus - 1)) + 1 : 0; if (pthread_create(&t, NULL, sender_thread, (void *)(long)target_cpu) != 0) { printf("[-] pthread_create failed: %s\n", strerror(errno)); exit(1); } } // 6. Wait for soft lockup to trigger (watchdog threshold is usually 20s) printf("[+] Waiting for soft lockup...\n"); sleep(150); return 0; }