// https://syzkaller.appspot.com/bug?id=c347f5beb316877d59036205071cdc70cc8a2564 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NLA_HDRLEN #define NLA_ALIGNTO 4U #define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) #define NLA_HDRLEN NLA_ALIGN(sizeof(struct nlattr)) #endif #ifndef NLMSG_ALIGN #define NLMSG_ALIGNTO 4U #define NLMSG_ALIGN(len) (((len) + NLMSG_ALIGNTO - 1) & ~(NLMSG_ALIGNTO - 1)) #endif #ifndef NL80211_CMD_RELOAD_REGDB #define NL80211_CMD_RELOAD_REGDB 126 #endif int resolve_nl80211(void) { int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd < 0) { printf("[-] Failed to create AF_NETLINK socket: %s\n", strerror(errno)); return -1; } struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); struct { struct nlmsghdr nlh; struct genlmsghdr gnlh; struct nlattr nla; char name[8]; } req = { .nlh = { .nlmsg_len = sizeof(req), .nlmsg_type = GENL_ID_CTRL, .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, }, .gnlh = { .cmd = CTRL_CMD_GETFAMILY, .version = 1, }, .nla = { .nla_len = sizeof(struct nlattr) + 8, .nla_type = CTRL_ATTR_FAMILY_NAME, }, .name = "nl80211" }; if (send(fd, &req, sizeof(req), 0) < 0) { printf("[-] Failed to send netlink message: %s\n", strerror(errno)); close(fd); return -1; } char buf[4096]; int len = recv(fd, buf, sizeof(buf), 0); if (len < 0) { printf("[-] Failed to receive netlink message: %s\n", strerror(errno)); close(fd); return -1; } int id = -1; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; if (nlh->nlmsg_type != NLMSG_ERROR) { struct genlmsghdr *gnlh = (struct genlmsghdr *)((char *)nlh + NLMSG_ALIGN(sizeof(struct nlmsghdr))); struct nlattr *nla = (struct nlattr *)((char *)gnlh + NLMSG_ALIGN(sizeof(struct genlmsghdr))); int nla_len = nlh->nlmsg_len - NLMSG_ALIGN(sizeof(struct nlmsghdr)) - NLMSG_ALIGN(sizeof(struct genlmsghdr)); while (nla_len >= (int)sizeof(struct nlattr)) { int attr_len = nla->nla_len; if (attr_len < (int)sizeof(struct nlattr) || attr_len > nla_len) break; if (nla->nla_type == CTRL_ATTR_FAMILY_ID) { id = *(uint16_t *)((char *)nla + NLA_HDRLEN); break; } int aligned_len = NLA_ALIGN(attr_len); nla = (struct nlattr *)((char *)nla + aligned_len); nla_len -= aligned_len; } } close(fd); if (id < 0) { printf("[-] Failed to resolve nl80211 family ID.\n"); } return id; } volatile int sync_flag = 0; void *write_thread(void *arg) { int fd = open("/dev/nvme-fabrics", O_RDWR); if (fd >= 0) { const char *payload = "transport=tcp,traddr=127.0.0.1,trsvcid=1234,nqn=testnqn"; sync_flag = 1; if (write(fd, payload, strlen(payload)) < 0) { // Might fail, that's fine } close(fd); } return NULL; } int main(void) { printf("[*] Starting reproducer...\n"); // Create dummy firmware files to ensure IMA measures them mkdir("/lib/firmware", 0755); int fw_fd = open("/lib/firmware/regulatory.db", O_CREAT | O_RDWR, 0644); if (fw_fd >= 0) { if (write(fw_fd, "dummy", 5) < 0) {} close(fw_fd); } fw_fd = open("/lib/firmware/regulatory.db.p7s", O_CREAT | O_RDWR, 0644); if (fw_fd >= 0) { if (write(fw_fd, "dummy", 5) < 0) {} close(fw_fd); } // Step 5: p->lock -> nvmf_dev_mutex printf("[*] Step 5: read from /dev/nvme-fabrics\n"); int fd5 = open("/dev/nvme-fabrics", O_RDONLY); if (fd5 < 0) { printf("[-] open /dev/nvme-fabrics failed: %s\n", strerror(errno)); } else { char buf5[4096]; if (read(fd5, buf5, sizeof(buf5)) < 0) { // Might fail with EINVAL, that's fine as long as it locks } close(fd5); printf("[+] Step 5 successful.\n"); } // Step 4: ima_iint_mutex_key -> p->lock printf("[*] Step 4: IMA policy and read binderfs/tracefs\n"); int fd_policy = open("/sys/kernel/security/ima/policy", O_WRONLY); if (fd_policy >= 0) { const char *policy = "measure func=FILE_CHECK\n"; if (write(fd_policy, policy, strlen(policy)) < 0) { printf("[-] Failed to write IMA policy: %s\n", strerror(errno)); } else { printf("[+] Wrote IMA policy.\n"); } close(fd_policy); } else { printf("[-] Failed to open IMA policy: %s\n", strerror(errno)); } mkdir("/dev/binderfs", 0777); if (mount("binder", "/dev/binderfs", "binder", 0, "") == 0) { printf("[+] Mounted binderfs\n"); } mkdir("/tmp/tracefs", 0777); if (mount("tracefs", "/tmp/tracefs", "tracefs", 0, "") == 0) { printf("[+] Mounted tracefs\n"); } const char *files[] = { "/dev/binderfs/binder_logs/stats", "/tmp/tracefs/trace", "/sys/kernel/tracing/trace" }; for (int i = 0; i < 3; i++) { int fd4 = open(files[i], O_RDONLY); if (fd4 >= 0) { printf("[+] Opened %s\n", files[i]); char buf4[64]; if (read(fd4, buf4, sizeof(buf4)) < 0) {} close(fd4); } } printf("[+] Step 4 successful.\n"); // Step 3: cb_lock -> ima_iint_mutex_key printf("[*] Step 3: send NL80211_CMD_RELOAD_REGDB\n"); int nl80211_id = resolve_nl80211(); if (nl80211_id >= 0) { int fd3 = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd3 >= 0) { struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; setsockopt(fd3, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; if (bind(fd3, (struct sockaddr *)&sa, sizeof(sa)) == 0) { struct { struct nlmsghdr nlh; struct genlmsghdr gnlh; } req = { .nlh = { .nlmsg_len = sizeof(req), .nlmsg_type = nl80211_id, .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, }, .gnlh = { .cmd = NL80211_CMD_RELOAD_REGDB, .version = 1, } }; if (send(fd3, &req, sizeof(req), 0) >= 0) { char buf3[4096]; if (recv(fd3, buf3, sizeof(buf3), 0) < 0) { // Do not exit here, the command might fail but the dependency is already recorded. } printf("[+] Step 3 successful.\n"); } else { printf("[-] send NL80211_CMD_RELOAD_REGDB failed: %s\n", strerror(errno)); } } else { printf("[-] bind AF_NETLINK failed: %s\n", strerror(errno)); } close(fd3); } else { printf("[-] socket AF_NETLINK failed: %s\n", strerror(errno)); } } // Step 2: inode_lock -> cb_lock printf("[*] Step 2: socket and close netlink\n"); int fd2 = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (fd2 < 0) { printf("[-] socket AF_NETLINK failed: %s\n", strerror(errno)); } else { if (close(fd2) < 0) { printf("[-] close netlink socket failed: %s\n", strerror(errno)); } else { printf("[+] Step 2 successful.\n"); } } // Step 1: nvmf_dev_mutex -> inode_lock printf("[*] Step 1: write to /dev/nvme-fabrics with exit race\n"); srand(time(NULL)); for (int i = 0; i < 200; i++) { pid_t pid = fork(); if (pid < 0) { printf("[-] fork failed: %s\n", strerror(errno)); break; } if (pid == 0) { pthread_t t; sync_flag = 0; if (pthread_create(&t, NULL, write_thread, NULL) != 0) { exit(1); } // Wait until the thread opens the file and is about to write int timeout = 10000; while (sync_flag == 0 && timeout > 0) { usleep(10); timeout--; } // Randomize sleep to hit the race window where task is exiting during write() usleep(rand() % 5000); syscall(SYS_exit_group, 0); } else { waitpid(pid, NULL, 0); } } printf("[+] Step 1 successful.\n"); // Sleep a bit to allow async tasks to finish and lockdep to print sleep(2); return 0; }