// https://syzkaller.appspot.com/bug?id=4c1a20ab0b1d87a1c23d59126c13f89b2135d43a #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include int my_id; volatile int stop = 0; int write_file(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd < 0) { return -1; } int ret = write(fd, val, strlen(val)); close(fd); return ret; } void setup_nullb() { char path[256]; snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d", my_id); if (mkdir(path, 0777) < 0) { if (errno == EEXIST) { char p[256]; snprintf(p, sizeof(p), "/sys/kernel/config/nullb/nullb_%d/power", my_id); write_file(p, "0"); rmdir(path); if (mkdir(path, 0777) < 0) { printf("[-] Failed to mkdir %s: %s\n", path, strerror(errno)); exit(1); } } else { printf("[-] Failed to mkdir %s: %s\n", path, strerror(errno)); exit(1); } } snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d/size", my_id); if (write_file(path, "1024") < 0) { printf("[-] Failed to write size: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d/zoned", my_id); if (write_file(path, "1") < 0) { printf("[-] Failed to write zoned: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d/zone_size", my_id); if (write_file(path, "256") < 0) { printf("[-] Failed to write zone_size: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d/memory_backed", my_id); if (write_file(path, "1") < 0) { printf("[-] Failed to write memory_backed: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d/power", my_id); if (write_file(path, "1") < 0) { printf("[-] Failed to write power: %s\n", strerror(errno)); exit(1); } // Wait for the device to appear char dev_path[256]; snprintf(dev_path, sizeof(dev_path), "/dev/nullb_%d", my_id); int found = 0; for (int i = 0; i < 50; i++) { if (access(dev_path, F_OK) == 0) { found = 1; break; } usleep(100000); } if (!found) { printf("[-] Failed to find %s\n", dev_path); exit(1); } printf("[+] setup_nullb successful.\n"); } void teardown_nullb() { char path[256]; snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d/power", my_id); write_file(path, "0"); snprintf(path, sizeof(path), "/sys/kernel/config/nullb/nullb_%d", my_id); rmdir(path); printf("[+] teardown_nullb successful.\n"); } void setup_nvmet() { char path[256]; snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d", my_id); if (mkdir(path, 0777) < 0 && errno != EEXIST) { printf("[-] Failed to mkdir nvmet subsystem: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d/attr_allow_any_host", my_id); if (write_file(path, "1") < 0) { printf("[-] Failed to write attr_allow_any_host: %s\n", strerror(errno)); exit(1); } // Create 1 namespace snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d/namespaces/1", my_id); if (mkdir(path, 0777) < 0 && errno != EEXIST) { printf("[-] Failed to mkdir nvmet namespace: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d/namespaces/1/device_path", my_id); char dev_path[256]; snprintf(dev_path, sizeof(dev_path), "/dev/nullb_%d", my_id); if (write_file(path, dev_path) < 0) { printf("[-] Failed to write device_path: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d/namespaces/1/enable", my_id); if (write_file(path, "1") < 0) { printf("[-] Failed to enable namespace: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/ports/%d", my_id); if (mkdir(path, 0777) < 0 && errno != EEXIST) { printf("[-] Failed to mkdir nvmet port: %s\n", strerror(errno)); exit(1); } snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/ports/%d/addr_trtype", my_id); if (write_file(path, "loop") < 0) { printf("[-] Failed to write addr_trtype: %s\n", strerror(errno)); exit(1); } char symlink_path[256]; snprintf(symlink_path, sizeof(symlink_path), "/sys/kernel/config/nvmet/ports/%d/subsystems/test-subsys-%d", my_id, my_id); snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d", my_id); if (symlink(path, symlink_path) < 0 && errno != EEXIST) { printf("[-] Failed to symlink subsystem to port: %s\n", strerror(errno)); exit(1); } printf("[+] setup_nvmet successful.\n"); } void teardown_nvmet() { char path[256]; char symlink_path[256]; snprintf(symlink_path, sizeof(symlink_path), "/sys/kernel/config/nvmet/ports/%d/subsystems/test-subsys-%d", my_id, my_id); unlink(symlink_path); snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/ports/%d", my_id); rmdir(path); snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d/namespaces/1/enable", my_id); write_file(path, "0"); snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d/namespaces/1", my_id); rmdir(path); snprintf(path, sizeof(path), "/sys/kernel/config/nvmet/subsystems/test-subsys-%d", my_id); rmdir(path); printf("[+] teardown_nvmet successful.\n"); } int is_controller(const char *name) { if (strncmp(name, "nvme", 4) != 0) return 0; for (int i = 4; name[i] != '\0'; i++) { if (name[i] < '0' || name[i] > '9') return 0; } return 1; } int is_our_controller(const char *name) { if (!is_controller(name)) return 0; char path[256]; snprintf(path, sizeof(path), "/sys/class/nvme/%s/subsysnqn", name); int fd = open(path, O_RDONLY); if (fd < 0) return 0; char buf[256]; int n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n > 0) { buf[n] = '\0'; char expected[256]; snprintf(expected, sizeof(expected), "test-subsys-%d", my_id); if (strstr(buf, expected) != NULL) { return 1; } } return 0; } void *connect_thread(void *arg) { char cmd[256]; // Omit hostnqn and hostid to use defaults, and use duplicate_connect to allow multiple paths snprintf(cmd, sizeof(cmd), "transport=loop,nqn=test-subsys-%d,duplicate_connect", my_id); while (!stop) { int fd = open("/dev/nvme-fabrics", O_WRONLY); if (fd >= 0) { if (write(fd, cmd, strlen(cmd)) < 0) { // Ignore errors } close(fd); } usleep(10000); // Small delay } return NULL; } void *delete_thread(void *arg) { while (!stop) { DIR *dir = opendir("/sys/class/nvme"); if (dir) { struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (is_our_controller(ent->d_name)) { char path[256]; snprintf(path, sizeof(path), "/sys/class/nvme/%s/delete_controller", ent->d_name); write_file(path, "1"); } } closedir(dir); } usleep(15000); // Delete periodically } return NULL; } void *rescan_thread(void *arg) { while (!stop) { DIR *dir = opendir("/sys/class/nvme"); if (dir) { struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (is_our_controller(ent->d_name)) { char path[256]; snprintf(path, sizeof(path), "/sys/class/nvme/%s/rescan_controller", ent->d_name); write_file(path, "1"); } } closedir(dir); } usleep(10000); } return NULL; } int main() { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); srand(time(NULL) ^ getpid()); // Port ID must be a valid u16 (1-65535) my_id = rand() % 60000 + 1; // We don't check system() return values strictly because modules might already be loaded system("modprobe null_blk"); system("modprobe nvmet"); system("modprobe nvmet-loop"); system("modprobe nvme-fabrics"); setup_nullb(); time_t start = time(NULL); while (time(NULL) - start < 30) { setup_nvmet(); stop = 0; pthread_t t1, t2, t3, t4, t5; if (pthread_create(&t1, NULL, connect_thread, NULL) != 0) { printf("[-] Failed to create thread\n"); exit(1); } if (pthread_create(&t2, NULL, connect_thread, NULL) != 0) { printf("[-] Failed to create thread\n"); exit(1); } if (pthread_create(&t3, NULL, rescan_thread, NULL) != 0) { printf("[-] Failed to create thread\n"); exit(1); } if (pthread_create(&t4, NULL, delete_thread, NULL) != 0) { printf("[-] Failed to create thread\n"); exit(1); } if (pthread_create(&t5, NULL, delete_thread, NULL) != 0) { printf("[-] Failed to create thread\n"); exit(1); } sleep(2); // Run the race for 2 seconds per iteration stop = 1; pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_join(t5, NULL); // Final cleanup of controllers DIR *dir = opendir("/sys/class/nvme"); if (dir) { struct dirent *ent; while ((ent = readdir(dir)) != NULL) { if (is_our_controller(ent->d_name)) { char path[256]; snprintf(path, sizeof(path), "/sys/class/nvme/%s/delete_controller", ent->d_name); write_file(path, "1"); } } closedir(dir); } usleep(200000); teardown_nvmet(); } teardown_nullb(); printf("[+] Reproducer finished.\n"); return 0; }