// https://syzkaller.appspot.com/bug?id=012540f44f7d4617f72856cd8b97753580d84d1c #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef NVME_IOCTL_ID #define NVME_IOCTL_ID _IO('N', 0x40) #endif #ifndef NVME_IOCTL_ADMIN_CMD #define NVME_IOCTL_ADMIN_CMD _IOWR('N', 0x41, struct nvme_passthru_cmd) #endif #ifndef NVME_IOCTL_SUBMIT_IO #define NVME_IOCTL_SUBMIT_IO _IOW('N', 0x42, struct nvme_user_io) #endif #ifndef NVME_IOCTL_RESET #define NVME_IOCTL_RESET _IO('N', 0x44) #endif #ifndef NVME_IOCTL_SUBSYS_RESET #define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45) #endif #ifndef NVME_IOCTL_RESCAN #define NVME_IOCTL_RESCAN _IO('N', 0x46) #endif static volatile int stop_threads = 0; static char g_pci_dev_name[64] = "0000:00:02.0"; static char g_pci_reset_path[256] = ""; static char g_pci_config_path[256] = ""; static char g_sysfs_reset_path[256] = ""; static char g_nvme_ctrl_dev[64] = "/dev/nvme0"; static char g_nvme_ns_dev[64] = "/dev/nvme0n1"; static int ensure_dev_node(const char *devpath, const char *sysfs_dev_file, mode_t type, bool verbose) { struct stat st; if (stat(devpath, &st) == 0) { if (verbose) printf("[+] Device node %s already exists.\n", devpath); return 0; } FILE *f = fopen(sysfs_dev_file, "r"); if (!f) { if (verbose) printf("[-] Failed to fopen %s: %s\n", sysfs_dev_file, strerror(errno)); return -1; } unsigned int major = 0, minor = 0; if (fscanf(f, "%u:%u", &major, &minor) != 2) { if (verbose) printf("[-] Failed to parse major:minor from %s\n", sysfs_dev_file); fclose(f); return -1; } fclose(f); unlink(devpath); int res = mknod(devpath, type | 0660, makedev(major, minor)); if (res < 0 && errno != EEXIST) { if (verbose) printf("[-] Failed to mknod %s (type 0%o, %u:%u): %s\n", devpath, type, major, minor, strerror(errno)); return -1; } chmod(devpath, 0666); if (verbose) printf("[+] Device node %s ready (major:minor %u:%u).\n", devpath, major, minor); return 0; } static int locate_nvme_entities(char *ctrl_name, size_t ctrl_sz, char *ns_name, size_t ns_sz) { DIR *nd = opendir("/sys/class/nvme"); if (!nd) return -1; struct dirent *de; ctrl_name[0] = '\0'; while ((de = readdir(nd)) != NULL) { if (de->d_name[0] == '.') continue; if (strncmp(de->d_name, "nvme", 4) == 0 && isdigit((unsigned char)de->d_name[4]) && !strchr(de->d_name + 4, 'n')) { snprintf(ctrl_name, ctrl_sz, "%s", de->d_name); break; } } closedir(nd); if (ctrl_name[0] == '\0') return -1; char state_path[256]; snprintf(state_path, sizeof(state_path), "/sys/class/nvme/%s/state", ctrl_name); int s_fd = open(state_path, O_RDONLY); if (s_fd >= 0) { char state_buf[32] = {0}; ssize_t r = read(s_fd, state_buf, sizeof(state_buf) - 1); close(s_fd); if (r > 0) { if (strstr(state_buf, "dead") || strstr(state_buf, "deleting")) { return -1; } } } ns_name[0] = '\0'; DIR *bd = opendir("/sys/class/block"); if (bd) { while ((de = readdir(bd)) != NULL) { if (de->d_name[0] == '.') continue; if (strncmp(de->d_name, ctrl_name, strlen(ctrl_name)) == 0 && strstr(de->d_name, "n1")) { snprintf(ns_name, ns_sz, "%s", de->d_name); break; } } closedir(bd); } if (ns_name[0] == '\0') { snprintf(ns_name, ns_sz, "%sn1", ctrl_name); } return 0; } static void trigger_pci_rescan(void) { int fd = open("/sys/bus/pci/rescan", O_WRONLY); if (fd >= 0) { write(fd, "1\n", 2); close(fd); } } static void *ns_io_worker(void *arg) { (void)arg; char *buf = NULL; if (posix_memalign((void **)&buf, 4096, 65536) != 0 || !buf) return NULL; memset(buf, 0x5a, 65536); int fd = -1; uint64_t lba = 0; while (!stop_threads) { if (fd < 0) { fd = open(g_nvme_ns_dev, O_RDWR | O_DIRECT); if (fd < 0) { fd = open(g_nvme_ns_dev, O_RDWR); if (fd < 0) { usleep(1000); continue; } } } pread(fd, buf, 4096, (off_t)(lba * 512)); pwrite(fd, buf, 4096, (off_t)(lba * 512)); struct nvme_user_io uio; memset(&uio, 0, sizeof(uio)); uio.opcode = (lba & 1) ? 1 : 2; /* 1 = write, 2 = read */ uio.nblocks = (uint16_t)(lba % 16); uio.addr = (uint64_t)(uintptr_t)buf; uio.slba = lba; if (ioctl(fd, NVME_IOCTL_SUBMIT_IO, &uio) < 0) { if (errno == EBADF || errno == ENXIO || errno == EIO || errno == ENODEV) { close(fd); fd = -1; } } lba = (lba + 8) % 2048; } if (fd >= 0) close(fd); free(buf); return NULL; } static void *pci_config_race_worker(void *arg) { (void)arg; int cfd = -1; /* * Value 0x0201: clears Memory Space Enable (bit 1) in PCI_COMMAND (offset 4). * Continuously overwrites PCI_COMMAND so readl(CSTS) immediately following * pci_enable_device_mem() returns -1 (0xffffffff), forcing -ENODEV (-19). */ uint16_t cmd = 0x0201; while (!stop_threads) { if (cfd < 0) { cfd = open(g_pci_config_path, O_RDWR); if (cfd < 0) { usleep(1000); continue; } } for (int i = 0; i < 500 && !stop_threads; i++) { pwrite(cfd, &cmd, sizeof(cmd), 4); } sched_yield(); } if (cfd >= 0) close(cfd); return NULL; } static void *reset_worker(void *arg) { (void)arg; int ctrl_fd = -1; int sfd = -1; while (!stop_threads) { if (ctrl_fd < 0) ctrl_fd = open(g_nvme_ctrl_dev, O_RDWR); if (ctrl_fd >= 0) { if (ioctl(ctrl_fd, NVME_IOCTL_RESET) < 0) { if (errno == ENODEV || errno == ENXIO || errno == EBADF) { close(ctrl_fd); ctrl_fd = -1; } } else { ioctl(ctrl_fd, NVME_IOCTL_SUBSYS_RESET); } } if (sfd < 0 && g_sysfs_reset_path[0]) sfd = open(g_sysfs_reset_path, O_WRONLY); if (sfd >= 0) { if (write(sfd, "1\n", 2) < 0) { close(sfd); sfd = -1; } } usleep(500); } if (ctrl_fd >= 0) close(ctrl_fd); if (sfd >= 0) close(sfd); return NULL; } static void *pci_function_reset_worker(void *arg) { (void)arg; int rfd = -1; while (!stop_threads) { if (rfd < 0 && g_pci_reset_path[0]) rfd = open(g_pci_reset_path, O_WRONLY); if (rfd >= 0) { if (write(rfd, "1\n", 2) < 0) { close(rfd); rfd = -1; } } usleep(2000); } if (rfd >= 0) close(rfd); return NULL; } static void *watchdog_worker(void *arg) { (void)arg; while (!stop_threads) { char ctrl_name[64] = {0}; char ns_name[64] = {0}; int ret = locate_nvme_entities(ctrl_name, sizeof(ctrl_name), ns_name, sizeof(ns_name)); if (ret == 0) { char ctrl_sys[256]; snprintf(ctrl_sys, sizeof(ctrl_sys), "/sys/class/nvme/%s/dev", ctrl_name); ensure_dev_node(g_nvme_ctrl_dev, ctrl_sys, S_IFCHR, false); char ns_sys[256]; snprintf(ns_sys, sizeof(ns_sys), "/sys/class/block/%s/dev", ns_name); if (access(ns_sys, F_OK) != 0) snprintf(ns_sys, sizeof(ns_sys), "/sys/class/nvme/%s/%s/dev", ctrl_name, ns_name); ensure_dev_node(g_nvme_ns_dev, ns_sys, S_IFBLK, false); } else { char remove_path[256]; snprintf(remove_path, sizeof(remove_path), "/sys/bus/pci/devices/%s/remove", g_pci_dev_name); int rm_fd = open(remove_path, O_WRONLY); if (rm_fd >= 0) { write(rm_fd, "1\n", 2); close(rm_fd); } trigger_pci_rescan(); usleep(50000); char enable_path[256]; snprintf(enable_path, sizeof(enable_path), "/sys/bus/pci/devices/%s/enable", g_pci_dev_name); int efd = open(enable_path, O_WRONLY); if (efd >= 0) { write(efd, "1\n", 2); close(efd); } int bfd = open("/sys/bus/pci/drivers/nvme/bind", O_WRONLY); if (bfd >= 0) { write(bfd, g_pci_dev_name, strlen(g_pci_dev_name)); close(bfd); } } usleep(25000); } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting NVMe blk_mq_complete_request_remote crash reproducer...\n"); /* Step 1: Trigger PCI rescan */ int rfd = open("/sys/bus/pci/rescan", O_WRONLY); if (rfd < 0) { printf("[-] Failed to open /sys/bus/pci/rescan: %s\n", strerror(errno)); exit(1); } ssize_t w = write(rfd, "1\n", 2); if (w < 0) { printf("[-] Failed to write to /sys/bus/pci/rescan: %s\n", strerror(errno)); close(rfd); exit(1); } close(rfd); printf("[+] Successfully triggered /sys/bus/pci/rescan.\n"); usleep(100000); /* Step 2: Locate NVMe PCI device */ DIR *pci_dir = opendir("/sys/bus/pci/devices"); if (!pci_dir) { printf("[-] Failed to opendir /sys/bus/pci/devices: %s\n", strerror(errno)); exit(1); } int pci_found = 0; struct dirent *de; while ((de = readdir(pci_dir)) != NULL) { if (de->d_name[0] == '.') continue; char class_path[256]; snprintf(class_path, sizeof(class_path), "/sys/bus/pci/devices/%s/class", de->d_name); FILE *cf = fopen(class_path, "r"); if (cf) { unsigned int pci_class = 0; if (fscanf(cf, "0x%x", &pci_class) == 1) { if ((pci_class & 0xffff00) == 0x010800 || strcmp(de->d_name, "0000:00:02.0") == 0) { snprintf(g_pci_dev_name, sizeof(g_pci_dev_name), "%s", de->d_name); pci_found = 1; fclose(cf); break; } } fclose(cf); } } closedir(pci_dir); if (!pci_found) { printf("[-] Failed to find NVMe PCI device: %s\n", strerror(ENOENT)); exit(1); } printf("[+] Found NVMe PCI device: %s\n", g_pci_dev_name); /* Step 3: Setup PCI reset and config space paths */ snprintf(g_pci_reset_path, sizeof(g_pci_reset_path), "/sys/bus/pci/devices/%s/reset", g_pci_dev_name); snprintf(g_pci_config_path, sizeof(g_pci_config_path), "/sys/bus/pci/devices/%s/config", g_pci_dev_name); /* Step 4: Ensure device is enabled and bound to nvme driver */ char driver_link[256]; snprintf(driver_link, sizeof(driver_link), "/sys/bus/pci/devices/%s/driver", g_pci_dev_name); if (access(driver_link, F_OK) != 0) { printf("[*] PCI device %s is unbound. Enabling and binding to nvme driver...\n", g_pci_dev_name); char enable_path[256]; snprintf(enable_path, sizeof(enable_path), "/sys/bus/pci/devices/%s/enable", g_pci_dev_name); int efd = open(enable_path, O_WRONLY); if (efd >= 0) { write(efd, "1\n", 2); close(efd); } int bfd = open("/sys/bus/pci/drivers/nvme/bind", O_WRONLY); if (bfd >= 0) { write(bfd, g_pci_dev_name, strlen(g_pci_dev_name)); close(bfd); } trigger_pci_rescan(); } else { printf("[+] PCI device %s is bound to nvme driver.\n", g_pci_dev_name); } /* Step 5: Locate NVMe controller and namespace entities */ char ctrl_name[64] = {0}; char ns_name[64] = {0}; int located = 0; for (int i = 0; i < 50; i++) { if (locate_nvme_entities(ctrl_name, sizeof(ctrl_name), ns_name, sizeof(ns_name)) == 0) { located = 1; break; } trigger_pci_rescan(); usleep(100000); } if (!located) { printf("[-] Failed to locate NVMe controller under /sys/class/nvme: %s\n", strerror(ENODEV)); exit(1); } printf("[+] Located NVMe controller: %s, namespace: %s\n", ctrl_name, ns_name); snprintf(g_nvme_ctrl_dev, sizeof(g_nvme_ctrl_dev), "/dev/%s", ctrl_name); snprintf(g_nvme_ns_dev, sizeof(g_nvme_ns_dev), "/dev/%s", ns_name); snprintf(g_sysfs_reset_path, sizeof(g_sysfs_reset_path), "/sys/class/nvme/%s/reset_controller", ctrl_name); /* Step 6: Create/verify controller character device node */ char ctrl_sys_dev[256]; snprintf(ctrl_sys_dev, sizeof(ctrl_sys_dev), "/sys/class/nvme/%s/dev", ctrl_name); int res = ensure_dev_node(g_nvme_ctrl_dev, ctrl_sys_dev, S_IFCHR, true); if (res < 0) { printf("[-] Failed to ensure controller node %s: %s\n", g_nvme_ctrl_dev, strerror(errno)); exit(1); } /* Step 7: Create/verify namespace block device node */ char ns_sys_dev[256]; snprintf(ns_sys_dev, sizeof(ns_sys_dev), "/sys/class/block/%s/dev", ns_name); if (access(ns_sys_dev, F_OK) != 0) { snprintf(ns_sys_dev, sizeof(ns_sys_dev), "/sys/class/nvme/%s/%s/dev", ctrl_name, ns_name); } res = ensure_dev_node(g_nvme_ns_dev, ns_sys_dev, S_IFBLK, true); if (res < 0) { printf("[-] Failed to ensure namespace block node %s: %s\n", g_nvme_ns_dev, strerror(errno)); exit(1); } /* Step 8: Verify opening controller and executing test Identify command */ int test_ctrl_fd = open(g_nvme_ctrl_dev, O_RDWR); if (test_ctrl_fd < 0) { printf("[-] Failed to open controller %s: %s\n", g_nvme_ctrl_dev, strerror(errno)); exit(1); } printf("[+] Successfully opened controller %s (fd=%d).\n", g_nvme_ctrl_dev, test_ctrl_fd); void *id_buf = NULL; if (posix_memalign(&id_buf, 4096, 4096) != 0 || !id_buf) { printf("[-] Failed to allocate 4KB aligned memory: %s\n", strerror(errno)); close(test_ctrl_fd); exit(1); } memset(id_buf, 0, 4096); struct nvme_passthru_cmd id_cmd; memset(&id_cmd, 0, sizeof(id_cmd)); id_cmd.opcode = 0x06; /* Identify */ id_cmd.nsid = 0; id_cmd.addr = (uint64_t)(uintptr_t)id_buf; id_cmd.data_len = 4096; id_cmd.cdw10 = 1; /* CNS = 1: Identify Controller */ res = ioctl(test_ctrl_fd, NVME_IOCTL_ADMIN_CMD, &id_cmd); if (res < 0) { printf("[-] Initial NVME_IOCTL_ADMIN_CMD failed: %s\n", strerror(errno)); free(id_buf); close(test_ctrl_fd); exit(1); } printf("[+] Initial NVME_IOCTL_ADMIN_CMD successful.\n"); free(id_buf); close(test_ctrl_fd); /* Step 9: Verify opening namespace and reading 512 bytes */ int test_ns_fd = open(g_nvme_ns_dev, O_RDONLY); if (test_ns_fd < 0) { printf("[-] Failed to open namespace %s: %s\n", g_nvme_ns_dev, strerror(errno)); exit(1); } char test_read_buf[512]; ssize_t read_bytes = pread(test_ns_fd, test_read_buf, sizeof(test_read_buf), 0); if (read_bytes < 0) { printf("[-] Initial pread on namespace %s failed: %s\n", g_nvme_ns_dev, strerror(errno)); close(test_ns_fd); exit(1); } printf("[+] Initial pread on namespace %s read %zd bytes successfully.\n", g_nvme_ns_dev, read_bytes); close(test_ns_fd); /* Step 10: Verify PCI config space accessibility */ int test_cfg = open(g_pci_config_path, O_RDWR); if (test_cfg < 0) { printf("[-] Failed to open %s: %s\n", g_pci_config_path, strerror(errno)); exit(1); } printf("[+] Successfully opened PCI config space %s.\n", g_pci_config_path); close(test_cfg); /* Step 11: Spawn concurrent race worker threads */ pthread_t threads[9]; for (int i = 0; i < 4; i++) { int p_res = pthread_create(&threads[i], NULL, ns_io_worker, NULL); if (p_res != 0) { printf("[-] Failed to create ns_io_worker thread %d: %s\n", i, strerror(p_res)); exit(1); } } int p_res = pthread_create(&threads[4], NULL, pci_config_race_worker, NULL); if (p_res != 0) { printf("[-] Failed to create pci_config_race_worker thread 0: %s\n", strerror(p_res)); exit(1); } p_res = pthread_create(&threads[5], NULL, pci_config_race_worker, NULL); if (p_res != 0) { printf("[-] Failed to create pci_config_race_worker thread 1: %s\n", strerror(p_res)); exit(1); } p_res = pthread_create(&threads[6], NULL, reset_worker, NULL); if (p_res != 0) { printf("[-] Failed to create reset_worker thread: %s\n", strerror(p_res)); exit(1); } p_res = pthread_create(&threads[7], NULL, pci_function_reset_worker, NULL); if (p_res != 0) { printf("[-] Failed to create pci_function_reset_worker thread: %s\n", strerror(p_res)); exit(1); } p_res = pthread_create(&threads[8], NULL, watchdog_worker, NULL); if (p_res != 0) { printf("[-] Failed to create watchdog_worker thread: %s\n", strerror(p_res)); exit(1); } printf("[+] All 9 worker threads spawned successfully. Racing controller reset, I/O, and PCI config...\n"); /* Step 12: Run race condition attack */ for (int s = 1; s <= 45; s++) { sleep(1); printf("[*] Race in progress... %d/45 seconds elapsed.\n", s); } /* Step 13: Clean shutdown */ printf("[*] Stopping race worker threads...\n"); stop_threads = 1; for (int i = 0; i < 9; i++) { pthread_join(threads[i], NULL); } printf("[+] All worker threads stopped successfully.\n"); printf("[+] Reproducer finished execution.\n"); return 0; }