// https://syzkaller.appspot.com/bug?id=a4e9aa5f8d9fdb485234b29e416baddd4683442b #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DM_VERSION_MAJOR #define DM_VERSION_MAJOR 4 #define DM_VERSION_MINOR 0 #define DM_VERSION_PATCHLEVEL 0 #endif static uint32_t dm_version[3] = { DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL }; static unsigned int g_loop_major = 7; static unsigned int g_loop_minor = 0; static void write_debugfs_file(const char *path, const char *str) { int fd = open(path, O_WRONLY); if (fd >= 0) { ssize_t len = (ssize_t)strlen(str); ssize_t res = write(fd, str, len); if (res == len) { printf("[+] Wrote '%s' to %s\n", str, path); } else { printf("[-] Failed writing to %s: %s\n", path, strerror(errno)); } close(fd); } else { printf("[*] Note: unable to open %s: %s\n", path, strerror(errno)); } } static void configure_fault_injection(void) { int res = mount("debugfs", "/sys/kernel/debug", "debugfs", 0, NULL); if (res < 0 && errno != EBUSY) { printf("[*] Note: mount debugfs: %s\n", strerror(errno)); } else { printf("[+] debugfs mounted at /sys/kernel/debug.\n"); } write_debugfs_file("/sys/kernel/debug/failslab/ignore-gfp-wait", "N\n"); write_debugfs_file("/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", "N\n"); write_debugfs_file("/sys/kernel/debug/fail_page_alloc/min-order", "0\n"); write_debugfs_file("/sys/kernel/debug/failslab/verbose", "0\n"); write_debugfs_file("/sys/kernel/debug/fail_page_alloc/verbose", "0\n"); } static int arm_fail_nth(int fn_fd, unsigned int n) { if (fn_fd < 0) return -1; char buf[32]; int len = snprintf(buf, sizeof(buf), "%u\n", n); ssize_t res = pwrite(fn_fd, buf, len, 0); if (res == len) return 0; res = write(fn_fd, buf, len); if (res == len) return 0; return -1; } static unsigned int read_fail_nth(int fn_fd) { if (fn_fd < 0) return 0; char buf[32] = {0}; ssize_t s = pread(fn_fd, buf, sizeof(buf) - 1, 0); if (s <= 0) { int rfd = open("/proc/self/fail-nth", O_RDONLY); if (rfd >= 0) { s = read(rfd, buf, sizeof(buf) - 1); close(rfd); } } if (s <= 0) return 0; return (unsigned int)strtoul(buf, NULL, 10); } static int remove_dm_device(int dm_fd, const char *dev_name) { char buf[sizeof(struct dm_ioctl) + 128]; memset(buf, 0, sizeof(buf)); struct dm_ioctl *d = (struct dm_ioctl *)buf; d->version[0] = dm_version[0]; d->version[1] = dm_version[1]; d->version[2] = dm_version[2]; d->data_size = sizeof(buf); d->data_start = sizeof(struct dm_ioctl); strncpy(d->name, dev_name, sizeof(d->name) - 1); return ioctl(dm_fd, DM_DEV_REMOVE, d); } static int create_dm_device(int dm_fd, const char *dev_name) { char buf[sizeof(struct dm_ioctl) + 128]; memset(buf, 0, sizeof(buf)); struct dm_ioctl *d = (struct dm_ioctl *)buf; d->version[0] = dm_version[0]; d->version[1] = dm_version[1]; d->version[2] = dm_version[2]; d->data_size = sizeof(buf); d->data_start = sizeof(struct dm_ioctl); strncpy(d->name, dev_name, sizeof(d->name) - 1); int res = ioctl(dm_fd, DM_DEV_CREATE, d); if (res < 0) { remove_dm_device(dm_fd, dev_name); res = ioctl(dm_fd, DM_DEV_CREATE, d); } return res; } static void init_load_param(char *buf, size_t buf_len, const char *dev_name, unsigned int maj, unsigned int min) { memset(buf, 0, buf_len); struct dm_ioctl *d = (struct dm_ioctl *)buf; d->version[0] = dm_version[0]; d->version[1] = dm_version[1]; d->version[2] = dm_version[2]; d->data_start = sizeof(struct dm_ioctl); d->target_count = 1; strncpy(d->name, dev_name, sizeof(d->name) - 1); struct dm_target_spec *tgt = (struct dm_target_spec *)(buf + sizeof(struct dm_ioctl)); tgt->sector_start = 0; tgt->length = 2048; tgt->status = 0; strncpy(tgt->target_type, "multipath", sizeof(tgt->target_type) - 1); char *params = (char *)(tgt + 1); snprintf(params, buf_len - sizeof(struct dm_ioctl) - sizeof(struct dm_target_spec), "0 0 1 1 round-robin 0 1 1 %u:%u 1", maj, min); size_t param_len = strlen(params) + 1; tgt->next = (sizeof(struct dm_target_spec) + param_len + 7) & ~7; d->data_size = sizeof(struct dm_ioctl) + tgt->next; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for blk_mq_exit_hctx null-ptr-deref...\n"); prctl(PR_SET_NAME, "syz.5.600"); cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(0, &cpuset); sched_setaffinity(0, sizeof(cpuset), &cpuset); configure_fault_injection(); int dm_fd = open("/dev/mapper/control", O_RDWR); if (dm_fd < 0) { printf("[-] Failed to open /dev/mapper/control: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/mapper/control successful.\n"); struct dm_ioctl ver_d; memset(&ver_d, 0, sizeof(ver_d)); ver_d.version[0] = DM_VERSION_MAJOR; ver_d.version[1] = DM_VERSION_MINOR; ver_d.version[2] = DM_VERSION_PATCHLEVEL; ver_d.data_size = sizeof(ver_d); ver_d.data_start = sizeof(ver_d); int res = ioctl(dm_fd, DM_VERSION, &ver_d); if (res < 0) { printf("[-] Failed to ioctl DM_VERSION: %s\n", strerror(errno)); exit(1); } dm_version[0] = ver_d.version[0]; dm_version[1] = ver_d.version[1]; dm_version[2] = ver_d.version[2]; printf("[+] ioctl DM_VERSION successful: %u.%u.%u\n", dm_version[0], dm_version[1], dm_version[2]); int loop_ctrl_fd = open("/dev/loop-control", O_RDWR); if (loop_ctrl_fd < 0) { printf("[-] Failed to open /dev/loop-control: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/loop-control successful.\n"); res = ioctl(loop_ctrl_fd, LOOP_CTL_ADD, 0); if (res < 0 && errno != EEXIST) { printf("[-] Failed to ioctl LOOP_CTL_ADD 0: %s\n", strerror(errno)); exit(1); } printf("[+] ioctl LOOP_CTL_ADD 0 successful.\n"); struct stat st; if (stat("/dev/loop0", &st) < 0) { res = mknod("/dev/loop0", S_IFBLK | 0660, makedev(7, 0)); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mknod /dev/loop0: %s\n", strerror(errno)); exit(1); } if (stat("/dev/loop0", &st) < 0) { printf("[-] Failed to stat /dev/loop0: %s\n", strerror(errno)); exit(1); } } g_loop_major = major(st.st_rdev); g_loop_minor = minor(st.st_rdev); printf("[+] Backing device /dev/loop0 ready (%u:%u).\n", g_loop_major, g_loop_minor); const char *img_path = "/tmp/test.img"; int img_fd = open(img_path, O_RDWR | O_CREAT | O_TRUNC, 0666); if (img_fd < 0) { printf("[-] Failed to open %s: %s\n", img_path, strerror(errno)); exit(1); } printf("[+] open %s successful.\n", img_path); res = ftruncate(img_fd, 16 * 1024 * 1024); if (res < 0) { printf("[-] Failed to ftruncate %s: %s\n", img_path, strerror(errno)); exit(1); } printf("[+] ftruncate backing image to 16MB successful.\n"); int loop_fd = open("/dev/loop0", O_RDWR); if (loop_fd < 0) { printf("[-] Failed to open /dev/loop0: %s\n", strerror(errno)); exit(1); } printf("[+] open /dev/loop0 successful.\n"); ioctl(loop_fd, LOOP_CLR_FD, 0); res = ioctl(loop_fd, LOOP_SET_FD, img_fd); if (res < 0) { printf("[-] Failed to ioctl LOOP_SET_FD: %s\n", strerror(errno)); exit(1); } printf("[+] Bound /tmp/test.img to /dev/loop0 successfully.\n"); int fn_fd = open("/proc/self/fail-nth", O_RDWR); if (fn_fd < 0) { char tid_path[64]; snprintf(tid_path, sizeof(tid_path), "/proc/self/task/%ld/fail-nth", syscall(SYS_gettid)); fn_fd = open(tid_path, O_RDWR); } if (fn_fd < 0) { fn_fd = open("/proc/thread-self/fail-nth", O_RDWR); } if (fn_fd < 0) { printf("[-] Failed to open fail-nth: %s\n", strerror(errno)); exit(1); } printf("[+] open /proc/self/fail-nth successful (fd=%d).\n", fn_fd); res = arm_fail_nth(fn_fd, 12345); if (res < 0) { printf("[-] Failed to test arm fail-nth: %s\n", strerror(errno)); exit(1); } unsigned int test_val = read_fail_nth(fn_fd); printf("[+] Tested fail-nth mechanism: armed 12345, read back %u.\n", test_val); arm_fail_nth(fn_fd, 0); const char *dm_name = "test-dm"; static char table_buf[4096]; remove_dm_device(dm_fd, dm_name); res = create_dm_device(dm_fd, dm_name); if (res < 0) { printf("[-] Failed to create DM device for probe: %s\n", strerror(errno)); exit(1); } printf("[+] Created DM device for probe.\n"); res = arm_fail_nth(fn_fd, 100000); if (res < 0) { printf("[-] Failed to arm fail-nth for probe: %s\n", strerror(errno)); exit(1); } init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); int probe_res = ioctl(dm_fd, DM_TABLE_LOAD, table_buf); unsigned int rem = read_fail_nth(fn_fd); arm_fail_nth(fn_fd, 0); unsigned int total_allocs = 0; if (probe_res >= 0 && rem > 0 && rem < 100000) { total_allocs = 100000 - rem; printf("[+] Probed DM_TABLE_LOAD: exactly %u allocations occurred (probe_res=%d, rem=%u).\n", total_allocs, probe_res, rem); } else { printf("[*] Probed DM_TABLE_LOAD: res=%d (%s), rem=%u\n", probe_res, probe_res < 0 ? strerror(errno) : "success", rem); if (rem > 0 && rem < 100000) total_allocs = 100000 - rem; } remove_dm_device(dm_fd, dm_name); unsigned int primary_start, primary_end; if (total_allocs > 0) { primary_start = total_allocs + 10; primary_end = (total_allocs > 100) ? (total_allocs - 100) : 1; printf("[*] Phase 1: Probed targeted sweep from N=%u down to %u...\n", primary_start, primary_end); } else { primary_start = 650; primary_end = 550; printf("[*] Phase 1: Default syz.5.600 sweep from N=%u down to %u...\n", primary_start, primary_end); } for (unsigned int n = primary_start; n >= primary_end; n--) { if (n % 10 == 0 || n == primary_start) printf("[*] Testing fail-nth = %u...\n", n); create_dm_device(dm_fd, dm_name); arm_fail_nth(fn_fd, n); init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); ioctl(dm_fd, DM_TABLE_LOAD, table_buf); arm_fail_nth(fn_fd, 0); init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); ioctl(dm_fd, DM_TABLE_LOAD, table_buf); init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); ioctl(dm_fd, DM_TABLE_LOAD, table_buf); remove_dm_device(dm_fd, dm_name); } printf("[*] Phase 2: Broader sweep...\n"); unsigned int sweep_max = (total_allocs > 0 && total_allocs + 50 > 1000) ? total_allocs + 50 : 1000; for (unsigned int n = sweep_max; n >= 1; n--) { if (n <= primary_start && n >= primary_end) continue; if (n % 50 == 0) printf("[*] Broader sweep testing fail-nth = %u...\n", n); create_dm_device(dm_fd, dm_name); arm_fail_nth(fn_fd, n); init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); ioctl(dm_fd, DM_TABLE_LOAD, table_buf); arm_fail_nth(fn_fd, 0); init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); ioctl(dm_fd, DM_TABLE_LOAD, table_buf); init_load_param(table_buf, sizeof(table_buf), dm_name, g_loop_major, g_loop_minor); ioctl(dm_fd, DM_TABLE_LOAD, table_buf); remove_dm_device(dm_fd, dm_name); } remove_dm_device(dm_fd, dm_name); close(loop_fd); close(img_fd); unlink(img_path); close(loop_ctrl_fd); close(dm_fd); close(fn_fd); printf("[+] Program completed without triggering crash.\n"); return 0; }