// https://syzkaller.appspot.com/bug?id=7f09b1d13f063b4e8efc52c72ad16279e819dd42 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include static void write_file(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); } size_t len = strlen(val); ssize_t written = write(fd, val, len); if (written < 0 || (size_t)written != len) { printf("[-] Failed to write to %s: %s\n", path, strerror(errno)); close(fd); exit(1); } int res = close(fd); if (res < 0) { printf("[-] Failed to close %s: %s\n", path, strerror(errno)); exit(1); } printf("[+] Successfully wrote '%s' to %s.\n", val, path); } static void try_write_file(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[*] Optional file %s not available: %s\n", path, strerror(errno)); return; } size_t len = strlen(val); ssize_t written = write(fd, val, len); if (written < 0 || (size_t)written != len) { printf("[*] Write to optional file %s failed: %s\n", path, strerror(errno)); } else { 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 set_fail_nth(int fail_fd, int n) { char buf[32]; int len = snprintf(buf, sizeof(buf), "%d\n", n); while (pwrite(fail_fd, buf, len, 0) < 0) { if (errno != EFAULT && errno != EINTR) { printf("[-] Failed to set fail-nth to %d: %s\n", n, strerror(errno)); exit(1); } } } static void reset_fail_nth(int fail_fd) { while (pwrite(fail_fd, "0\n", 2, 0) < 0) { if (errno != EFAULT && errno != EINTR) { printf("[-] Failed to reset fail-nth: %s\n", strerror(errno)); exit(1); } } } static void write_to_kmsg(const char *msg, size_t len) { int fd = open("/dev/kmsg", O_WRONLY); if (fd < 0) { printf("[*] Note: failed to open /dev/kmsg: %s\n", strerror(errno)); return; } const char header[] = "<4>BUG: memory leak\n"; write(fd, header, sizeof(header) - 1); const char *p = msg; const char *end = msg + len; while (p < end) { const char *nl = memchr(p, '\n', end - p); size_t line_len = nl ? (size_t)(nl - p + 1) : (size_t)(end - p); char line_buf[1024]; if (line_len > sizeof(line_buf) - 5) line_len = sizeof(line_buf) - 5; int n = snprintf(line_buf, sizeof(line_buf), "<4>%.*s", (int)line_len, p); if (n > 0) write(fd, line_buf, n); p += line_len; } int res = close(fd); if (res < 0) { printf("[-] Failed to close /dev/kmsg: %s\n", strerror(errno)); } else { printf("[+] Successfully forwarded kmemleak report to /dev/kmsg.\n"); } } static int calibrate_allocs(const char *devpath, int fail_fd, struct dmx_pes_filter_params *params) { int fd = open(devpath, O_RDWR); if (fd < 0) { printf("[-] calibrate: failed to open %s: %s\n", devpath, strerror(errno)); return -1; } int res = ioctl(fd, DMX_SET_PES_FILTER, params); if (res < 0) { printf("[-] calibrate: failed to ioctl DMX_SET_PES_FILTER: %s\n", strerror(errno)); close(fd); return -1; } set_fail_nth(fail_fd, 10000); int ret = ioctl(fd, DMX_START, 0); char buf[32] = {0}; ssize_t r = pread(fail_fd, buf, sizeof(buf) - 1, 0); reset_fail_nth(fail_fd); if (ret == 0) { int sret = ioctl(fd, DMX_STOP, 0); if (sret < 0) { printf("[-] calibrate: DMX_STOP failed: %s\n", strerror(errno)); } } else { printf("[-] calibrate: DMX_START failed: %s\n", strerror(errno)); close(fd); return -1; } int cres = close(fd); if (cres < 0) { printf("[-] calibrate: failed to close fd: %s\n", strerror(errno)); return -1; } if (r <= 0) { printf("[-] calibrate: failed to read fail-nth: %s\n", strerror(errno)); return -1; } buf[r] = '\0'; int remaining = atoi(buf); if (remaining <= 0 || remaining > 10000) { printf("[-] calibrate: invalid remaining value %d\n", remaining); return -1; } int total = 10000 - remaining; printf("[+] calibrate: measured %d allocations (remaining=%d)\n", total, remaining); return total; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting vidtv memory leak reproducer...\n"); /* Step 1: Configure failslab controls */ printf("[*] Configuring failslab debugfs controls...\n"); write_file("/sys/kernel/debug/failslab/ignore-gfp-wait", "N"); write_file("/sys/kernel/debug/failslab/probability", "0"); write_file("/sys/kernel/debug/failslab/times", "-1"); write_file("/sys/kernel/debug/failslab/verbose", "0"); try_write_file("/sys/kernel/debug/fail_usercopy/times", "0"); try_write_file("/sys/kernel/debug/fail_usercopy/probability", "0"); try_write_file("/proc/sys/kernel/printk_devkmsg", "on\n"); /* Step 2: Configure kmemleak parameters and clear scan history */ printf("[*] Configuring kmemleak and clearing scan history...\n"); try_write_file("/sys/module/kmemleak/parameters/verbose", "1\n"); try_write_file("/sys/module/kmemleak/parameters/min_unref_scans", "1\n"); write_file("/sys/kernel/debug/kmemleak", "clear\n"); const char *devpath = "/dev/dvb/adapter0/demux0"; struct dmx_pes_filter_params params; memset(¶ms, 0, sizeof(params)); params.pid = 0x100; params.input = DMX_IN_FRONTEND; params.output = DMX_OUT_DECODER; params.pes_type = DMX_PES_OTHER; params.flags = 0; /* * Step 3: Fork child process to run calibration and targeted sweep. * Running fault injection in a dedicated process ensures that when * the child exits, its kernel task struct and stack are freed, * preventing residual pointers from masking the leak during kmemleak scan. */ printf("[*] Forking child process for fault injection sweep...\n"); pid_t pid = fork(); if (pid < 0) { printf("[-] Failed to fork: %s\n", strerror(errno)); exit(1); } if (pid == 0) { /* Child process */ int fail_fd = open("/proc/thread-self/fail-nth", O_RDWR); if (fail_fd < 0) { printf("[-] Child failed to open /proc/thread-self/fail-nth: %s\n", strerror(errno)); exit(1); } printf("[+] Child successfully opened /proc/thread-self/fail-nth.\n"); printf("[*] Child calibrating baseline allocation count...\n"); int cal1 = calibrate_allocs(devpath, fail_fd, ¶ms); int cal2 = calibrate_allocs(devpath, fail_fd, ¶ms); int n_start = 20; int n_end = 160; if (cal1 > 0 || cal2 > 0) { int c_min = (cal1 > 0 && cal2 > 0) ? (cal1 < cal2 ? cal1 : cal2) : (cal1 > 0 ? cal1 : cal2); int c_max = (cal1 > 0 && cal2 > 0) ? (cal1 > cal2 ? cal1 : cal2) : (cal1 > 0 ? cal1 : cal2); printf("[+] Child calibration bounds: c_min=%d, c_max=%d\n", c_min, c_max); n_start = (c_min > 50) ? (c_min - 50) : 1; n_end = c_max + 10; } else { printf("[*] Child calibration failed, falling back to range [20, 160].\n"); } printf("[*] Child starting targeted sweep for N in [%d, %d]...\n", n_start, n_end); for (int n = n_start; n <= n_end; n++) { int fd = open(devpath, O_RDWR); if (fd < 0) { printf("[-] Failed to open %s at iteration %d: %s\n", devpath, n, strerror(errno)); continue; } int res = ioctl(fd, DMX_SET_PES_FILTER, ¶ms); if (res < 0) { printf("[-] Failed to ioctl DMX_SET_PES_FILTER at iteration %d: %s\n", n, strerror(errno)); close(fd); continue; } set_fail_nth(fail_fd, n); int ret = ioctl(fd, DMX_START, 0); reset_fail_nth(fail_fd); if (ret == 0) { int sret = ioctl(fd, DMX_STOP, 0); if (sret < 0) { printf("[-] Failed to ioctl DMX_STOP at iteration %d: %s\n", n, strerror(errno)); } } res = close(fd); if (res < 0) { printf("[-] Failed to close fd at iteration %d: %s\n", n, strerror(errno)); } } int cres = close(fail_fd); if (cres < 0) { printf("[-] Child failed to close fail_fd: %s\n", strerror(errno)); exit(1); } printf("[+] Child fault-injection sweep complete.\n"); exit(0); } /* Step 4: Parent waits for child process to terminate */ int status = 0; pid_t w = waitpid(pid, &status, 0); if (w < 0) { printf("[-] waitpid failed: %s\n", strerror(errno)); exit(1); } if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { printf("[-] Child process terminated abnormally with status %d\n", status); exit(1); } printf("[+] Child process completed and exited cleanly.\n"); /* Step 5: Wait for kmemleak MSECS_MIN_AGE (5000ms) to elapse */ printf("[*] Waiting 8 seconds for kmemleak MSECS_MIN_AGE (5000ms) to elapse...\n"); sleep(8); printf("[+] Wait complete.\n"); /* Step 6: Trigger two consecutive kmemleak scans to confirm unreferenced leaks */ printf("[*] Triggering first kmemleak scan...\n"); write_file("/sys/kernel/debug/kmemleak", "scan\n"); printf("[*] Waiting 2 seconds before confirmation scan...\n"); sleep(2); printf("[*] Triggering second kmemleak scan...\n"); write_file("/sys/kernel/debug/kmemleak", "scan\n"); /* Step 7: Read kmemleak report and forward to /dev/kmsg */ printf("[*] Reading kmemleak report...\n"); int kmemleak_fd = open("/sys/kernel/debug/kmemleak", O_RDONLY); if (kmemleak_fd < 0) { printf("[-] Failed to open /sys/kernel/debug/kmemleak: %s\n", strerror(errno)); exit(1); } printf("[+] Successfully opened /sys/kernel/debug/kmemleak.\n"); char report[8192]; ssize_t bytes; int leak_found = 0; while ((bytes = read(kmemleak_fd, report, sizeof(report) - 1)) > 0) { report[bytes] = '\0'; printf("%s", report); write_to_kmsg(report, (size_t)bytes); if (strstr(report, "vidtv_psi_pmt_create_sec_for_each_pat_entry") != NULL) { leak_found = 1; } } if (bytes < 0) { printf("[-] Failed reading from /sys/kernel/debug/kmemleak: %s\n", strerror(errno)); close(kmemleak_fd); exit(1); } int cres = close(kmemleak_fd); if (cres < 0) { printf("[-] Failed to close /sys/kernel/debug/kmemleak: %s\n", strerror(errno)); exit(1); } printf("[+] Successfully closed /sys/kernel/debug/kmemleak.\n"); if (leak_found) { printf("[+] Target memory leak in vidtv_psi_pmt_create_sec_for_each_pat_entry successfully reproduced!\n"); } else { printf("[-] Target leak not found in kmemleak output.\n"); exit(1); } printf("[*] Waiting 2 seconds to ensure log delivery...\n"); sleep(2); return 0; }