// https://syzkaller.appspot.com/bug?id=704f4174fde8607450da4f02de1f84d3672112f6 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef __NR_io_uring_setup #define __NR_io_uring_setup 425 #endif #ifndef __NR_io_uring_enter #define __NR_io_uring_enter 426 #endif #ifndef __NR_exit #define __NR_exit 60 #endif #ifndef IORING_OFF_SQ_RING #define IORING_OFF_SQ_RING 0ULL #endif #ifndef IORING_OFF_SQES #define IORING_OFF_SQES 0x10000000ULL #endif #ifndef IORING_OP_OPENAT #define IORING_OP_OPENAT 18 #endif #ifndef IOSQE_ASYNC #define IOSQE_ASYNC (1U << 4) #endif #ifndef FUSE_KERNEL_VERSION #define FUSE_KERNEL_VERSION 7 #endif #ifndef FUSE_INIT #define FUSE_INIT 26 #endif #define MSR_IA32_TSC 0x00000010 #define MSR_IA32_TSC_ADJUST 0x0000003b static int g_fuse_fd = -1; static volatile int g_req_read = 0; static int g_msr_fds[64]; static int g_num_cpus = 0; static inline uint64_t read_tsc(void) { uint32_t lo, hi; __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); return ((uint64_t)hi << 32) | lo; } static uint64_t measure_tsc_freq(void) { uint64_t t1 = read_tsc(); usleep(20000); uint64_t t2 = read_tsc(); uint64_t diff = t2 - t1; uint64_t freq = diff * 50; if (freq < 500000000ULL || freq > 10000000000ULL) { printf("[*] Measured TSC freq unusual (%llu Hz), using fallback 2.5 GHz\n", (unsigned long long)freq); return 2500000000ULL; } printf("[+] Measured TSC frequency: %llu Hz\n", (unsigned long long)freq); return freq; } static void init_msr(void) { printf("[*] Initializing MSR device access...\n"); g_num_cpus = (int)sysconf(_SC_NPROCESSORS_CONF); if (g_num_cpus <= 0 || g_num_cpus > 64) g_num_cpus = 1; for (int i = 0; i < 64; i++) g_msr_fds[i] = -1; int afd = open("/sys/module/msr/parameters/allow_writes", O_WRONLY); if (afd >= 0) { ssize_t w = write(afd, "on\n", 3); if (w > 0) printf("[+] Enabled /sys/module/msr/parameters/allow_writes\n"); close(afd); } mkdir("/dev/cpu", 0755); for (int i = 0; i < g_num_cpus; i++) { char dir[64], path[64]; snprintf(dir, sizeof(dir), "/dev/cpu/%d", i); mkdir(dir, 0755); snprintf(path, sizeof(path), "/dev/cpu/%d/msr", i); if (access(path, F_OK) != 0) { mknod(path, S_IFCHR | 0600, makedev(202, i)); } g_msr_fds[i] = open(path, O_RDWR); if (g_msr_fds[i] < 0) { char alt_path[64]; snprintf(alt_path, sizeof(alt_path), "/dev/msr%d", i); if (access(alt_path, F_OK) != 0) mknod(alt_path, S_IFCHR | 0600, makedev(202, i)); g_msr_fds[i] = open(alt_path, O_RDWR); } if (g_msr_fds[i] >= 0) { printf("[+] Successfully opened MSR device for CPU %d (fd=%d)\n", i, g_msr_fds[i]); } else { printf("[-] Note: Could not open MSR device for CPU %d: %s\n", i, strerror(errno)); } } } static int advance_tsc(uint64_t seconds) { uint64_t freq = measure_tsc_freq(); uint64_t delta_cycles = freq * seconds; int success_count = 0; printf("[*] Advancing TSC by %llu seconds (%llu cycles) to fast-forward kernel jiffies...\n", (unsigned long long)seconds, (unsigned long long)delta_cycles); for (int i = 0; i < g_num_cpus; i++) { if (g_msr_fds[i] < 0) continue; uint64_t adjust = 0; ssize_t r = pread(g_msr_fds[i], &adjust, sizeof(adjust), MSR_IA32_TSC_ADJUST); if (r == sizeof(adjust)) { adjust += delta_cycles; ssize_t w = pwrite(g_msr_fds[i], &adjust, sizeof(adjust), MSR_IA32_TSC_ADJUST); if (w == sizeof(adjust)) { printf("[+] CPU %d: Written MSR_IA32_TSC_ADJUST (+%llu cycles)\n", i, (unsigned long long)delta_cycles); success_count++; continue; } } uint64_t tsc = 0; r = pread(g_msr_fds[i], &tsc, sizeof(tsc), MSR_IA32_TSC); if (r == sizeof(tsc)) { tsc += delta_cycles; ssize_t w = pwrite(g_msr_fds[i], &tsc, sizeof(tsc), MSR_IA32_TSC); if (w == sizeof(tsc)) { printf("[+] CPU %d: Written MSR_IA32_TSC (+%llu cycles)\n", i, (unsigned long long)delta_cycles); success_count++; } } } return success_count; } static void tune_hung_task(void) { printf("[*] Configuring /proc/sys/kernel/hung_task_timeout_secs...\n"); int fd = open("/proc/sys/kernel/hung_task_timeout_secs", O_WRONLY); if (fd < 0) { printf("[-] Warning: Failed to open /proc/sys/kernel/hung_task_timeout_secs: %s\n", strerror(errno)); return; } printf("[+] open /proc/sys/kernel/hung_task_timeout_secs successful.\n"); ssize_t w = write(fd, "2\n", 2); if (w < 0) { printf("[-] Warning: Failed to write to /proc/sys/kernel/hung_task_timeout_secs: %s\n", strerror(errno)); } else { printf("[+] write to /proc/sys/kernel/hung_task_timeout_secs successful.\n"); } int res = close(fd); if (res < 0) { printf("[-] Warning: Failed to close /proc/sys/kernel/hung_task_timeout_secs: %s\n", strerror(errno)); } else { printf("[+] close /proc/sys/kernel/hung_task_timeout_secs successful.\n"); } } static void setup_userns(void) { printf("[*] Setting up user and mount namespaces...\n"); int res = unshare(CLONE_NEWUSER | CLONE_NEWNS); if (res < 0) { printf("[-] Failed unshare(CLONE_NEWUSER | CLONE_NEWNS): %s\n", strerror(errno)); exit(1); } printf("[+] unshare(CLONE_NEWUSER | CLONE_NEWNS) successful.\n"); int fd = open("/proc/self/setgroups", O_WRONLY); if (fd < 0) { printf("[-] Failed to open /proc/self/setgroups: %s\n", strerror(errno)); exit(1); } printf("[+] open /proc/self/setgroups successful.\n"); ssize_t w = write(fd, "deny", 4); if (w != 4) { printf("[-] Failed to write to /proc/self/setgroups: %s\n", strerror(errno)); exit(1); } printf("[+] write to /proc/self/setgroups successful.\n"); res = close(fd); if (res < 0) { printf("[-] Failed to close /proc/self/setgroups: %s\n", strerror(errno)); exit(1); } printf("[+] close /proc/self/setgroups successful.\n"); fd = open("/proc/self/uid_map", O_WRONLY); if (fd < 0) { printf("[-] Failed to open /proc/self/uid_map: %s\n", strerror(errno)); exit(1); } printf("[+] open /proc/self/uid_map successful.\n"); w = write(fd, "0 0 1\n", 6); if (w != 6) { printf("[-] Failed to write to /proc/self/uid_map: %s\n", strerror(errno)); exit(1); } printf("[+] write to /proc/self/uid_map successful.\n"); res = close(fd); if (res < 0) { printf("[-] Failed to close /proc/self/uid_map: %s\n", strerror(errno)); exit(1); } printf("[+] close /proc/self/uid_map successful.\n"); fd = open("/proc/self/gid_map", O_WRONLY); if (fd < 0) { printf("[-] Failed to open /proc/self/gid_map: %s\n", strerror(errno)); exit(1); } printf("[+] open /proc/self/gid_map successful.\n"); w = write(fd, "0 0 1\n", 6); if (w != 6) { printf("[-] Failed to write to /proc/self/gid_map: %s\n", strerror(errno)); exit(1); } printf("[+] write to /proc/self/gid_map successful.\n"); res = close(fd); if (res < 0) { printf("[-] Failed to close /proc/self/gid_map: %s\n", strerror(errno)); exit(1); } printf("[+] close /proc/self/gid_map successful.\n"); } static int mount_and_init_fuse(const char *mountpoint) { printf("[*] Opening /dev/fuse...\n"); int fuse_fd = open("/dev/fuse", O_RDWR); if (fuse_fd < 0) { printf("[-] Failed to open /dev/fuse: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/fuse successful (fuse_fd=%d).\n", fuse_fd); int res = mkdir(mountpoint, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", mountpoint, strerror(errno)); exit(1); } printf("[+] mkdir %s successful.\n", mountpoint); char opts[128]; snprintf(opts, sizeof(opts), "fd=%d,rootmode=0040755,user_id=0,group_id=0", fuse_fd); printf("[*] Mounting FUSE on %s with options: %s...\n", mountpoint, opts); res = mount("fuse", mountpoint, "fuse", MS_NOSUID | MS_NODEV, opts); if (res < 0) { printf("[-] Failed to mount FUSE on %s: %s\n", mountpoint, strerror(errno)); exit(1); } printf("[+] mount FUSE on %s successful.\n", mountpoint); printf("[*] Reading FUSE_INIT request from /dev/fuse...\n"); char buf[65536]; ssize_t n = read(fuse_fd, buf, sizeof(buf)); if (n < (ssize_t)(sizeof(struct fuse_in_header) + sizeof(struct fuse_init_in))) { printf("[-] Failed to read FUSE_INIT request: %s (bytes=%zd)\n", strerror(errno), n); exit(1); } printf("[+] Read FUSE_INIT request (%zd bytes).\n", n); struct fuse_in_header *in_hdr = (struct fuse_in_header *)buf; if (in_hdr->opcode != FUSE_INIT) { printf("[-] Unexpected opcode for init request: %u (expected %u)\n", in_hdr->opcode, FUSE_INIT); exit(1); } struct fuse_init_in *init_in = (struct fuse_init_in *)(buf + sizeof(*in_hdr)); printf("[+] Received FUSE_INIT (unique=%llu, major=%u, minor=%u).\n", (unsigned long long)in_hdr->unique, init_in->major, init_in->minor); struct { struct fuse_out_header out; struct fuse_init_out init; } reply; memset(&reply, 0, sizeof(reply)); reply.out.len = sizeof(reply); reply.out.error = 0; reply.out.unique = in_hdr->unique; reply.init.major = FUSE_KERNEL_VERSION; reply.init.minor = init_in->minor; reply.init.max_readahead = 4096; reply.init.max_write = 4096; reply.init.max_background = 16; reply.init.congestion_threshold = 12; reply.init.time_gran = 1; printf("[*] Writing FUSE_INIT reply...\n"); ssize_t w = write(fuse_fd, &reply, sizeof(reply)); if (w != (ssize_t)sizeof(reply)) { printf("[-] Failed to write FUSE_INIT reply: %s (written=%zd)\n", strerror(errno), w); exit(1); } printf("[+] FUSE_INIT reply written successfully. FUSE filesystem initialized.\n"); return fuse_fd; } static void *submitter_thread(void *arg) { const char *mp = (const char *)arg; printf("[*] [Thread B] Submitter thread started.\n"); cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); sched_setaffinity(0, sizeof(cpuset), &cpuset); struct io_uring_params p; memset(&p, 0, sizeof(p)); int ring_fd = syscall(__NR_io_uring_setup, 8, &p); if (ring_fd < 0) { printf("[-] [Thread B] Failed io_uring_setup: %s\n", strerror(errno)); syscall(__NR_exit, 1); } printf("[+] [Thread B] io_uring_setup successful (ring_fd=%d, sq_entries=%u).\n", ring_fd, p.sq_entries); size_t sq_ring_sz = p.sq_off.array + p.sq_entries * sizeof(__u32); void *sq_ptr = mmap(NULL, sq_ring_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ring_fd, IORING_OFF_SQ_RING); if (sq_ptr == MAP_FAILED) { printf("[-] [Thread B] Failed mmap SQ_RING: %s\n", strerror(errno)); syscall(__NR_exit, 1); } printf("[+] [Thread B] mmap SQ_RING successful.\n"); size_t sqes_sz = p.sq_entries * sizeof(struct io_uring_sqe); struct io_uring_sqe *sqes = mmap(NULL, sqes_sz, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, ring_fd, IORING_OFF_SQES); if (sqes == MAP_FAILED) { printf("[-] [Thread B] Failed mmap SQES: %s\n", strerror(errno)); syscall(__NR_exit, 1); } printf("[+] [Thread B] mmap SQES successful.\n"); __u32 *sq_tail = (__u32 *)((char *)sq_ptr + p.sq_off.tail); __u32 *sq_array = (__u32 *)((char *)sq_ptr + p.sq_off.array); char fuse_file[256]; snprintf(fuse_file, sizeof(fuse_file), "%s/stuck_file", mp); struct io_uring_sqe *sqe = &sqes[0]; memset(sqe, 0, sizeof(*sqe)); sqe->opcode = IORING_OP_OPENAT; sqe->flags = IOSQE_ASYNC; sqe->fd = AT_FDCWD; sqe->addr = (unsigned long)fuse_file; sqe->open_flags = O_RDONLY; sq_array[0] = 0; __atomic_store_n(sq_tail, 1, __ATOMIC_RELEASE); printf("[*] [Thread B] Submitting async OPENAT request via io_uring_enter...\n"); int res = syscall(__NR_io_uring_enter, ring_fd, 1, 0, 0, NULL, 0); if (res < 0) { printf("[-] [Thread B] Failed io_uring_enter: %s\n", strerror(errno)); syscall(__NR_exit, 1); } printf("[+] [Thread B] io_uring_enter submitted %d request.\n", res); printf("[*] [Thread B] Waiting for main thread to read FUSE request...\n"); while (!__atomic_load_n(&g_req_read, __ATOMIC_ACQUIRE)) usleep(1000); printf("[+] [Thread B] Main thread confirmed request read into userspace.\n"); usleep(100000); printf("[+] [Thread B] Exiting via sys_exit to enter do_exit() and io_wq_exit_workers()...\n"); syscall(__NR_exit, 0); return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for WARNING in io_wq_put_and_exit...\n"); cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); sched_setaffinity(0, sizeof(cpuset), &cpuset); tune_hung_task(); init_msr(); setup_userns(); const char *mp = "/tmp/fuse_mnt"; g_fuse_fd = mount_and_init_fuse(mp); pthread_t th; printf("[*] Spawning submitter thread (Thread B)...\n"); int p_res = pthread_create(&th, NULL, submitter_thread, (void *)mp); if (p_res != 0) { printf("[-] Failed to create submitter thread: %s\n", strerror(p_res)); exit(1); } printf("[+] Submitter thread spawned successfully.\n"); printf("[*] Waiting to read FUSE_LOOKUP request from /dev/fuse...\n"); char buf[65536]; ssize_t n = read(g_fuse_fd, buf, sizeof(buf)); if (n < 0) { printf("[-] Failed to read from /dev/fuse: %s\n", strerror(errno)); exit(1); } printf("[+] Read FUSE request (%zd bytes) into userspace.\n", n); struct fuse_in_header *in_hdr = (struct fuse_in_header *)buf; printf("[+] Request details: opcode=%u unique=%llu nodeid=%llu.\n", in_hdr->opcode, (unsigned long long)in_hdr->unique, (unsigned long long)in_hdr->nodeid); __atomic_store_n(&g_req_read, 1, __ATOMIC_RELEASE); printf("[+] Signaled submitter thread. io-wq worker is trapped in request_wait_answer().\n"); printf("[*] Waiting 1 second for Thread B to enter do_exit() -> io_wq_exit_workers()...\n"); sleep(1); int msr_ok = advance_tsc(400); if (msr_ok > 0) { printf("[+] Advanced TSC on %d CPU(s). Sleeping 10s to let WARN_ON_ONCE trigger and print to dmesg...\n", msr_ok); sleep(10); printf("[+] Sleep completed. Closing FUSE connection to gracefully unblock worker and exit.\n"); close(g_fuse_fd); for (int i = 0; i < g_num_cpus; i++) { if (g_msr_fds[i] >= 0) close(g_msr_fds[i]); } return 0; } printf("[-] MSR adjustment not available on this host. Falling back to hold loop...\n"); for (int sec = 1; sec <= 360; sec++) { sleep(1); if (sec % 10 == 0 || sec <= 5) { printf("[*] FUSE request held open; io_wq_exit_workers waiting... (%d/360s elapsed)\n", sec); } } close(g_fuse_fd); for (int i = 0; i < g_num_cpus; i++) { if (g_msr_fds[i] >= 0) close(g_msr_fds[i]); } return 0; }