// https://syzkaller.appspot.com/bug?id=416a58dce9f16ea8e68f9f58cb06bc0f4869ada8 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MD_MAJOR 9 #define NUM_DEVS 16 static volatile int arm_listener = 0; static volatile int listener_ready = 0; static void write_sysfs(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); } ssize_t len = (ssize_t)strlen(val); ssize_t written = write(fd, val, len); if (written < 0) { printf("[-] Failed to write '%s' to %s: %s\n", val, path, strerror(errno)); close(fd); exit(1); } printf("[+] Successfully wrote '%s' to %s.\n", val, path); int res = close(fd); if (res < 0) { printf("[-] Failed to close %s: %s\n", path, strerror(errno)); exit(1); } } static void disable_rt_throttling(void) { int fd = open("/proc/sys/kernel/sched_rt_runtime_us", O_WRONLY); if (fd < 0) { printf("[-] Failed to open /proc/sys/kernel/sched_rt_runtime_us: %s\n", strerror(errno)); exit(1); } ssize_t written = write(fd, "-1", 2); if (written < 0) { printf("[*] Writing -1 to sched_rt_runtime_us failed: %s, falling back to 1000000...\n", strerror(errno)); written = write(fd, "1000000", 7); if (written < 0) { printf("[-] Failed to write 1000000 to sched_rt_runtime_us: %s\n", strerror(errno)); close(fd); exit(1); } printf("[+] Successfully set sched_rt_runtime_us to 1000000.\n"); } else { printf("[+] Successfully set sched_rt_runtime_us to -1 (disabled RT throttling).\n"); } int res = close(fd); if (res < 0) { printf("[-] Failed to close /proc/sys/kernel/sched_rt_runtime_us: %s\n", strerror(errno)); exit(1); } } static void pin_to_cpu(int cpu) { cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(cpu, &mask); int res = sched_setaffinity(0, sizeof(mask), &mask); if (res < 0) { printf("[-] Failed to sched_setaffinity to CPU %d: %s\n", cpu, strerror(errno)); exit(1); } printf("[+] Successfully pinned thread to CPU %d.\n", cpu); } static void setup_md_node(int minor_num) { char dev_path[32]; snprintf(dev_path, sizeof(dev_path), "/dev/md%d", minor_num); struct stat st; if (stat(dev_path, &st) == 0) { if (S_ISBLK(st.st_mode) && major(st.st_rdev) == MD_MAJOR && minor(st.st_rdev) == (unsigned int)minor_num) { printf("[+] Device node %s already exists and has correct major/minor.\n", dev_path); return; } int res = unlink(dev_path); if (res < 0) { printf("[-] Failed to unlink mismatched %s: %s\n", dev_path, strerror(errno)); exit(1); } } int res = mknod(dev_path, S_IFBLK | 0660, makedev(MD_MAJOR, minor_num)); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mknod %s: %s\n", dev_path, strerror(errno)); exit(1); } printf("[+] Device node %s ready.\n", dev_path); } /* Dedicated RT listener thread pinned to CPU 0 */ static void *thr_uevent_listener(void *arg) { (void)arg; pin_to_cpu(0); /* 1. Create NETLINK_KOBJECT_UEVENT socket */ int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); if (sock < 0) { printf("[-] Failed to create NETLINK_KOBJECT_UEVENT socket: %s\n", strerror(errno)); exit(1); } printf("[+] Created NETLINK_KOBJECT_UEVENT socket.\n"); struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = 1; int res = bind(sock, (struct sockaddr *)&sa, sizeof(sa)); if (res < 0) { printf("[-] Failed to bind NETLINK_KOBJECT_UEVENT socket: %s\n", strerror(errno)); close(sock); exit(1); } printf("[+] Bound NETLINK_KOBJECT_UEVENT socket to group 1.\n"); /* 2. Wait for main thread to prepare devices and signal arming */ while (!arm_listener) { usleep(1000); } /* 3. Drain any stale uevents */ char drain_buf[4096]; while (recv(sock, drain_buf, sizeof(drain_buf), MSG_DONTWAIT) > 0) { /* drain */ } /* 4. Elevate to SCHED_FIFO priority 99 */ struct sched_param sp; memset(&sp, 0, sizeof(sp)); sp.sched_priority = 99; res = sched_setscheduler(0, SCHED_FIFO, &sp); if (res < 0) { printf("[-] Failed to sched_setscheduler to SCHED_FIFO 99: %s\n", strerror(errno)); close(sock); exit(1); } printf("[+] Elevated listener thread to SCHED_FIFO priority 99.\n"); /* 5. Signal readiness to main thread */ listener_ready = 1; /* 6. Block on recv(). Releases CPU 0 until uevent arrives */ char buf[4096]; while (1) { ssize_t n = recv(sock, buf, sizeof(buf) - 1, 0); if (n <= 0) { continue; } buf[n] = '\0'; if (strstr(buf, "remove") || strstr(buf, "REMOVE") || strstr(buf, "md") || strstr(buf, "block") || strstr(buf, "bdi")) { printf("[+] Listener woken by uevent: %.48s\n", buf); break; } } /* 7. kworker is now preempted in mddev_delayed_delete. Monopolize CPU 0 for 35s */ printf("[+] Monopolizing CPU 0 for 35 seconds to trigger workqueue stall...\n"); struct timespec start, now; clock_gettime(CLOCK_MONOTONIC, &start); while (1) { clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec - start.tv_sec >= 35) { break; } __asm__ __volatile__("" ::: "memory"); } printf("[+] 35-second spin loop finished.\n"); close(sock); return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Starting reproducer for BUG: workqueue lockup in mddev_delayed_delete...\n"); /* 1. Configure module parameters and RT throttling */ write_sysfs("/sys/module/md_mod/parameters/legacy_async_del_gendisk", "1"); write_sysfs("/sys/module/md_mod/parameters/create_on_open", "1"); write_sysfs("/sys/module/workqueue/parameters/watchdog_thresh", "3"); disable_rt_throttling(); /* 2. Setup block device nodes */ for (int i = 0; i < NUM_DEVS; i++) { setup_md_node(i); } /* 3. Pin main thread to CPU 0 */ pin_to_cpu(0); /* 4. Open devices and stop them to prepare hold_active = 0 and prime deletion */ int fds[NUM_DEVS]; for (int i = 0; i < NUM_DEVS; i++) { char dev_path[32]; snprintf(dev_path, sizeof(dev_path), "/dev/md%d", i); fds[i] = open(dev_path, O_RDONLY | O_NONBLOCK); if (fds[i] < 0) { printf("[-] Failed to open %s: %s\n", dev_path, strerror(errno)); exit(1); } printf("[+] Opened %s (fd=%d).\n", dev_path, fds[i]); int res = ioctl(fds[i], STOP_ARRAY, 0); if (res < 0) { printf("[*] ioctl STOP_ARRAY on %s returned %d (%s).\n", dev_path, res, strerror(errno)); } else { printf("[+] ioctl STOP_ARRAY on %s succeeded.\n", dev_path); } } /* 5. Spawn listener thread on CPU 0 */ pthread_t th; int res = pthread_create(&th, NULL, thr_uevent_listener, NULL); if (res != 0) { printf("[-] Failed to create listener thread: %s\n", strerror(res)); exit(1); } printf("[+] Created listener thread.\n"); /* 6. Arm listener and wait for it to enter recv() at SCHED_FIFO 99 */ arm_listener = 1; while (!listener_ready) { usleep(1000); } usleep(50000); /* Ensure listener is blocked in sys_recv */ /* 7. Temporarily elevate main thread to SCHED_FIFO 50 so that closing fds cannot be preempted by CFS kworker */ struct sched_param sp_main; memset(&sp_main, 0, sizeof(sp_main)); sp_main.sched_priority = 50; res = sched_setscheduler(0, SCHED_FIFO, &sp_main); if (res < 0) { printf("[*] sched_setscheduler for main thread failed: %s (non-fatal, continuing)\n", strerror(errno)); } else { printf("[+] Elevated main thread to SCHED_FIFO 50.\n"); } /* 8. Close all devices in rapid succession to queue 16 works into pool 2 */ printf("[+] Closing %d devices to queue deletion works into pool 2...\n", NUM_DEVS); for (int i = 0; i < NUM_DEVS; i++) { res = close(fds[i]); if (res < 0) { printf("[-] Failed to close fd %d: %s\n", fds[i], strerror(errno)); exit(1); } } printf("[+] All %d devices closed.\n", NUM_DEVS); /* 9. Sleep main thread to yield CPU 0 entirely to kworker, which gets preempted by listener */ printf("[+] Main thread sleeping for 40 seconds to allow watchdog trigger...\n"); sleep(40); /* 10. Clean up */ res = pthread_join(th, NULL); if (res != 0) { printf("[-] Failed to join listener thread: %s\n", strerror(res)); } else { printf("[+] Joined listener thread.\n"); } printf("[+] Reproducer finished.\n"); return 0; }