// https://syzkaller.appspot.com/bug?id=25fab0b3e4e166e23eaf05a34648d9cbde252f98 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NLA_F_NESTED #define NLA_F_NESTED (1 << 15) #endif #define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN)) #define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN)) #define NLA_NEXT(na, len) ((struct nlattr *)((char *)(na) + NLA_ALIGN((na)->nla_len))) #define NLA_OK(na, len) ((len) >= (int)sizeof(struct nlattr) && \ (na)->nla_len >= sizeof(struct nlattr) && \ (na)->nla_len <= (len)) void reduce_hung_task_timeout(void) { int fd = open("/proc/sys/kernel/hung_task_timeout_secs", O_WRONLY); if (fd >= 0) { if (write(fd, "15\n", 3) < 0) { printf("[-] Failed to write to hung_task_timeout_secs: %s\n", strerror(errno)); } else { printf("[+] Reduced hung_task_timeout_secs to 15.\n"); } close(fd); } else { printf("[-] Failed to open /proc/sys/kernel/hung_task_timeout_secs: %s\n", strerror(errno)); } } int resolve_family_id(const char *family_name) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd < 0) { printf("[-] Failed to socket(AF_NETLINK): %s\n", strerror(errno)); exit(1); } struct { struct nlmsghdr n; struct genlmsghdr g; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); req.n.nlmsg_type = GENL_ID_CTRL; req.n.nlmsg_flags = NLM_F_REQUEST; req.g.cmd = CTRL_CMD_GETFAMILY; req.g.version = 1; struct nlattr *na = (struct nlattr *)GENLMSG_DATA(&req.n); na->nla_type = CTRL_ATTR_FAMILY_NAME; na->nla_len = NLA_HDRLEN + strlen(family_name) + 1; strcpy(NLA_DATA(na), family_name); req.n.nlmsg_len += NLA_ALIGN(na->nla_len); struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) { printf("[-] Failed to sendto(AF_NETLINK): %s\n", strerror(errno)); exit(1); } char buf[4096]; int len = recv(fd, buf, sizeof(buf), 0); close(fd); if (len < 0) { printf("[-] Failed to recv(AF_NETLINK): %s\n", strerror(errno)); exit(1); } struct nlmsghdr *nlh = (struct nlmsghdr *)buf; if (nlh->nlmsg_type == NLMSG_ERROR) { printf("[-] Netlink returned error\n"); exit(1); } struct genlmsghdr *gh = (struct genlmsghdr *)NLMSG_DATA(nlh); na = (struct nlattr *)GENLMSG_DATA(nlh); int nla_len = nlh->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN; while (NLA_OK(na, nla_len)) { if (na->nla_type == CTRL_ATTR_FAMILY_ID) { return *(uint16_t *)NLA_DATA(na); } na = NLA_NEXT(na, nla_len); } return -1; } void *rpcbind_client_thread(void *arg) { int client = (int)(intptr_t)arg; while (1) { uint32_t frag_header; int n = recv(client, &frag_header, 4, MSG_WAITALL); if (n <= 0) break; uint32_t len = ntohl(frag_header) & 0x7FFFFFFF; if (len > 65536) break; char *buf = malloc(len); if (!buf) break; n = recv(client, buf, len, MSG_WAITALL); if (n <= 0) { free(buf); break; } if (len >= 24) { uint32_t *rpc = (uint32_t *)buf; uint32_t xid = rpc[0]; uint32_t msg_type = ntohl(rpc[1]); uint32_t proc = ntohl(rpc[5]); if (msg_type == 0 && proc == 1) { // PMAPPROC_SET uint32_t reply[7]; reply[0] = xid; reply[1] = htonl(1); // REPLY reply[2] = htonl(0); // MSG_ACCEPTED reply[3] = htonl(0); // AUTH_NULL reply[4] = htonl(0); // length 0 reply[5] = htonl(0); // SUCCESS reply[6] = htonl(1); // true uint32_t reply_len = sizeof(reply); uint32_t reply_frag = htonl(0x80000000 | reply_len); send(client, &reply_frag, 4, MSG_NOSIGNAL); send(client, reply, reply_len, MSG_NOSIGNAL); } else if (msg_type == 0 && proc == 2) { // PMAPPROC_UNSET // Block forever to trigger the 60s TCP RPC timeout sleep(10000); } else if (msg_type == 0 && proc == 0) { // NULL uint32_t reply[6]; reply[0] = xid; reply[1] = htonl(1); reply[2] = htonl(0); reply[3] = htonl(0); reply[4] = htonl(0); reply[5] = htonl(0); uint32_t reply_len = sizeof(reply); uint32_t reply_frag = htonl(0x80000000 | reply_len); send(client, &reply_frag, 4, MSG_NOSIGNAL); send(client, reply, reply_len, MSG_NOSIGNAL); } else { uint32_t reply[7]; reply[0] = xid; reply[1] = htonl(1); reply[2] = htonl(0); reply[3] = htonl(0); reply[4] = htonl(0); reply[5] = htonl(0); reply[6] = htonl(0); uint32_t reply_len = sizeof(reply); uint32_t reply_frag = htonl(0x80000000 | reply_len); send(client, &reply_frag, 4, MSG_NOSIGNAL); send(client, reply, reply_len, MSG_NOSIGNAL); } } free(buf); } close(client); return NULL; } void *rpcbind_thread(void *arg) { int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { printf("[-] Failed to socket(AF_INET): %s\n", strerror(errno)); exit(1); } int opt = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { printf("[-] Failed to setsockopt: %s\n", strerror(errno)); exit(1); } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(111); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { printf("[-] Failed to bind rpcbind: %s\n", strerror(errno)); exit(1); } if (listen(fd, 100) < 0) { printf("[-] Failed to listen rpcbind: %s\n", strerror(errno)); exit(1); } while (1) { int client = accept(fd, NULL, NULL); if (client >= 0) { pthread_t tid; pthread_create(&tid, NULL, rpcbind_client_thread, (void *)(intptr_t)client); pthread_detach(tid); } } return NULL; } void send_listener_set(int fd, int family_id, int num_ports) { struct { struct nlmsghdr n; struct genlmsghdr g; char buf[8192]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); req.n.nlmsg_type = family_id; req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.g.cmd = 6; // NFSD_CMD_LISTENER_SET req.g.version = 1; for (int i = 0; i < num_ports; i++) { struct nlattr *nest = (struct nlattr *)((char *)&req.n + req.n.nlmsg_len); nest->nla_type = 1 | NLA_F_NESTED; // NFSD_A_SERVER_SOCK_ADDR nest->nla_len = NLA_HDRLEN; req.n.nlmsg_len += NLA_HDRLEN; struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(2049 + i); sa.sin_addr.s_addr = htonl(INADDR_ANY); struct nlattr *na_addr = (struct nlattr *)((char *)&req.n + req.n.nlmsg_len); na_addr->nla_type = 1; // NFSD_A_SOCK_ADDR na_addr->nla_len = NLA_HDRLEN + sizeof(sa); memcpy(NLA_DATA(na_addr), &sa, sizeof(sa)); req.n.nlmsg_len += NLA_ALIGN(na_addr->nla_len); nest->nla_len += NLA_ALIGN(na_addr->nla_len); const char *transport = "tcp"; struct nlattr *na_trans = (struct nlattr *)((char *)&req.n + req.n.nlmsg_len); na_trans->nla_type = 2; // NFSD_A_SOCK_TRANSPORT_NAME na_trans->nla_len = NLA_HDRLEN + strlen(transport) + 1; strcpy(NLA_DATA(na_trans), transport); req.n.nlmsg_len += NLA_ALIGN(na_trans->nla_len); nest->nla_len += NLA_ALIGN(na_trans->nla_len); } struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) { printf("[-] Failed to sendto(AF_NETLINK) in send_listener_set: %s\n", strerror(errno)); exit(1); } char buf[8192]; if (recv(fd, buf, sizeof(buf), 0) < 0) { printf("[-] Failed to recv(AF_NETLINK) in send_listener_set: %s\n", strerror(errno)); exit(1); } } void *netlink_thread(void *arg) { int family_id = *(int *)arg; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd < 0) { printf("[-] Failed to socket(AF_NETLINK) in netlink_thread: %s\n", strerror(errno)); exit(1); } struct { struct nlmsghdr n; struct genlmsghdr g; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); req.n.nlmsg_type = family_id; req.n.nlmsg_flags = NLM_F_REQUEST; req.g.cmd = 3; // NFSD_CMD_THREADS_GET req.g.version = 1; struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; while (1) { if (sendto(fd, &req, req.n.nlmsg_len, 0, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) { break; } char buf[4096]; if (recv(fd, buf, sizeof(buf), 0) < 0) { break; } usleep(100000); // 100ms } close(fd); return NULL; } struct destroy_args { int family_id; }; void *destroy_nfsd_thread(void *arg) { struct destroy_args *args = (struct destroy_args *)arg; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd < 0) { printf("[-] Failed to socket(AF_NETLINK) in destroy_nfsd_thread: %s\n", strerror(errno)); exit(1); } send_listener_set(fd, args->family_id, 0); close(fd); return NULL; } void *create_nfsd_thread(void *arg) { struct destroy_args *args = (struct destroy_args *)arg; int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd < 0) { printf("[-] Failed to socket(AF_NETLINK) in create_nfsd_thread: %s\n", strerror(errno)); exit(1); } send_listener_set(fd, args->family_id, 20); close(fd); return NULL; } void setup_lo() { int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { printf("[-] Failed to socket(AF_INET) for lo: %s\n", strerror(errno)); exit(1); } struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, "lo"); if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { printf("[-] Failed to ioctl SIOCGIFFLAGS: %s\n", strerror(errno)); exit(1); } ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) { printf("[-] Failed to ioctl SIOCSIFFLAGS: %s\n", strerror(errno)); exit(1); } close(fd); printf("[+] setup_lo successful.\n"); } int main() { int res; // Reduce hung task timeout to 15s to trigger the bug before the 150s execution limit reduce_hung_task_timeout(); res = unshare(CLONE_NEWNET | CLONE_NEWNS); if (res < 0) { printf("[-] Failed to unshare: %s\n", strerror(errno)); exit(1); } printf("[+] unshare successful.\n"); setup_lo(); // Hide any existing rpcbind sockets so the kernel falls back to TCP 127.0.0.1:111 res = mount("tmpfs", "/run", "tmpfs", 0, NULL); if (res < 0) { printf("[-] Failed to mount tmpfs on /run: %s\n", strerror(errno)); } res = mount("tmpfs", "/var/run", "tmpfs", 0, NULL); if (res < 0) { printf("[-] Failed to mount tmpfs on /var/run: %s\n", strerror(errno)); } pthread_t tid; res = pthread_create(&tid, NULL, rpcbind_thread, NULL); if (res != 0) { printf("[-] Failed to pthread_create rpcbind_thread: %s\n", strerror(res)); exit(1); } printf("[+] rpcbind_thread created successful.\n"); sleep(1); res = mkdir("/proc/fs/nfsd", 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir /proc/fs/nfsd: %s\n", strerror(errno)); exit(1); } res = mount("nfsd", "/proc/fs/nfsd", "nfsd", 0, NULL); if (res < 0) { printf("[-] Failed to mount nfsd: %s\n", strerror(errno)); exit(1); } printf("[+] mount nfsd successful.\n"); int family_id = resolve_family_id("nfsd"); if (family_id < 0) { printf("[-] Failed to resolve nfsd family id\n"); exit(1); } printf("[+] resolve_family_id successful, id: %d.\n", family_id); struct destroy_args d_args = {family_id}; printf("[+] Spawning concurrent threads...\n"); // Spawn concurrent threads to block on nfsd_mutex in TASK_UNINTERRUPTIBLE for (int i = 0; i < 5; i++) { pthread_t nl_tid; res = pthread_create(&nl_tid, NULL, netlink_thread, &family_id); if (res != 0) { printf("[-] Failed to create netlink_thread: %s\n", strerror(res)); exit(1); } } printf("[+] Netlink threads created.\n"); printf("[+] Creating listeners...\n"); pthread_t create_tid; res = pthread_create(&create_tid, NULL, create_nfsd_thread, &d_args); if (res != 0) { printf("[-] Failed to create create_nfsd_thread: %s\n", strerror(res)); exit(1); } sleep(2); printf("[+] Destroying listeners...\n"); pthread_t stop_tid; res = pthread_create(&stop_tid, NULL, destroy_nfsd_thread, &d_args); if (res != 0) { printf("[-] Failed to create destroy_nfsd_thread: %s\n", strerror(res)); exit(1); } printf("[+] Waiting for hung task timeout (60s)...\n"); sleep(60); // Wait for 60 seconds, which is > 15s timeout return 0; }