// https://syzkaller.appspot.com/bug?id=21332681f7cc0d5f591d904c28c3a5da7355ba09 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MD_MAJOR #define MD_MAJOR 9 #endif #ifndef LOOP_MAJOR #define LOOP_MAJOR 7 #endif #define NUM_HAMMER_THREADS 8 static volatile int stop_threads = 0; static void *hammer_thread(void *arg) { char *path = (char *)arg; char buf[128]; while (!stop_threads) { int fd = open(path, O_RDWR); if (fd < 0) { fd = open(path, O_RDONLY); if (fd < 0) { usleep(50); continue; } } while (!stop_threads) { ssize_t rret = pread(fd, buf, sizeof(buf), 0); (void)rret; ssize_t wret = pwrite(fd, "inactive\n", 9, 0); (void)wret; } close(fd); } return NULL; } static int open_or_create_md(int minor) { char devpath[64]; snprintf(devpath, sizeof(devpath), "/dev/md%d", minor); unlink(devpath); int res = mknod(devpath, S_IFBLK | 0660, makedev(MD_MAJOR, minor)); if (res < 0 && errno != EEXIST) { snprintf(devpath, sizeof(devpath), "./md%d", minor); unlink(devpath); res = mknod(devpath, S_IFBLK | 0660, makedev(MD_MAJOR, minor)); if (res < 0 && errno != EEXIST) { printf("[-] mknod %s failed: %s\n", devpath, strerror(errno)); exit(1); } } printf("[+] mknod %s successful.\n", devpath); int fd = open(devpath, O_RDWR); if (fd < 0) { printf("[-] open %s failed: %s\n", devpath, strerror(errno)); exit(1); } printf("[+] open %s successful (fd=%d).\n", devpath, fd); return fd; } static void run_cycle(int mA, int mB, int mC, int loop_minor, int md_probe_minor) { printf("\n[*] === Starting reproduction cycle: mA=%d, mB=%d, mC=%d, loop_minor=%d, md_probe_minor=%d ===\n", mA, mB, mC, loop_minor, md_probe_minor); /* * Step 1: Device A - Establish &mddev->reconfig_mutex -> major_names_lock * and major_names_lock -> (wq_completion)md_misc. */ int fdA = open_or_create_md(mA); mdu_array_info_t ainfo; memset(&ainfo, 0, sizeof(ainfo)); ainfo.level = 1; ainfo.nr_disks = 2; ainfo.raid_disks = 2; ainfo.major_version = 0; ainfo.minor_version = 90; int res = ioctl(fdA, SET_ARRAY_INFO, &ainfo); if (res < 0) { printf("[-] ioctl SET_ARRAY_INFO failed on md%d: %s\n", mA, strerror(errno)); exit(1); } printf("[+] ioctl SET_ARRAY_INFO successful on md%d.\n", mA); /* Probe unprobed loop minor under &mddev->reconfig_mutex */ mdu_disk_info_t dinfo; memset(&dinfo, 0, sizeof(dinfo)); dinfo.number = 0; dinfo.major = LOOP_MAJOR; dinfo.minor = loop_minor; dinfo.raid_disk = 0; dinfo.state = 0; res = ioctl(fdA, ADD_NEW_DISK, &dinfo); if (res < 0) { printf("[*] ioctl ADD_NEW_DISK (loop minor %d) returned: %s (expected for probe)\n", loop_minor, strerror(errno)); } else { printf("[+] ioctl ADD_NEW_DISK (loop minor %d) successful.\n", loop_minor); } /* Probe unprobed md minor under &mddev->reconfig_mutex */ memset(&dinfo, 0, sizeof(dinfo)); dinfo.number = 1; dinfo.major = MD_MAJOR; dinfo.minor = md_probe_minor; dinfo.raid_disk = 1; dinfo.state = 0; res = ioctl(fdA, ADD_NEW_DISK, &dinfo); if (res < 0) { printf("[*] ioctl ADD_NEW_DISK (md minor %d) returned: %s (expected for probe)\n", md_probe_minor, strerror(errno)); } else { printf("[+] ioctl ADD_NEW_DISK (md minor %d) successful.\n", md_probe_minor); } res = ioctl(fdA, STOP_ARRAY, 0); if (res < 0) { printf("[-] ioctl STOP_ARRAY on md%d failed: %s\n", mA, strerror(errno)); exit(1); } printf("[+] ioctl STOP_ARRAY successful on md%d.\n", mA); res = close(fdA); if (res < 0) { printf("[-] close fdA failed: %s\n", strerror(errno)); exit(1); } printf("[+] close fdA successful.\n"); /* * Step 2: Device B - Open, start active readers/writers on sysfs array_state, * stop array, and close to queue &mddev->del_work onto md_misc_wq. */ int fdB = open_or_create_md(mB); char pathB[128]; snprintf(pathB, sizeof(pathB), "/sys/block/md%d/md/array_state", mB); int foundB = 0; for (int i = 0; i < 50; i++) { if (access(pathB, F_OK) == 0) { foundB = 1; break; } usleep(5000); } if (!foundB) { printf("[-] sysfs node %s did not appear: %s\n", pathB, strerror(errno)); exit(1); } printf("[+] Found sysfs attribute %s.\n", pathB); /* Start hammer threads on Device B's array_state */ stop_threads = 0; pthread_t th[NUM_HAMMER_THREADS]; for (int i = 0; i < NUM_HAMMER_THREADS; i++) { res = pthread_create(&th[i], NULL, hammer_thread, pathB); if (res != 0) { printf("[-] pthread_create failed: %s\n", strerror(res)); exit(1); } } printf("[+] Started %d hammer threads on %s.\n", NUM_HAMMER_THREADS, pathB); /* Stop array on Device B to clear hold_active and clean structures */ res = ioctl(fdB, STOP_ARRAY, 0); if (res < 0) { printf("[-] ioctl STOP_ARRAY on md%d failed: %s\n", mB, strerror(errno)); exit(1); } printf("[+] ioctl STOP_ARRAY successful on md%d.\n", mB); /* * Closing fdB decrements mddev->active to 0 with hold_active==0, raid_disks==0, * queuing &mddev->del_work onto md_misc_wq. */ res = close(fdB); if (res < 0) { printf("[-] close fdB failed: %s\n", strerror(errno)); exit(1); } printf("[+] close fdB successful, del_work queued to md_misc_wq.\n"); /* * Step 3: Device C - Probe unallocated MD device to flush md_misc_wq synchronously. * md_probe() -> md_alloc() -> flush_workqueue(md_misc_wq). * Worker thread runs mddev_delayed_delete() -> kobject_put() -> kernfs_drain(). * Hammer threads keep kn->active held, forcing kernfs_drain() to execute * rwsem_acquire(&kn->dep_map) (Edge #3 -> #4). */ int fdC = open_or_create_md(mC); /* Stop and join hammer threads now that md_misc_wq has flushed */ stop_threads = 1; for (int i = 0; i < NUM_HAMMER_THREADS; i++) { pthread_join(th[i], NULL); } printf("[+] Joined all hammer threads.\n"); /* * Step 4: Write to Device C's array_state to trigger lockdep circular warning. * kernfs_fop_write_iter holds kn->active#77. * array_state_store calls mddev_lock() acquiring &mddev->reconfig_mutex. * Lockdep detects the circular dependency: * &mddev->reconfig_mutex -> major_names_lock -> (wq_completion)md_misc -> * (work_completion)(&mddev->del_work) -> kn->active#77 -> &mddev->reconfig_mutex. */ char pathC[128]; snprintf(pathC, sizeof(pathC), "/sys/block/md%d/md/array_state", mC); int sfd = -1; for (int i = 0; i < 50; i++) { sfd = open(pathC, O_RDWR); if (sfd < 0) sfd = open(pathC, O_WRONLY); if (sfd >= 0) break; usleep(5000); } if (sfd < 0) { printf("[-] open %s failed: %s\n", pathC, strerror(errno)); exit(1); } printf("[+] open %s successful (sfd=%d).\n", pathC, sfd); printf("[*] Writing 'inactive' to %s to trigger lockdep...\n", pathC); ssize_t wres = write(sfd, "inactive\n", 9); if (wres < 0) { printf("[*] write 'inactive' returned: %s\n", strerror(errno)); } else { printf("[+] write 'inactive' successful (bytes=%zd).\n", wres); } printf("[*] Writing 'clean' to %s...\n", pathC); wres = write(sfd, "clean\n", 6); if (wres < 0) { printf("[*] write 'clean' returned: %s\n", strerror(errno)); } else { printf("[+] write 'clean' successful (bytes=%zd).\n", wres); } printf("[*] Writing 'clear' to %s...\n", pathC); wres = write(sfd, "clear\n", 6); if (wres < 0) { printf("[*] write 'clear' returned: %s\n", strerror(errno)); } else { printf("[+] write 'clear' successful (bytes=%zd).\n", wres); } res = close(sfd); if (res < 0) { printf("[-] close sfd failed: %s\n", strerror(errno)); exit(1); } printf("[+] close sfd successful.\n"); res = ioctl(fdC, STOP_ARRAY, 0); if (res < 0) { printf("[*] ioctl STOP_ARRAY on md%d returned: %s\n", mC, strerror(errno)); } else { printf("[+] ioctl STOP_ARRAY successful on md%d.\n", mC); } res = close(fdC); if (res < 0) { printf("[-] close fdC failed: %s\n", strerror(errno)); exit(1); } printf("[+] close fdC successful.\n"); char devA[64], devB[64], devC[64]; snprintf(devA, sizeof(devA), "/dev/md%d", mA); snprintf(devB, sizeof(devB), "/dev/md%d", mB); snprintf(devC, sizeof(devC), "/dev/md%d", mC); unlink(devA); unlink(devB); unlink(devC); printf("[+] Cycle mA=%d, mB=%d, mC=%d complete.\n", mA, mB, mC); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[+] Reproducer for circular locking dependency in array_state_store starting...\n"); run_cycle(10, 11, 12, 20, 21); run_cycle(13, 14, 15, 22, 23); run_cycle(16, 17, 18, 24, 25); run_cycle(19, 20, 21, 26, 27); printf("[+] All cycles finished, waiting for kernel lockdep asynchronous report...\n"); sleep(2); printf("[+] Reproducer completed.\n"); return 0; }