// https://syzkaller.appspot.com/bug?id=6395b8a252098d624b0469d43da8c0476e7ae2f4 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NETLINK_RDMA #define NETLINK_RDMA 20 #endif #ifndef RDMA_NL_NLDEV #define RDMA_NL_NLDEV 5 #endif #ifndef RDMA_NLDEV_CMD_NEWLINK #define RDMA_NLDEV_CMD_NEWLINK 3 #endif #ifndef RDMA_NLDEV_CMD_DELLINK #define RDMA_NLDEV_CMD_DELLINK 4 #endif #ifndef RDMA_NL_GET_TYPE #define RDMA_NL_GET_TYPE(c, op) (((c) << 10) + (op)) #endif #ifndef RDMA_NLDEV_ATTR_DEV_INDEX #define RDMA_NLDEV_ATTR_DEV_INDEX 1 #endif #ifndef RDMA_NLDEV_ATTR_DEV_NAME #define RDMA_NLDEV_ATTR_DEV_NAME 2 #endif #ifndef RDMA_NLDEV_ATTR_NDEV_NAME #define RDMA_NLDEV_ATTR_NDEV_NAME 51 #endif #ifndef RDMA_NLDEV_ATTR_LINK_TYPE #define RDMA_NLDEV_ATTR_LINK_TYPE 65 #endif static uint32_t nl_seq = 1000; 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 w = write(fd, val, len); if (w < 0 || (size_t)w != 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 write_file_if_exists(const char *path, const char *val) { if (access(path, F_OK) == 0) { int fd = open(path, O_WRONLY); if (fd >= 0) { size_t len = strlen(val); write(fd, val, len); close(fd); printf("[+] Wrote '%s' to %s\n", val, path); } } } static void disable_fault_framework(const char *name) { char dirpath[256]; snprintf(dirpath, sizeof(dirpath), "/sys/kernel/debug/%s", name); if (access(dirpath, F_OK) != 0) return; printf("[*] Isolating and disabling fault framework: %s\n", name); char path[256]; snprintf(path, sizeof(path), "/sys/kernel/debug/%s/require-start", name); write_file_if_exists(path, "1\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/require-end", name); write_file_if_exists(path, "1\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/reject-start", name); write_file_if_exists(path, "1\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/reject-end", name); write_file_if_exists(path, "0xffffffffffffffff\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/probability", name); write_file_if_exists(path, "0\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/times", name); write_file_if_exists(path, "0\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/verbose", name); write_file_if_exists(path, "0\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/min-order", name); write_file_if_exists(path, "100\n"); snprintf(path, sizeof(path), "/sys/kernel/debug/%s/stacktrace-depth", name); write_file_if_exists(path, "32\n"); } static void set_fail_nth(int val) { char buf[32]; int len = snprintf(buf, sizeof(buf), "%d\n", val); int fd = open("/proc/thread-self/fail-nth", O_WRONLY); if (fd < 0) { char path[64]; snprintf(path, sizeof(path), "/proc/self/task/%ld/fail-nth", syscall(SYS_gettid)); fd = open(path, O_WRONLY); } if (fd < 0) { printf("[-] Failed to open fail-nth: %s\n", strerror(errno)); exit(1); } ssize_t w = write(fd, buf, (size_t)len); if (w < 0) { printf("[-] Failed to write %d to fail-nth: %s\n", val, strerror(errno)); close(fd); exit(1); } int res = close(fd); if (res < 0) { printf("[-] Failed to close fail-nth: %s\n", strerror(errno)); exit(1); } } static void setup_failslab(void) { if (access("/sys/kernel/debug/kmemleak", F_OK) != 0) { int mres = mount("debugfs", "/sys/kernel/debug", "debugfs", 0, NULL); if (mres < 0 && errno != EBUSY) { printf("[-] mount(debugfs) failed: %s\n", strerror(errno)); exit(1); } printf("[+] mount(debugfs) successful.\n"); } else { printf("[+] debugfs already accessible.\n"); } if (access("/sys/kernel/slab", F_OK) != 0) { int mres = mount("sysfs", "/sys", "sysfs", 0, NULL); if (mres < 0 && errno != EBUSY) { printf("[-] mount(sysfs) failed: %s\n", strerror(errno)); exit(1); } printf("[+] mount(sysfs) successful.\n"); } else { printf("[+] sysfs already accessible.\n"); } /* Disable ratelimiting on /dev/kmsg */ write_file_if_exists("/proc/sys/kernel/printk_devkmsg", "on\n"); /* * Completely disable fail_usercopy and all other fault injectors so fail-nth * does not trigger in copy_from_user / netlink_sendmsg. */ const char *fault_frameworks[] = { "fail_usercopy", "fail_page_alloc", "fail_futex", "fail_function", "fail_mempool_alloc", "fail_iommu", "fail_make_request", "fail_io_timeout" }; for (size_t i = 0; i < sizeof(fault_frameworks) / sizeof(fault_frameworks[0]); i++) disable_fault_framework(fault_frameworks[i]); /* Dynamically disable any other debugfs fault frameworks */ DIR *dbg = opendir("/sys/kernel/debug"); if (dbg) { struct dirent *de; while ((de = readdir(dbg)) != NULL) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; if (strcmp(de->d_name, "failslab") == 0) continue; char test_file[512]; snprintf(test_file, sizeof(test_file), "/sys/kernel/debug/%s/reject-start", de->d_name); if (access(test_file, F_OK) == 0) disable_fault_framework(de->d_name); } closedir(dbg); } /* Allow failing GFP_KERNEL allocations */ write_file("/sys/kernel/debug/failslab/ignore-gfp-wait", "N\n"); /* Probability must be 0: fail-nth bypasses probability checks */ write_file("/sys/kernel/debug/failslab/probability", "0\n"); write_file("/sys/kernel/debug/failslab/times", "-1\n"); write_file("/sys/kernel/debug/failslab/verbose", "0\n"); /* Enable cache-filter so only designated radix_tree_node allocations fail */ write_file("/sys/kernel/debug/failslab/cache-filter", "Y\n"); DIR *dir = opendir("/sys/kernel/slab"); int count = 0; if (dir) { struct dirent *de; while ((de = readdir(dir)) != NULL) { if (strstr(de->d_name, "radix_tree_node")) { char path[256]; snprintf(path, sizeof(path), "/sys/kernel/slab/%s/failslab", de->d_name); write_file(path, "1\n"); count++; } } closedir(dir); } if (count == 0) { write_file("/sys/kernel/slab/radix_tree_node/failslab", "1\n"); } printf("[+] Configured failslab cache-filter targeting radix_tree_node.\n"); } static void cleanup_failslab(void) { set_fail_nth(0); write_file_if_exists("/sys/kernel/debug/failslab/cache-filter", "N\n"); DIR *dir = opendir("/sys/kernel/slab"); if (dir) { struct dirent *de; while ((de = readdir(dir)) != NULL) { if (strstr(de->d_name, "radix_tree_node")) { char path[256]; snprintf(path, sizeof(path), "/sys/kernel/slab/%s/failslab", de->d_name); write_file_if_exists(path, "0\n"); } } closedir(dir); } else { write_file_if_exists("/sys/kernel/slab/radix_tree_node/failslab", "0\n"); } printf("[+] Restored failslab settings.\n"); } static void bring_up_lo(void) { int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { printf("[-] Failed to socket(AF_INET, SOCK_DGRAM): %s\n", strerror(errno)); exit(1); } printf("[+] socket(AF_INET, SOCK_DGRAM) successful.\n"); struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1); int res = ioctl(s, SIOCGIFFLAGS, &ifr); if (res < 0) { printf("[-] Failed to ioctl(SIOCGIFFLAGS): %s\n", strerror(errno)); close(s); exit(1); } printf("[+] ioctl(SIOCGIFFLAGS) successful.\n"); ifr.ifr_flags |= IFF_UP | IFF_RUNNING; res = ioctl(s, SIOCSIFFLAGS, &ifr); if (res < 0) { printf("[-] Failed to ioctl(SIOCSIFFLAGS): %s\n", strerror(errno)); close(s); exit(1); } printf("[+] ioctl(SIOCSIFFLAGS) successful, lo is UP.\n"); res = close(s); if (res < 0) { printf("[-] Failed to close datagram socket: %s\n", strerror(errno)); exit(1); } printf("[+] Closed datagram socket successfully.\n"); } static void drain_socket(int fd) { char buf[2048]; while (recv(fd, buf, sizeof(buf), MSG_DONTWAIT) > 0) ; } static void nla_put_str(struct nlmsghdr *nlh, uint16_t type, const char *str) { uint16_t len = (uint16_t)(strlen(str) + 1); struct nlattr *nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); nla->nla_type = type; nla->nla_len = NLA_HDRLEN + len; memcpy((char *)nla + NLA_HDRLEN, str, len); nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + NLA_ALIGN(nla->nla_len); } static void nla_put_u32(struct nlmsghdr *nlh, uint16_t type, uint32_t val) { struct nlattr *nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); nla->nla_type = type; nla->nla_len = NLA_HDRLEN + sizeof(uint32_t); memcpy((char *)nla + NLA_HDRLEN, &val, sizeof(uint32_t)); nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + NLA_ALIGN(nla->nla_len); } static int wait_netlink_ack(int fd, uint32_t expected_seq) { char buf[4096]; while (1) { ssize_t len = recv(fd, buf, sizeof(buf), 0); if (len <= 0) return -1; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; for (; NLMSG_OK(nlh, (size_t)len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_seq != expected_seq) continue; if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh); return err->error; } } } } static void delete_siw_link(int fd, uint32_t dev_index) { char buf[512]; memset(buf, 0, sizeof(buf)); struct nlmsghdr *nlh = (struct nlmsghdr *)buf; nlh->nlmsg_len = NLMSG_LENGTH(0); nlh->nlmsg_type = RDMA_NL_GET_TYPE(RDMA_NL_NLDEV, RDMA_NLDEV_CMD_DELLINK); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = ++nl_seq; nla_put_u32(nlh, RDMA_NLDEV_ATTR_DEV_INDEX, dev_index); send(fd, buf, nlh->nlmsg_len, 0); wait_netlink_ack(fd, nlh->nlmsg_seq); } static void emit_kmemleak_to_kmsg(void) { printf("[*] Configuring kmemleak scan parameters...\n"); write_file_if_exists("/sys/module/kmemleak/parameters/min_unref_scans", "1\n"); write_file_if_exists("/sys/module/kmemleak/parameters/verbose", "1\n"); /* Trigger consecutive kmemleak scans to detect unreferenced objects */ for (int s = 0; s < 3; s++) { write_file("/sys/kernel/debug/kmemleak", "scan\n"); printf("[+] Triggered kmemleak scan %d/3.\n", s + 1); sleep(1); } FILE *kmem_f = fopen("/sys/kernel/debug/kmemleak", "r"); if (!kmem_f) { printf("[-] Failed to fopen /sys/kernel/debug/kmemleak: %s\n", strerror(errno)); exit(1); } printf("[+] fopen(/sys/kernel/debug/kmemleak) successful.\n"); int kfd = open("/dev/kmsg", O_WRONLY); if (kfd < 0) { printf("[-] Failed to open /dev/kmsg: %s\n", strerror(errno)); fclose(kmem_f); exit(1); } printf("[+] open(/dev/kmsg) successful.\n"); char line[512]; char kmsg_buf[600]; int unref_count = 0; /* * Syzkaller crash parser identifies memory leak reports strictly by matching * "BUG: memory leak". Prepend "<1>BUG: memory leak\n" before each unreferenced * object record and stream it line-by-line (< 1024 bytes) to /dev/kmsg. */ while (fgets(line, sizeof(line), kmem_f)) { fputs(line, stdout); if (strstr(line, "unreferenced object")) { unref_count++; const char *bug_header = "<1>BUG: memory leak\n"; ssize_t w = write(kfd, bug_header, strlen(bug_header)); if (w < 0) { printf("[-] Failed to write bug header to /dev/kmsg: %s\n", strerror(errno)); } } int klen = snprintf(kmsg_buf, sizeof(kmsg_buf), "<1>%s", line); if (klen > 0) { ssize_t w = write(kfd, kmsg_buf, (size_t)klen); if (w < 0) { printf("[-] Failed to write line to /dev/kmsg: %s\n", strerror(errno)); } } } int res = fclose(kmem_f); if (res < 0) { printf("[-] Failed to fclose /sys/kernel/debug/kmemleak: %s\n", strerror(errno)); close(kfd); exit(1); } printf("[+] fclose(/sys/kernel/debug/kmemleak) successful. Identified %d unreferenced objects.\n", unref_count); res = close(kfd); if (res < 0) { printf("[-] Failed to close /dev/kmsg: %s\n", strerror(errno)); exit(1); } printf("[+] close(/dev/kmsg) successful.\n"); /* Allow console drivers to finish printing */ printf("[*] Sleeping 3 seconds to allow serial console buffer flush...\n"); sleep(3); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting standalone reproducer for radix_tree_insert memory leak...\n"); bring_up_lo(); setup_failslab(); /* Clear pre-existing kmemleak records */ write_file_if_exists("/sys/kernel/debug/kmemleak", "clear\n"); int nl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_RDMA); if (nl_fd < 0) { printf("[-] Failed to socket(AF_NETLINK, SOCK_RAW, NETLINK_RDMA): %s\n", strerror(errno)); exit(1); } printf("[+] Opened NETLINK_RDMA socket (fd %d).\n", nl_fd); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; int cres = connect(nl_fd, (struct sockaddr *)&sa, sizeof(sa)); if (cres < 0) { printf("[-] Failed to connect NETLINK_RDMA socket: %s\n", strerror(errno)); close(nl_fd); exit(1); } printf("[+] Connected NETLINK_RDMA socket to kernel.\n"); struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; int sres = setsockopt(nl_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); if (sres < 0) { printf("[-] Failed to setsockopt(SO_RCVTIMEO): %s\n", strerror(errno)); close(nl_fd); exit(1); } printf("[+] Configured socket receive timeout.\n"); for (uint32_t idx = 0; idx < 16; idx++) delete_siw_link(nl_fd, idx); /* * Targeted fail-nth sweep across radix_tree_node allocations during * siw device registration (uverbs_alloc_api -> radix_tree_insert). * With non-failslab fault injectors disabled, fail-nth strictly targets * allocations from radix_tree_node cache. */ for (int nth = 2; nth <= 30; nth++) { drain_socket(nl_fd); char buf[512]; memset(buf, 0, sizeof(buf)); struct nlmsghdr *nlh = (struct nlmsghdr *)buf; nlh->nlmsg_len = NLMSG_LENGTH(0); nlh->nlmsg_type = RDMA_NL_GET_TYPE(RDMA_NL_NLDEV, RDMA_NLDEV_CMD_NEWLINK); nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = ++nl_seq; nla_put_str(nlh, RDMA_NLDEV_ATTR_DEV_NAME, "siw0"); nla_put_str(nlh, RDMA_NLDEV_ATTR_LINK_TYPE, "siw"); nla_put_str(nlh, RDMA_NLDEV_ATTR_NDEV_NAME, "lo"); set_fail_nth(nth); ssize_t s = send(nl_fd, buf, nlh->nlmsg_len, 0); set_fail_nth(0); if (s < 0) { printf("[-] send(nth=%d) failed: %s\n", nth, strerror(errno)); } else { int ack = wait_netlink_ack(nl_fd, nlh->nlmsg_seq); printf("[*] create_siw_link(nth=%d) ack: %d (%s)\n", nth, ack, ack == 0 ? "success" : strerror(-ack)); } for (uint32_t idx = 0; idx < 8; idx++) delete_siw_link(nl_fd, idx); usleep(5000); } int clres = close(nl_fd); if (clres < 0) { printf("[-] Failed to close NETLINK_RDMA socket: %s\n", strerror(errno)); exit(1); } printf("[+] Closed NETLINK_RDMA socket.\n"); cleanup_failslab(); /* Sleep 6 seconds to exceed kmemleak MSECS_MIN_AGE (5000 ms) threshold */ printf("[*] Sleeping 6 seconds to exceed kmemleak MSECS_MIN_AGE threshold (5000 ms)...\n"); sleep(6); /* Run kmemleak scans and stream the memory leak report directly to /dev/kmsg */ emit_kmemleak_to_kmsg(); printf("[+] Reproducer finished execution successfully.\n"); return 0; }