// https://syzkaller.appspot.com/bug?id=dd35e38993f9b65c42db9314a5072ea01f21d47f #define _GNU_SOURCE #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 // } static char n_vclocks_path[256]; static char base_clock_name[64]; static int ptp_major = 244; static int n_cpus = 1; static volatile int running = 1; static void pin_cpu(int cpu) { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(cpu % n_cpus, &mask); sched_setaffinity(0, sizeof(mask), &mask); } // Thread 0: Continuously registers and unregisters virtual clocks static void *thread_lifecycle(void *arg) { (void)arg; pin_cpu(0); printf("[+] Lifecycle thread started on CPU 0.\n"); int fd = open(n_vclocks_path, O_RDWR); if (fd < 0) { printf("[-] Failed to open %s in lifecycle thread: %s\n", n_vclocks_path, strerror(errno)); return NULL; } printf("[+] Lifecycle thread successfully opened %s.\n", n_vclocks_path); unsigned int seed = 12345; const char *create_cmds[] = {"2\n", "4\n"}; int cmd_idx = 0; while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { // Register virtual clocks pwrite(fd, create_cmds[cmd_idx], 2, 0); cmd_idx ^= 1; usleep(rand_r(&seed) % 250); // Unregister virtual clocks pwrite(fd, "0\n", 2, 0); usleep(rand_r(&seed) % 250); } pwrite(fd, "0\n", 2, 0); close(fd); printf("[+] Lifecycle thread finished.\n"); return NULL; } // Thread 1 & 2: Rapidly open and close persistent character devices static void *thread_closer(void *arg) { int cpu = (int)(uintptr_t)arg; pin_cpu(cpu); const char *nodes[] = { "/dev/ptp_v1", "/dev/ptp_v2", "/dev/ptp_v3", "/dev/ptp_v4", "/dev/ptp1", "/dev/ptp2" }; while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { for (int i = 0; i < 6; i++) { int fd = open(nodes[i], O_RDWR | O_NONBLOCK); if (fd >= 0) { close(fd); } } } return NULL; } // Thread 3 & 4: Fork children that exit holding open descriptors (fput_close_sync path) static void *thread_exiter(void *arg) { int cpu = (int)(uintptr_t)arg; pin_cpu(cpu); const char *nodes[] = { "/dev/ptp_v1", "/dev/ptp_v2", "/dev/ptp_v3", "/dev/ptp_v4", "/dev/ptp1", "/dev/ptp2" }; while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { pid_t pid = fork(); if (pid < 0) { usleep(1000); continue; } if (pid == 0) { // Child: open devices across descriptors and exit immediately for (int r = 0; r < 2; r++) { for (int i = 0; i < 6; i++) { int fd = open(nodes[i], O_RDWR | O_NONBLOCK); (void)fd; } } _exit(0); } int status = 0; waitpid(pid, &status, 0); } return NULL; } // Thread 5: Multi-descriptor batch opener/closer with yield static void *thread_batch(void *arg) { int cpu = (int)(uintptr_t)arg; pin_cpu(cpu); const char *nodes[] = { "/dev/ptp_v1", "/dev/ptp_v2", "/dev/ptp_v3", "/dev/ptp_v4" }; int fds[16]; while (__atomic_load_n(&running, __ATOMIC_RELAXED)) { int count = 0; for (int r = 0; r < 3; r++) { for (int i = 0; i < 4; i++) { int fd = open(nodes[i], O_RDWR | O_NONBLOCK); if (fd >= 0) { if (count < 16) { fds[count++] = fd; } else { close(fd); } } } } sched_yield(); for (int i = 0; i < count; i++) { close(fds[i]); } } return NULL; } static int find_base_clock(void) { printf("[*] Searching /sys/class/ptp for base clock with n_vclocks...\n"); DIR *d = opendir("/sys/class/ptp"); if (!d) { printf("[-] Failed to opendir /sys/class/ptp: %s\n", strerror(errno)); return -1; } struct dirent *de; int found = 0; while ((de = readdir(d)) != NULL) { if (strncmp(de->d_name, "ptp", 3) != 0) continue; char test_path[256]; snprintf(test_path, sizeof(test_path), "/sys/class/ptp/%s/n_vclocks", de->d_name); if (access(test_path, W_OK) == 0) { strncpy(base_clock_name, de->d_name, sizeof(base_clock_name) - 1); base_clock_name[sizeof(base_clock_name) - 1] = '\0'; snprintf(n_vclocks_path, sizeof(n_vclocks_path), "%s", test_path); found = 1; break; } } closedir(d); if (!found) { printf("[-] No base PTP clock with writable n_vclocks found in /sys/class/ptp\n"); return -1; } printf("[+] Found base PTP clock: %s, sysfs path: %s\n", base_clock_name, n_vclocks_path); // Query major number from /sys/class/ptp//dev char dev_path[256]; snprintf(dev_path, sizeof(dev_path), "/sys/class/ptp/%s/dev", base_clock_name); FILE *f = fopen(dev_path, "r"); if (f) { int maj = 0, min = 0; if (fscanf(f, "%d:%d", &maj, &min) == 2) { ptp_major = maj; printf("[+] Discovered PTP major number: %d (minor: %d)\n", maj, min); } fclose(f); } else { printf("[-] Warning: Failed to open %s: %s, defaulting to major %d\n", dev_path, strerror(errno), ptp_major); } return 0; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting multi-threaded PTP cdev_put UAF race reproducer...\n"); n_cpus = (int)sysconf(_SC_NPROCESSORS_ONLN); if (n_cpus < 1) n_cpus = 1; printf("[+] Detected %d online CPU cores.\n", n_cpus); int res = find_base_clock(); if (res < 0) { printf("[-] Failed to locate base PTP clock.\n"); exit(1); } // Step 1: Reset existing virtual clocks printf("[*] Resetting any active virtual clocks on %s...\n", n_vclocks_path); int fd_reset = open(n_vclocks_path, O_WRONLY); if (fd_reset < 0) { printf("[-] Failed to open %s for reset: %s\n", n_vclocks_path, strerror(errno)); exit(1); } ssize_t nw = write(fd_reset, "0\n", 2); if (nw != 2) { printf("[-] Failed to write reset command to %s: %s\n", n_vclocks_path, strerror(errno)); close(fd_reset); exit(1); } close(fd_reset); printf("[+] Active virtual clocks reset successfully.\n"); usleep(50000); // Step 2: Create persistent character device nodes for minors 1 through 4 printf("[*] Creating persistent device nodes /dev/ptp_v1 .. /dev/ptp_v4...\n"); for (int min = 1; min <= 4; min++) { char node_path[64]; snprintf(node_path, sizeof(node_path), "/dev/ptp_v%d", min); unlink(node_path); res = mknod(node_path, S_IFCHR | 0666, makedev(ptp_major, min)); if (res < 0) { printf("[-] Failed to mknod %s (maj=%d, min=%d): %s\n", node_path, ptp_major, min, strerror(errno)); exit(1); } printf("[+] Created character device node: %s\n", node_path); } // Step 3: Spawn race worker threads printf("[*] Spawning 6 race worker threads across CPU cores...\n"); #define NUM_WORKERS 6 pthread_t threads[NUM_WORKERS]; int rc = pthread_create(&threads[0], NULL, thread_lifecycle, NULL); if (rc != 0) { printf("[-] Failed to create thread_lifecycle: %s\n", strerror(rc)); exit(1); } rc = pthread_create(&threads[1], NULL, thread_closer, (void *)(uintptr_t)1); if (rc != 0) { printf("[-] Failed to create thread_closer 1: %s\n", strerror(rc)); exit(1); } rc = pthread_create(&threads[2], NULL, thread_closer, (void *)(uintptr_t)2); if (rc != 0) { printf("[-] Failed to create thread_closer 2: %s\n", strerror(rc)); exit(1); } rc = pthread_create(&threads[3], NULL, thread_exiter, (void *)(uintptr_t)1); if (rc != 0) { printf("[-] Failed to create thread_exiter 1: %s\n", strerror(rc)); exit(1); } rc = pthread_create(&threads[4], NULL, thread_exiter, (void *)(uintptr_t)2); if (rc != 0) { printf("[-] Failed to create thread_exiter 2: %s\n", strerror(rc)); exit(1); } rc = pthread_create(&threads[5], NULL, thread_batch, (void *)(uintptr_t)3); if (rc != 0) { printf("[-] Failed to create thread_batch: %s\n", strerror(rc)); exit(1); } printf("[+] All worker threads created successfully.\n"); // Step 4: Run concurrent race for 12 seconds printf("[*] Racing clock unregistration vs. file descriptor closure for 12 seconds...\n"); TIMER_START(start); int elapsed_sec = 0; while (TIMER_NOT_EXPIRED(start, 12.0)) { usleep(1000000); elapsed_sec++; printf("[*] Race running... (%d/12 seconds elapsed)\n", elapsed_sec); } // Step 5: Terminate threads and cleanup printf("[*] Stopping race worker threads...\n"); __atomic_store_n(&running, 0, __ATOMIC_RELEASE); for (int i = 0; i < NUM_WORKERS; i++) { pthread_join(threads[i], NULL); } printf("[+] All worker threads stopped and joined.\n"); // Clean up persistent device nodes for (int min = 1; min <= 4; min++) { char node_path[64]; snprintf(node_path, sizeof(node_path), "/dev/ptp_v%d", min); unlink(node_path); } printf("[+] Persistent device nodes unlinked.\n"); // Reset virtual clocks fd_reset = open(n_vclocks_path, O_WRONLY); if (fd_reset >= 0) { write(fd_reset, "0\n", 2); close(fd_reset); } printf("[*] Sleeping 2 seconds for any delayed kernel logs...\n"); sleep(2); printf("[+] Reproducer run completed successfully.\n"); return 0; }