// https://syzkaller.appspot.com/bug?id=f97a6312e9644543f11f37129406269c1b6f6469 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include static void pin_cpu(int cpu) { long nprocs = sysconf(_SC_NPROCESSORS_ONLN); if (nprocs > 0) cpu = cpu % (int)nprocs; else cpu = 0; cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset); } static void write_file(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] Failed to open %s: %s\n", path, strerror(errno)); exit(1); } size_t len = strlen(val); ssize_t written = write(fd, val, len); if (written < 0 || (size_t)written != len) { printf("[-] Failed to write to %s: %s\n", path, strerror(errno)); exit(1); } int res = close(fd); if (res < 0) { printf("[-] Failed to close %s fd: %s\n", path, strerror(errno)); exit(1); } printf("[+] Successfully wrote '%s' to %s\n", val, path); } static void setup_environment(void) { 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("[+] mkdir /sys/kernel/config successful or directory already exists.\n"); res = mount("none", "/sys/kernel/config", "configfs", 0, NULL); if (res < 0 && errno != EBUSY) { printf("[-] Failed to mount configfs: %s\n", strerror(errno)); exit(1); } printf("[+] configfs mounted or already mounted.\n"); res = mkdir("/sys/kernel/debug", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /sys/kernel/debug: %s\n", strerror(errno)); exit(1); } printf("[+] mkdir /sys/kernel/debug successful or directory already exists.\n"); res = mount("none", "/sys/kernel/debug", "debugfs", 0, NULL); if (res < 0 && errno != EBUSY) { printf("[-] Failed to mount debugfs: %s\n", strerror(errno)); exit(1); } printf("[+] debugfs mounted or already mounted.\n"); /* Configure failslab to allow fault injection on GFP_KERNEL allocations */ write_file("/sys/kernel/debug/failslab/ignore-gfp-wait", "N\n"); write_file("/sys/kernel/debug/failslab/cache-filter", "N\n"); write_file("/sys/kernel/debug/failslab/times", "-1\n"); } struct helper_info { const char *path; int flags; int cpu; }; #define NUM_HELPERS 6 static pthread_t helper_threads[NUM_HELPERS]; static struct helper_info helpers[NUM_HELPERS]; static volatile int stop_race = 0; static void *helper_fn(void *arg) { struct helper_info *hi = (struct helper_info *)arg; pin_cpu(hi->cpu); long nprocs = sysconf(_SC_NPROCESSORS_ONLN); const char *path = hi->path; int flags = hi->flags; while (!__atomic_load_n(&stop_race, __ATOMIC_RELAXED)) { int fd = open(path, flags); if (fd >= 0) { close(fd); } if (nprocs == 1) { sched_yield(); } } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for kernel BUG in create_default_group...\n"); setup_environment(); const char *target = "/sys/kernel/config/nvmet/ports/1"; const char *target_ana = "/sys/kernel/config/nvmet/ports/1/ana_groups"; const char *target_sub = "/sys/kernel/config/nvmet/ports/1/subsystems"; /* Pre-clean target directory if present */ int res = rmdir(target); if (res < 0 && errno != ENOENT) { printf("[-] Failed to pre-clean %s: %s\n", target, strerror(errno)); exit(1); } printf("[+] Pre-clean of %s successful.\n", target); /* Determine processor count and launch racing background threads */ long nprocs = sysconf(_SC_NPROCESSORS_ONLN); if (nprocs < 1) nprocs = 1; printf("[+] Online CPU count: %ld\n", nprocs); for (int i = 0; i < NUM_HELPERS; i++) { helpers[i].cpu = (nprocs > 1) ? (1 + (i % (int)(nprocs - 1))) : 0; if (i == 4) { helpers[i].path = target_sub; helpers[i].flags = O_RDONLY | O_DIRECTORY; } else if (i == 5) { helpers[i].path = target; helpers[i].flags = O_RDONLY | O_DIRECTORY; } else { helpers[i].path = target_ana; helpers[i].flags = (i % 2 == 0) ? (O_RDONLY | O_DIRECTORY) : O_RDONLY; } int err = pthread_create(&helper_threads[i], NULL, helper_fn, &helpers[i]); if (err != 0) { printf("[-] Failed to create helper thread %d: %s\n", i, strerror(err)); exit(1); } } printf("[+] Started %d background racing helper threads.\n", NUM_HELPERS); printf("[*] Iterating fault injection points (fail-nth 1..80 with concurrent race)...\n"); for (int n = 1; n <= 80; n++) { int reps = (n >= 38 && n <= 50) ? 12 : 1; printf("[*] Testing fail-nth = %d (%d attempts)...\n", n, reps); for (int r = 0; r < reps; r++) { pid_t pid = fork(); if (pid < 0) { printf("[-] Failed to fork: %s\n", strerror(errno)); exit(1); } if (pid == 0) { /* Keep child on CPU 0 to run concurrently with helpers on other CPUs */ pin_cpu(0); char buf[32]; int len = snprintf(buf, sizeof(buf), "%d\n", n); if (len < 0 || len >= (int)sizeof(buf)) { _exit(1); } int fd = open("/proc/thread-self/fail-nth", O_WRONLY); if (fd < 0) { fd = open("/proc/self/fail-nth", O_WRONLY); } if (fd < 0) { _exit(1); } ssize_t written = write(fd, buf, (size_t)len); if (written < 0 || written != len) { _exit(1); } /* * mkdir() triggers: * configfs_mkdir -> configfs_attach_group -> populate_groups * -> create_default_group("ana_groups") -> configfs_attach_group * -> populate_groups -> create_default_group("1") * At n=43, alloc_inode fails. * Because helper threads are racing open() on ana_groups, * ana_groups has dentry refcount > 1. * d_delete() skips dentry_unlink_inode(), leaving d_inode non-NULL. * create_default_group hits BUG_ON(d_inode(child)) at fs/configfs/dir.c:734! */ mkdir(target, 0777); _exit(0); } int status = 0; pid_t wpid = waitpid(pid, &status, 0); if (wpid < 0) { printf("[-] waitpid failed: %s\n", strerror(errno)); exit(1); } if (WIFSIGNALED(status)) { printf("[!] Child terminated by signal %d (%s) at n=%d rep=%d\n", WTERMSIG(status), strsignal(WTERMSIG(status)), n, r); } /* Cleanup target if mkdir succeeded */ res = rmdir(target); if (res < 0 && errno != ENOENT) { int retries = 0; while (res < 0 && errno == EBUSY && retries < 20) { usleep(200); res = rmdir(target); retries++; } } } } printf("[*] Stopping helper threads...\n"); __atomic_store_n(&stop_race, 1, __ATOMIC_RELAXED); for (int i = 0; i < NUM_HELPERS; i++) { pthread_join(helper_threads[i], NULL); } printf("[+] Helper threads joined.\n"); printf("[-] Loop finished without triggering crash.\n"); return 0; }