// https://syzkaller.appspot.com/bug?id=cd2b808bd2f5b00cd5c9d17bd49f815fbbd16e87 #define _GNU_SOURCE #include #include #include #include #include #include #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 // } #ifndef DLM_LSFL_FS #define DLM_LSFL_FS 0x00000004 #endif static inline void busy_delay_us(int us) { if (us <= 0) return; struct timespec start, cur; if (clock_gettime(CLOCK_MONOTONIC, &start) < 0) return; long nsec = (long)us * 1000; while (1) { if (clock_gettime(CLOCK_MONOTONIC, &cur) < 0) break; long elapsed = (cur.tv_sec - start.tv_sec) * 1000000000L + (cur.tv_nsec - start.tv_nsec); if (elapsed >= nsec) break; } } static void unblock_and_reap_child(pid_t pid, const char *ls_name) { char path[128]; snprintf(path, sizeof(path), "/sys/kernel/dlm/%s/event_done", ls_name); for (int i = 0; i < 50; i++) { int status; pid_t r = waitpid(pid, &status, WNOHANG); if (r == pid) return; int ev_fd = open(path, O_WRONLY); if (ev_fd >= 0) { ssize_t written = write(ev_fd, "-1\n", 3); (void)written; close(ev_fd); } usleep(2000); } kill(pid, SIGKILL); int status; waitpid(pid, &status, 0); } int main(void) { SETUP_UNBUFFERED_IO(); printf("[*] Starting reproducer for WARNING in __dlm_new_lockspace...\n"); if (geteuid() != 0) { printf("[-] Insufficient privileges: root (CAP_SYS_ADMIN) required\n"); exit(1); } printf("[+] Running with root privileges.\n"); /* 1. Ensure configfs is mounted */ struct stat st; if (stat("/sys/kernel/config", &st) < 0) { int res = mkdir("/sys/kernel/config", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config: %s\n", strerror(errno)); exit(1); } printf("[+] Created /sys/kernel/config directory.\n"); } int res = mount("configfs", "/sys/kernel/config", "configfs", 0, NULL); if (res < 0) { if (errno == EBUSY) { printf("[+] configfs already mounted at /sys/kernel/config.\n"); } else { printf("[-] Failed to mount configfs at /sys/kernel/config: %s\n", strerror(errno)); exit(1); } } else { printf("[+] Mounted configfs at /sys/kernel/config.\n"); } res = stat("/sys/kernel/config/dlm", &st); if (res < 0) { printf("[-] DLM configfs subsystem not found: %s\n", strerror(errno)); exit(1); } printf("[+] DLM configfs subsystem present.\n"); /* 2. Configure DLM cluster and comms node */ res = mkdir("/sys/kernel/config/dlm/cluster", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/dlm/cluster: %s\n", strerror(errno)); exit(1); } printf("[+] DLM cluster group created/ready.\n"); res = mkdir("/sys/kernel/config/dlm/cluster/comms/1", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/config/dlm/cluster/comms/1: %s\n", strerror(errno)); exit(1); } printf("[+] DLM comms node 1 directory created/ready.\n"); int fd_local = open("/sys/kernel/config/dlm/cluster/comms/1/local", O_WRONLY); if (fd_local < 0) { printf("[-] Failed to open .../comms/1/local: %s\n", strerror(errno)); exit(1); } ssize_t wres = write(fd_local, "1\n", 2); if (wres < 0) { printf("[-] Failed to write to .../comms/1/local: %s\n", strerror(errno)); close(fd_local); exit(1); } close(fd_local); printf("[+] Set comms node 1 as local node.\n"); struct sockaddr_storage addr; memset(&addr, 0, sizeof(addr)); struct sockaddr_in *sin = (struct sockaddr_in *)&addr; sin->sin_family = AF_INET; sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); int fd_addr = open("/sys/kernel/config/dlm/cluster/comms/1/addr", O_WRONLY); if (fd_addr < 0) { printf("[-] Failed to open .../comms/1/addr: %s\n", strerror(errno)); exit(1); } wres = write(fd_addr, &addr, sizeof(addr)); if (wres < 0 && errno != ENOSPC) { printf("[-] Failed to write to .../comms/1/addr: %s\n", strerror(errno)); close(fd_addr); exit(1); } close(fd_addr); printf("[+] Configured local node address.\n"); /* 3. Ensure /dev/dlm-control exists */ if (stat("/dev/dlm-control", &st) < 0) { printf("[*] /dev/dlm-control not found, querying sysfs...\n"); FILE *f = fopen("/sys/class/misc/dlm-control/dev", "r"); if (!f) { printf("[-] Failed to open /sys/class/misc/dlm-control/dev: %s\n", strerror(errno)); exit(1); } unsigned int maj = 0, min = 0; if (fscanf(f, "%u:%u", &maj, &min) != 2) { printf("[-] Failed to parse maj:min from sysfs\n"); fclose(f); exit(1); } fclose(f); printf("[+] dlm-control device major=%u minor=%u\n", maj, min); res = mknod("/dev/dlm-control", S_IFCHR | 0666, makedev(maj, min)); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mknod /dev/dlm-control: %s\n", strerror(errno)); exit(1); } printf("[+] Created /dev/dlm-control device node.\n"); } else { printf("[+] /dev/dlm-control device node exists.\n"); } /* Verify device opens cleanly */ int test_fd = open("/dev/dlm-control", O_RDWR); if (test_fd < 0) { printf("[-] Failed to open /dev/dlm-control: %s\n", strerror(errno)); exit(1); } close(test_fd); printf("[+] Verified /dev/dlm-control accessibility.\n"); /* 4. Setup shared memory synchronization */ volatile uint32_t *sync_ready = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (sync_ready == MAP_FAILED) { printf("[-] Failed to mmap shared memory: %s\n", strerror(errno)); exit(1); } printf("[+] Shared synchronization page initialized.\n"); int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); if (num_cpus < 1) num_cpus = 1; printf("[+] System has %d CPU(s) online.\n", num_cpus); static const int delays_us[] = { 0, 1, 2, 3, 5, 8, 12, 16, 20, 25, 30, 40, 50, 70, 100, 150, 200, 300, 500, 1000 }; const int num_delays = sizeof(delays_us) / sizeof(delays_us[0]); const int total_iterations = 200; printf("[*] Starting %d race iterations to trigger the uninitialized kobject bug...\n", total_iterations); for (int iter = 0; iter < total_iterations; iter++) { SIGNAL(sync_ready, 0); char ls_name[32]; snprintf(ls_name, sizeof(ls_name), "ls_%d_%d", getpid(), iter); pid_t pid = fork(); if (pid < 0) { printf("[-] Failed to fork iteration %d: %s\n", iter, strerror(errno)); exit(1); } if (pid == 0) { if (num_cpus >= 2) PIN_TO_CPU(0); int fd = open("/dev/dlm-control", O_RDWR); if (fd < 0) { printf("[-] Child failed to open /dev/dlm-control: %s\n", strerror(errno)); _exit(1); } char buf[128]; memset(buf, 0, sizeof(buf)); struct dlm_write_request *req = (struct dlm_write_request *)buf; req->version[0] = DLM_DEVICE_VERSION_MAJOR; req->version[1] = DLM_DEVICE_VERSION_MINOR; req->version[2] = DLM_DEVICE_VERSION_PATCH; req->cmd = DLM_USER_CREATE_LOCKSPACE; req->is64bit = 1; req->i.lspace.flags = DLM_LSFL_FS; snprintf(req->i.lspace.name, 32, "%s", ls_name); SIGNAL(sync_ready, 1); syscall(SYS_write, fd, buf, sizeof(buf)); close(fd); _exit(0); } if (num_cpus >= 2) PIN_TO_CPU(1); WAIT_ON(sync_ready, 1); int delay = delays_us[iter % num_delays]; busy_delay_us(delay); kill(pid, SIGKILL); unblock_and_reap_child(pid, ls_name); } printf("[+] Completed all %d race iterations.\n", total_iterations); /* Allow kernel asynchronous workers and logging to settle */ sleep(2); printf("[+] Test finished successfully.\n"); return 0; }