// https://syzkaller.appspot.com/bug?id=139b66f19289d2bfa6d16ae337da0b4260e28692 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #define USERIO_CMD_REGISTER 0 #define USERIO_CMD_SET_PORT_TYPE 1 #define USERIO_CMD_SEND_INTERRUPT 2 struct userio_cmd { uint8_t type; uint8_t data; } __attribute__((__packed__)); volatile int stop = 0; volatile int start_spam = 0; void write_file(const char *path, const char *val) { int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] Failed to open %s: %s\n", path, strerror(errno)); exit(1); } if (write(fd, val, strlen(val)) < 0) { printf("[-] Failed to write to %s: %s\n", path, strerror(errno)); exit(1); } if (close(fd) < 0) { printf("[-] Failed to close %s: %s\n", path, strerror(errno)); exit(1); } } void init_fault_injection() { write_file("/sys/kernel/debug/failslab/cache-filter", "N\n"); write_file("/sys/kernel/debug/failslab/task-filter", "N\n"); write_file("/sys/kernel/debug/failslab/ignore-gfp-wait", "N\n"); write_file("/sys/kernel/debug/failslab/times", "1000000\n"); printf("[+] Configured fault injection parameters\n"); } void set_fault_injection(int prob) { char buf[16]; snprintf(buf, sizeof(buf), "%d\n", prob); write_file("/sys/kernel/debug/failslab/probability", buf); } void *interrupt_thread(void *arg) { int fd = (int)(intptr_t)arg; struct userio_cmd cmd; cmd.type = USERIO_CMD_SEND_INTERRUPT; cmd.data = 0; while (!__atomic_load_n(&start_spam, __ATOMIC_ACQUIRE)) { sched_yield(); } while (!__atomic_load_n(&stop, __ATOMIC_ACQUIRE)) { ssize_t res = write(fd, &cmd, sizeof(cmd)); if (res < 0 && errno != EINTR && errno != ENODEV) { // Ignore write errors to avoid spamming console } sched_yield(); } return NULL; } int main() { printf("[+] Starting reproducer...\n"); init_fault_injection(); printf("[+] Fault injection initialized.\n"); pthread_t tids[4]; for (int iter = 0; iter < 2000; iter++) { set_fault_injection(0); int fd = open("/dev/userio", O_RDWR); if (fd < 0) { printf("[-] Failed to open /dev/userio: %s\n", strerror(errno)); exit(1); } struct userio_cmd cmd; cmd.type = USERIO_CMD_SET_PORT_TYPE; cmd.data = 3; // SERIO_HIL_MLC if (write(fd, &cmd, sizeof(cmd)) < 0) { printf("[-] Failed to write USERIO_CMD_SET_PORT_TYPE: %s\n", strerror(errno)); exit(1); } __atomic_store_n(&stop, 0, __ATOMIC_RELEASE); __atomic_store_n(&start_spam, 0, __ATOMIC_RELEASE); cmd.type = USERIO_CMD_REGISTER; cmd.data = 0; if (write(fd, &cmd, sizeof(cmd)) < 0) { printf("[-] Failed to write USERIO_CMD_REGISTER: %s\n", strerror(errno)); exit(1); } for (int i = 0; i < 4; i++) { if (pthread_create(&tids[i], NULL, interrupt_thread, (void *)(intptr_t)fd) != 0) { printf("[-] Failed to create interrupt_thread: %s\n", strerror(errno)); exit(1); } } // Enable fault injection immediately after REGISTER set_fault_injection(1); // 1% probability // Tell threads to start spamming interrupts __atomic_store_n(&start_spam, 1, __ATOMIC_RELEASE); // Wait to allow workqueue to process REGISTER and RESCAN usleep(50000); // Disable fault injection before joining and closing set_fault_injection(0); __atomic_store_n(&stop, 1, __ATOMIC_RELEASE); for (int i = 0; i < 4; i++) { if (pthread_join(tids[i], NULL) != 0) { printf("[-] Failed to join interrupt_thread: %s\n", strerror(errno)); exit(1); } } if (close(fd) < 0) { printf("[-] Failed to close /dev/userio: %s\n", strerror(errno)); exit(1); } if (iter % 100 == 0) { printf("[+] Completed iteration %d\n", iter); } } printf("[+] Done. Waiting for asynchronous events...\n"); sleep(2); return 0; }