// https://syzkaller.appspot.com/bug?id=170201af6bce054a8b52b7685e02d8af908286d5 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NUM_WORKERS 2 #define MAX_ATTEMPTS 4000 #define SWEEP_START_USEC 50 #define SWEEP_END_USEC 3500 #define SWEEP_STEP_USEC 25 static char loop_dev_path[NUM_WORKERS][3][64]; static char backing_path[NUM_WORKERS][3][64]; static pid_t worker_pids[NUM_WORKERS]; static int worker_usec[NUM_WORKERS]; static volatile int start_flag = 0; static volatile int stop_flag = 0; static void *killer_thread(void *arg) { long usec = (long)arg; while (!start_flag) { if (stop_flag) return NULL; sched_yield(); } struct timespec t0, t1; clock_gettime(CLOCK_MONOTONIC, &t0); long target_ns = usec * 1000L; while (!stop_flag) { clock_gettime(CLOCK_MONOTONIC, &t1); long diff_ns = (t1.tv_sec - t0.tv_sec) * 1000000000L + (t1.tv_nsec - t0.tv_nsec); if (diff_ns >= target_ns) { kill(getpid(), SIGKILL); break; } } return NULL; } static void run_worker(int w, int usec) { // 1. Zero the first 64KB of the metadata loop device to guarantee // __superblock_all_zeroes() succeeds and format_metadata() is invoked. int mfd = open(loop_dev_path[w][0], O_RDWR | O_SYNC); if (mfd >= 0) { static char zeroes[65536] = {0}; pwrite(mfd, zeroes, sizeof(zeroes), 0); fsync(mfd); ioctl(mfd, BLKFLSBUF, 0); close(mfd); } int dm_fd = open("/dev/mapper/control", O_RDWR); if (dm_fd < 0) { _exit(1); } char dev_name[64]; snprintf(dev_name, sizeof(dev_name), "test_cache_%d", w); // 2. Remove previously existing DM device if any struct dm_ioctl dmi_rem; memset(&dmi_rem, 0, sizeof(dmi_rem)); dmi_rem.version[0] = DM_VERSION_MAJOR; dmi_rem.version[1] = 0; dmi_rem.version[2] = 0; dmi_rem.data_size = sizeof(dmi_rem); dmi_rem.data_start = sizeof(dmi_rem); strncpy(dmi_rem.name, dev_name, sizeof(dmi_rem.name)); ioctl(dm_fd, DM_DEV_REMOVE, &dmi_rem); // 3. Create DM device struct dm_ioctl dmi_cr; memset(&dmi_cr, 0, sizeof(dmi_cr)); dmi_cr.version[0] = DM_VERSION_MAJOR; dmi_cr.version[1] = 0; dmi_cr.version[2] = 0; dmi_cr.data_size = sizeof(dmi_cr); dmi_cr.data_start = sizeof(dmi_cr); strncpy(dmi_cr.name, dev_name, sizeof(dmi_cr.name)); int res = ioctl(dm_fd, DM_DEV_CREATE, &dmi_cr); if (res < 0) { close(dm_fd); _exit(1); } // 4. Prepare DM_TABLE_LOAD request struct { struct dm_ioctl dmi; struct dm_target_spec spec; char params[256]; } req; memset(&req, 0, sizeof(req)); req.dmi.version[0] = DM_VERSION_MAJOR; req.dmi.version[1] = 0; req.dmi.version[2] = 0; req.dmi.data_size = sizeof(req); req.dmi.data_start = sizeof(struct dm_ioctl); req.dmi.target_count = 1; strncpy(req.dmi.name, dev_name, sizeof(req.dmi.name)); req.spec.sector_start = 0; req.spec.length = 131072; // 64 MB (131072 sectors) strncpy(req.spec.target_type, "cache", sizeof(req.spec.target_type)); snprintf(req.params, sizeof(req.params), "%s %s %s 128 0 default 0", loop_dev_path[w][0], loop_dev_path[w][1], loop_dev_path[w][2]); req.spec.next = sizeof(req.spec) + strlen(req.params) + 1; // 5. Setup high precision timers and signals prctl(PR_SET_TIMERSLACK, 1UL); start_flag = 0; stop_flag = 0; pthread_t th; pthread_create(&th, NULL, killer_thread, (void *)(long)usec); struct itimerval itv; memset(&itv, 0, sizeof(itv)); itv.it_value.tv_sec = usec / 1000000; itv.it_value.tv_usec = usec % 1000000; // Default action for SIGALRM is SIG_DFL (fatal), ensuring complete_signal() // marks sig_fatal() and adds SIGKILL to pending signals to interrupt wait_for_completion_killable. start_flag = 1; setitimer(ITIMER_REAL, &itv, NULL); // 6. Execute DM_TABLE_LOAD ioctl(dm_fd, DM_TABLE_LOAD, &req); // 7. Cleanup if syscall finished before timer/killer interrupted stop_flag = 1; struct itimerval zero_itv = {0}; setitimer(ITIMER_REAL, &zero_itv, NULL); pthread_join(th, NULL); ioctl(dm_fd, DM_DEV_REMOVE, &dmi_rem); close(dm_fd); _exit(0); } static void spawn_worker(int w) { pid_t pid = fork(); if (pid < 0) { printf("[-] Failed to fork worker %d: %s\n", w, strerror(errno)); exit(1); } if (pid == 0) { run_worker(w, worker_usec[w]); } worker_pids[w] = pid; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting dm_kcopyd_client_destroy null-ptr-deref reproducer...\n"); printf("[*] Opening /dev/loop-control...\n"); int loop_ctl_fd = open("/dev/loop-control", O_RDWR); if (loop_ctl_fd < 0) { printf("[-] Failed to open /dev/loop-control: %s\n", strerror(errno)); exit(1); } printf("[+] Open /dev/loop-control successful.\n"); printf("[*] Opening /dev/mapper/control...\n"); 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"); // Sizes: metadata (32 MB), cache (64 MB), origin (64 MB) off_t sizes[3] = { 32 * 1024 * 1024ULL, 64 * 1024 * 1024ULL, 64 * 1024 * 1024ULL }; printf("[*] Preparing backing files and loop devices for %d workers...\n", NUM_WORKERS); for (int w = 0; w < NUM_WORKERS; w++) { for (int t = 0; t < 3; t++) { snprintf(backing_path[w][t], sizeof(backing_path[w][t]), "/tmp/backing_%d_%d.img", w, t); int img_fd = open(backing_path[w][t], O_RDWR | O_CREAT | O_TRUNC, 0660); if (img_fd < 0) { printf("[-] Failed to create backing file %s: %s\n", backing_path[w][t], strerror(errno)); exit(1); } int res = ftruncate(img_fd, sizes[t]); if (res < 0) { printf("[-] Failed to ftruncate %s: %s\n", backing_path[w][t], strerror(errno)); exit(1); } if (t == 0) { static char zeroes[65536] = {0}; res = pwrite(img_fd, zeroes, sizeof(zeroes), 0); if (res < 0) { printf("[-] Failed to write zeroes to %s: %s\n", backing_path[w][t], strerror(errno)); exit(1); } } res = fsync(img_fd); if (res < 0) { printf("[-] Failed to fsync %s: %s\n", backing_path[w][t], strerror(errno)); exit(1); } int loop_num = ioctl(loop_ctl_fd, LOOP_CTL_GET_FREE); if (loop_num < 0) { printf("[-] Failed to allocate free loop device: %s\n", strerror(errno)); exit(1); } snprintf(loop_dev_path[w][t], sizeof(loop_dev_path[w][t]), "/dev/loop%d", loop_num); int lfd = open(loop_dev_path[w][t], O_RDWR); if (lfd < 0) { printf("[-] Failed to open loop device %s: %s\n", loop_dev_path[w][t], strerror(errno)); exit(1); } res = ioctl(lfd, LOOP_SET_FD, img_fd); if (res < 0) { printf("[-] Failed to attach %s to %s: %s\n", backing_path[w][t], loop_dev_path[w][t], strerror(errno)); exit(1); } close(lfd); close(img_fd); printf("[+] Attached %s -> %s successfully.\n", backing_path[w][t], loop_dev_path[w][t]); } worker_usec[w] = SWEEP_START_USEC + w * 25; } close(loop_ctl_fd); close(dm_fd); printf("[+] Loop devices configured. Spawning initial workers...\n"); for (int w = 0; w < NUM_WORKERS; w++) { spawn_worker(w); } printf("[*] Monitoring workers and sweeping interrupt delays (%d us - %d us)...\n", SWEEP_START_USEC, SWEEP_END_USEC); int total_attempts = 0; while (total_attempts < MAX_ATTEMPTS) { int status; pid_t dead_pid = waitpid(-1, &status, 0); if (dead_pid < 0) { if (errno == EINTR) continue; printf("[-] waitpid error: %s\n", strerror(errno)); break; } int slot = -1; for (int w = 0; w < NUM_WORKERS; w++) { if (worker_pids[w] == dead_pid) { slot = w; break; } } if (slot >= 0) { worker_usec[slot] += SWEEP_STEP_USEC; if (worker_usec[slot] > SWEEP_END_USEC) worker_usec[slot] = SWEEP_START_USEC; spawn_worker(slot); total_attempts++; if (total_attempts % 200 == 0) { printf("[*] Completed %d attempts (slot %d next usec: %d)...\n", total_attempts, slot, worker_usec[slot]); } } } printf("[*] Waiting for active workers to complete...\n"); for (int w = 0; w < NUM_WORKERS; w++) { int status; waitpid(worker_pids[w], &status, 0); } printf("[*] Cleaning up loop devices...\n"); for (int w = 0; w < NUM_WORKERS; w++) { for (int t = 0; t < 3; t++) { int lfd = open(loop_dev_path[w][t], O_RDWR); if (lfd >= 0) { ioctl(lfd, LOOP_CLR_FD, 0); close(lfd); } unlink(backing_path[w][t]); } } printf("[+] Reproducer finished.\n"); return 0; }