// https://syzkaller.appspot.com/bug?id=58a7b10730f02c40b3c4e6b8edcfbe5a108cfb8b #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef SOL_NETLINK #define SOL_NETLINK 270 #endif #ifndef NETLINK_ADD_MEMBERSHIP #define NETLINK_ADD_MEMBERSHIP 1 #endif #ifndef NBD_GENL_FAMILY_NAME #define NBD_GENL_FAMILY_NAME "nbd" #endif #ifndef NBD_GENL_VERSION #define NBD_GENL_VERSION 1 #endif #ifndef NBD_ATTR_MAX enum { NBD_ATTR_UNSPEC, NBD_ATTR_INDEX, NBD_ATTR_SIZE_BYTES, NBD_ATTR_BLOCK_SIZE_BYTES, NBD_ATTR_TIMEOUT, NBD_ATTR_SERVER_FLAGS, NBD_ATTR_CLIENT_FLAGS, NBD_ATTR_SOCKETS, NBD_ATTR_DEAD_CONN_TIMEOUT, NBD_ATTR_DEVICE_LIST, NBD_ATTR_BACKEND_IDENTIFIER, }; #endif #ifndef NBD_SOCK_ITEM_MAX enum { NBD_SOCK_ITEM_UNSPEC, NBD_SOCK_ITEM, }; #endif #ifndef NBD_SOCK_MAX enum { NBD_SOCK_UNSPEC, NBD_SOCK_FD, }; #endif #ifndef NBD_CMD_MAX enum { NBD_CMD_UNSPEC, NBD_CMD_CONNECT, NBD_CMD_DISCONNECT, NBD_CMD_RECONFIGURE, NBD_CMD_LINK_DEAD, NBD_CMD_STATUS, }; #endif #ifndef NBD_FLAG_CAN_MULTI_CONN #define NBD_FLAG_CAN_MULTI_CONN (1 << 8) #endif #ifndef NBD_REQUEST_MAGIC #define NBD_REQUEST_MAGIC 0x25609513 #endif #ifndef NBD_REPLY_MAGIC #define NBD_REPLY_MAGIC 0x67446698 #endif #ifndef NLA_F_NESTED #define NLA_F_NESTED (1 << 15) #endif #ifndef NLA_OK #define NLA_OK(nla, len) ((len) >= (int)sizeof(struct nlattr) && \ (nla)->nla_len >= sizeof(struct nlattr) && \ (nla)->nla_len <= (len)) #endif #ifndef NLA_NEXT #define NLA_NEXT(nla, len) ((len) -= NLA_ALIGN((nla)->nla_len), \ (struct nlattr *)(((char *)(nla)) + NLA_ALIGN((nla)->nla_len))) #endif struct nbd_request_wire { uint32_t magic; uint32_t type; char handle[8]; uint64_t from; uint32_t len; } __attribute__((packed)); struct nbd_reply_wire { uint32_t magic; uint32_t error; char handle[8]; } __attribute__((packed)); static void add_attr(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) { int len = NLA_HDRLEN + alen; if (NLMSG_ALIGN(n->nlmsg_len) + NLA_ALIGN(len) > (size_t)maxlen) { printf("[-] add_attr: buffer overflow\n"); exit(1); } struct nlattr *nla = (struct nlattr *)(((char *)n) + NLMSG_ALIGN(n->nlmsg_len)); nla->nla_type = type; nla->nla_len = len; if (alen > 0 && data) memcpy((char *)nla + NLA_HDRLEN, data, alen); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + NLA_ALIGN(len); } static void add_attr_u32(struct nlmsghdr *n, int maxlen, int type, uint32_t val) { add_attr(n, maxlen, type, &val, sizeof(val)); } static void add_attr_u64(struct nlmsghdr *n, int maxlen, int type, uint64_t val) { add_attr(n, maxlen, type, &val, sizeof(val)); } static struct nlattr *nla_nest_start(struct nlmsghdr *n, int maxlen, int type) { struct nlattr *nest = (struct nlattr *)(((char *)n) + NLMSG_ALIGN(n->nlmsg_len)); add_attr(n, maxlen, type | NLA_F_NESTED, NULL, 0); return nest; } static void nla_nest_end(struct nlmsghdr *n, struct nlattr *nest) { nest->nla_len = (char *)n + NLMSG_ALIGN(n->nlmsg_len) - (char *)nest; } static int create_tcp_pair(int sv[2]) { int srv = socket(AF_INET, SOCK_STREAM, 0); if (srv < 0) { printf("[-] socket(AF_INET, SOCK_STREAM) failed: %s\n", strerror(errno)); return -1; } int opt = 1; if (setsockopt(srv, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { printf("[-] setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno)); close(srv); return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = 0; if (bind(srv, (struct sockaddr *)&addr, sizeof(addr)) < 0) { printf("[-] bind failed: %s\n", strerror(errno)); close(srv); return -1; } socklen_t slen = sizeof(addr); if (getsockname(srv, (struct sockaddr *)&addr, &slen) < 0) { printf("[-] getsockname failed: %s\n", strerror(errno)); close(srv); return -1; } if (listen(srv, 1) < 0) { printf("[-] listen failed: %s\n", strerror(errno)); close(srv); return -1; } int cli = socket(AF_INET, SOCK_STREAM, 0); if (cli < 0) { printf("[-] socket(cli) failed: %s\n", strerror(errno)); close(srv); return -1; } if (connect(cli, (struct sockaddr *)&addr, sizeof(addr)) < 0) { printf("[-] connect failed: %s\n", strerror(errno)); close(cli); close(srv); return -1; } int accepted = accept(srv, NULL, NULL); close(srv); if (accepted < 0) { printf("[-] accept failed: %s\n", strerror(errno)); close(cli); return -1; } int nodelay = 1; setsockopt(cli, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); setsockopt(accepted, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); sv[0] = cli; /* Passed to kernel NBD driver */ sv[1] = accepted; /* Held by userspace server thread */ return 0; } static int get_genl_family(int nl_fd, const char *name) { 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.n.nlmsg_seq = 1; req.g.cmd = CTRL_CMD_GETFAMILY; req.g.version = 1; add_attr(&req.n, sizeof(req), CTRL_ATTR_FAMILY_NAME, name, strlen(name) + 1); int res = send(nl_fd, &req, req.n.nlmsg_len, 0); if (res < 0) { printf("[-] Failed to send CTRL_CMD_GETFAMILY: %s\n", strerror(errno)); return -1; } char resp[4096]; res = recv(nl_fd, resp, sizeof(resp), 0); if (res < 0) { printf("[-] Failed to recv CTRL_CMD_GETFAMILY response: %s\n", strerror(errno)); return -1; } int len = res; struct nlmsghdr *nlh = (struct nlmsghdr *)resp; for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh); printf("[-] Netlink error in CTRL_CMD_GETFAMILY: %d\n", err->error); return -1; } struct genlmsghdr *ghdr = (struct genlmsghdr *)NLMSG_DATA(nlh); struct nlattr *nla = (struct nlattr *)((char *)ghdr + GENL_HDRLEN); int attrlen = nlh->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN); while (NLA_OK(nla, attrlen)) { if ((nla->nla_type & NLA_TYPE_MASK) == CTRL_ATTR_FAMILY_ID) { uint16_t id = *(uint16_t *)((char *)nla + NLA_HDRLEN); return (int)id; } nla = NLA_NEXT(nla, attrlen); } } return -1; } static int nl_drain_reply(int fd) { char buf[4096]; int len = recv(fd, buf, sizeof(buf), 0); if (len < 0) { printf("[-] nl_drain_reply failed: %s\n", strerror(errno)); return -errno; } struct nlmsghdr *nlh = (struct nlmsghdr *)buf; for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { if (nlh->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh); return err->error; } } return 0; } /* Background NBD Server to answer kernel partition scans and pread requests */ static volatile int server_running = 1; static pthread_mutex_t srv_lock = PTHREAD_MUTEX_INITIALIZER; static int srv_fds[8] = {-1, -1, -1, -1, -1, -1, -1, -1}; static void server_add_sock(int fd) { pthread_mutex_lock(&srv_lock); for (int i = 0; i < 8; i++) { if (srv_fds[i] == -1) { srv_fds[i] = fd; break; } } pthread_mutex_unlock(&srv_lock); } static void server_remove_sock(int fd) { pthread_mutex_lock(&srv_lock); for (int i = 0; i < 8; i++) { if (srv_fds[i] == fd) { srv_fds[i] = -1; break; } } pthread_mutex_unlock(&srv_lock); } static void *server_thread_func(void *arg) { (void)arg; static char zero_buf[4096]; char drain_buf[4096]; while (server_running) { struct pollfd pfds[8]; int fds_copy[8]; int nfds = 0; pthread_mutex_lock(&srv_lock); for (int i = 0; i < 8; i++) { if (srv_fds[i] >= 0) { pfds[nfds].fd = srv_fds[i]; pfds[nfds].events = POLLIN; pfds[nfds].revents = 0; fds_copy[nfds] = srv_fds[i]; nfds++; } } pthread_mutex_unlock(&srv_lock); if (nfds == 0) { usleep(10000); continue; } int ret = poll(pfds, nfds, 50); if (ret <= 0) continue; for (int i = 0; i < nfds; i++) { if (pfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { server_remove_sock(fds_copy[i]); continue; } if (pfds[i].revents & POLLIN) { int fd = fds_copy[i]; struct nbd_request_wire req; ssize_t n = recv(fd, &req, sizeof(req), MSG_WAITALL); if (n != (ssize_t)sizeof(req)) { server_remove_sock(fd); continue; } if (ntohl(req.magic) != NBD_REQUEST_MAGIC) { server_remove_sock(fd); continue; } uint32_t type = ntohl(req.type) & 0xffff; uint32_t len = ntohl(req.len); if (type == 1) { /* NBD_CMD_WRITE */ uint32_t rem = len; while (rem > 0) { size_t to_read = rem > sizeof(drain_buf) ? sizeof(drain_buf) : rem; ssize_t r = recv(fd, drain_buf, to_read, MSG_WAITALL); if (r <= 0) break; rem -= r; } } struct nbd_reply_wire rep; memset(&rep, 0, sizeof(rep)); rep.magic = htonl(NBD_REPLY_MAGIC); rep.error = 0; memcpy(rep.handle, req.handle, sizeof(rep.handle)); ssize_t s = send(fd, &rep, sizeof(rep), MSG_NOSIGNAL); if (s <= 0) { server_remove_sock(fd); continue; } if (type == 0) { /* NBD_CMD_READ */ uint32_t rem = len; while (rem > 0) { size_t to_write = rem > sizeof(zero_buf) ? sizeof(zero_buf) : rem; ssize_t w = send(fd, zero_buf, to_write, MSG_NOSIGNAL); if (w <= 0) break; rem -= w; } } } } } return NULL; } static void trigger_uevent(const char *path, const char *action) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] open(%s) failed: %s\n", path, strerror(errno)); return; } printf("[+] open %s successful.\n", path); size_t len = strlen(action); ssize_t res = write(fd, action, len); if (res < 0) { printf("[-] write to %s failed: %s\n", path, strerror(errno)); } else { printf("[+] Wrote '%s' to %s (bytes: %zd)\n", action, path, res); } close(fd); } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting reproducer for possible deadlock in device_register...\n"); /* Ignore SIGPIPE so socket writes to broken connections do not terminate the process */ signal(SIGPIPE, SIG_IGN); /* Step 1: Register listener for uevent netlink broadcasts */ int uevent_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT); if (uevent_fd < 0) { printf("[-] Failed to create NETLINK_KOBJECT_UEVENT socket: %s\n", strerror(errno)); exit(1); } printf("[+] socket NETLINK_KOBJECT_UEVENT successful.\n"); struct sockaddr_nl sa; memset(&sa, 0, sizeof(sa)); sa.nl_family = AF_NETLINK; sa.nl_groups = 1; int res = bind(uevent_fd, (struct sockaddr *)&sa, sizeof(sa)); if (res < 0) { printf("[-] bind(uevent_fd) failed: %s\n", strerror(errno)); exit(1); } printf("[+] bind uevent_fd successful.\n"); int group = 1; res = setsockopt(uevent_fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); if (res < 0) { printf("[-] setsockopt(NETLINK_ADD_MEMBERSHIP) failed: %s\n", strerror(errno)); exit(1); } printf("[+] setsockopt NETLINK_ADD_MEMBERSHIP successful.\n"); /* Step 2: Open generic netlink socket and resolve NBD family */ int genl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (genl_fd < 0) { printf("[-] Failed to create NETLINK_GENERIC socket: %s\n", strerror(errno)); exit(1); } printf("[+] socket NETLINK_GENERIC successful.\n"); int nbd_family = get_genl_family(genl_fd, NBD_GENL_FAMILY_NAME); if (nbd_family < 0) { printf("[-] Failed to resolve NBD generic netlink family ID\n"); exit(1); } printf("[+] Resolved NBD generic netlink family ID: %d\n", nbd_family); /* Step 3: Prepare 2 TCP socket pairs */ int sv0[2], sv1[2]; res = create_tcp_pair(sv0); if (res < 0) { printf("[-] Failed to create TCP pair sv0\n"); exit(1); } printf("[+] create_tcp_pair sv0 successful.\n"); res = create_tcp_pair(sv1); if (res < 0) { printf("[-] Failed to create TCP pair sv1\n"); exit(1); } printf("[+] create_tcp_pair sv1 successful.\n"); /* Step 4: Start background server thread before connect */ server_add_sock(sv0[1]); server_add_sock(sv1[1]); pthread_t srv_th; res = pthread_create(&srv_th, NULL, server_thread_func, NULL); if (res != 0) { printf("[-] pthread_create failed: %s\n", strerror(res)); exit(1); } printf("[+] Background server thread started.\n"); /* Step 5: Connect NBD with 2 connections & NBD_FLAG_CAN_MULTI_CONN */ struct { struct nlmsghdr n; struct genlmsghdr g; char buf[1024]; } msg; memset(&msg, 0, sizeof(msg)); msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); msg.n.nlmsg_type = nbd_family; msg.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; msg.g.cmd = NBD_CMD_CONNECT; msg.g.version = NBD_GENL_VERSION; add_attr_u32(&msg.n, sizeof(msg), NBD_ATTR_INDEX, 0); add_attr_u64(&msg.n, sizeof(msg), NBD_ATTR_SIZE_BYTES, 1024 * 1024); add_attr_u64(&msg.n, sizeof(msg), NBD_ATTR_SERVER_FLAGS, NBD_FLAG_CAN_MULTI_CONN); struct nlattr *sockets = nla_nest_start(&msg.n, sizeof(msg), NBD_ATTR_SOCKETS); struct nlattr *item0 = nla_nest_start(&msg.n, sizeof(msg), NBD_SOCK_ITEM); add_attr_u32(&msg.n, sizeof(msg), NBD_SOCK_FD, sv0[0]); nla_nest_end(&msg.n, item0); struct nlattr *item1 = nla_nest_start(&msg.n, sizeof(msg), NBD_SOCK_ITEM); add_attr_u32(&msg.n, sizeof(msg), NBD_SOCK_FD, sv1[0]); nla_nest_end(&msg.n, item1); nla_nest_end(&msg.n, sockets); res = send(genl_fd, &msg, msg.n.nlmsg_len, 0); if (res < 0) { printf("[-] Failed to send NBD_CMD_CONNECT: %s\n", strerror(errno)); exit(1); } printf("[+] send NBD_CMD_CONNECT successful.\n"); res = nl_drain_reply(genl_fd); if (res < 0) { printf("[-] NBD_CMD_CONNECT returned error: %d\n", res); exit(1); } printf("[+] NBD_CMD_CONNECT successful (established &q->elevator_lock -> set->srcu).\n"); /* Step 6: Open block device and dispatch pread to establish set->srcu -> &cmd->lock -> &nsock->tx_lock */ int nbd_fd = open("/dev/nbd0", O_RDWR); if (nbd_fd < 0) { printf("[-] open(/dev/nbd0) failed: %s, falling back to /tmp/nbd0\n", strerror(errno)); if (mknod("/tmp/nbd0", S_IFBLK | 0660, makedev(43, 0)) < 0 && errno != EEXIST) { printf("[-] mknod(/tmp/nbd0) failed: %s\n", strerror(errno)); exit(1); } nbd_fd = open("/tmp/nbd0", O_RDWR); if (nbd_fd < 0) { printf("[-] open(/tmp/nbd0) failed: %s\n", strerror(errno)); exit(1); } printf("[+] open /tmp/nbd0 successful.\n"); } else { printf("[+] open /dev/nbd0 successful.\n"); } char read_buf[512]; ssize_t rres = pread(nbd_fd, read_buf, sizeof(read_buf), 0); if (rres < 0) { printf("[-] pread failed: %s\n", strerror(errno)); exit(1); } printf("[+] pread successful (read %zd bytes, established set->srcu -> &cmd->lock -> &nsock->tx_lock).\n", rres); /* Step 7: Close sv0[1] to mark socket 0 dead cleanly (lock class sk_lock-AF_INET-NBD) */ server_remove_sock(sv0[1]); res = shutdown(sv0[1], SHUT_RDWR); if (res < 0) { printf("[-] shutdown(sv0[1]) failed: %s\n", strerror(errno)); } else { printf("[+] shutdown(sv0[1]) successful.\n"); } res = close(sv0[1]); if (res < 0) { printf("[-] close(sv0[1]) failed: %s\n", strerror(errno)); } else { printf("[+] close(sv0[1]) successful, socket 0 marked dead.\n"); } usleep(150000); /* Step 8: Reconfigure socket 0 to establish &nsock->tx_lock -> cpu_hotplug_lock */ int sv_new[2]; res = create_tcp_pair(sv_new); if (res < 0) { printf("[-] Failed to create TCP pair sv_new\n"); exit(1); } printf("[+] create_tcp_pair sv_new successful.\n"); server_add_sock(sv_new[1]); memset(&msg, 0, sizeof(msg)); msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN); msg.n.nlmsg_type = nbd_family; msg.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; msg.g.cmd = NBD_CMD_RECONFIGURE; msg.g.version = NBD_GENL_VERSION; add_attr_u32(&msg.n, sizeof(msg), NBD_ATTR_INDEX, 0); sockets = nla_nest_start(&msg.n, sizeof(msg), NBD_ATTR_SOCKETS); item0 = nla_nest_start(&msg.n, sizeof(msg), NBD_SOCK_ITEM); add_attr_u32(&msg.n, sizeof(msg), NBD_SOCK_FD, sv_new[0]); nla_nest_end(&msg.n, item0); nla_nest_end(&msg.n, sockets); res = send(genl_fd, &msg, msg.n.nlmsg_len, 0); if (res < 0) { printf("[-] Failed to send NBD_CMD_RECONFIGURE: %s\n", strerror(errno)); exit(1); } printf("[+] send NBD_CMD_RECONFIGURE successful.\n"); res = nl_drain_reply(genl_fd); if (res < 0) { printf("[-] NBD_CMD_RECONFIGURE returned error: %d\n", res); exit(1); } printf("[+] NBD_CMD_RECONFIGURE successful (established &nsock->tx_lock -> cpu_hotplug_lock).\n"); /* * Step 9: Keep sv1 and sv_new active and served by the server thread. * Trigger synthetic uevents via sysfs to acquire uevent_sock_mutex -> fs_reclaim, * completing the full circular dependency chain and triggering the lockdep warning. */ printf("[*] Triggering uevents via sysfs...\n"); trigger_uevent("/sys/block/nbd0/uevent", "add\n"); trigger_uevent("/sys/block/nbd0/uevent", "change\n"); trigger_uevent("/sys/class/net/lo/uevent", "add\n"); /* Step 10: Close block device descriptor */ res = close(nbd_fd); if (res < 0) { printf("[-] close(nbd_fd) failed: %s\n", strerror(errno)); } else { printf("[+] close(nbd_fd) successful.\n"); } /* Step 11: Sleep to allow lockdep report to flush */ printf("[*] Sleeping to allow lockdep report to flush...\n"); sleep(3); server_running = 0; pthread_join(srv_th, NULL); printf("[+] Reproducer finished.\n"); return 0; }