// https://syzkaller.appspot.com/bug?id=0986e0af6fdc7261ad39cd60a928ee075cea78df #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIOCSPTLCK _IOW('T', 0x40, int) #define TIOCGPTN _IOR('T', 0x30, unsigned int) void trigger_tcsets() { int fd_serial = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd_serial < 0) fd_serial = open("/dev/ttyS1", O_RDWR | O_NOCTTY); if (fd_serial < 0) fd_serial = open("/dev/ttyS2", O_RDWR | O_NOCTTY); if (fd_serial < 0) fd_serial = open("/dev/ttyS3", O_RDWR | O_NOCTTY); if (fd_serial >= 0) { struct termios term; if (ioctl(fd_serial, TCGETS, &term) == 0) { if (ioctl(fd_serial, TCSETS, &term) < 0) { printf("[-] TCSETS failed: %s\n", strerror(errno)); } else { printf("[+] trigger_tcsets successful.\n"); } } else { printf("[-] TCGETS failed: %s\n", strerror(errno)); } close(fd_serial); } else { printf("[-] trigger_tcsets failed to open serial port: %s\n", strerror(errno)); } } void trigger_sak() { int vt_num = 5; int fd_tty = open("/dev/tty5", O_RDWR | O_NOCTTY); if (fd_tty < 0) { vt_num = 0; fd_tty = open("/dev/tty0", O_RDWR | O_NOCTTY); } if (fd_tty < 0) { printf("[-] trigger_sak failed to open tty: %s\n", strerror(errno)); exit(1); } if (vt_num != 0) { if (ioctl(fd_tty, VT_ACTIVATE, vt_num) < 0) { printf("[-] VT_ACTIVATE failed: %s\n", strerror(errno)); } if (ioctl(fd_tty, VT_WAITACTIVE, vt_num) < 0) { printf("[-] VT_WAITACTIVE failed: %s\n", strerror(errno)); } printf("[+] Activated /dev/tty%d.\n", vt_num); } // Map KEY_A to K_SAK across all keymaps to ensure it triggers for (int i = 0; i < 256; i++) { struct kbentry ke; ke.kb_table = i; ke.kb_index = 30; // KEY_A ke.kb_value = 0x020F; // K_SAK if (ioctl(fd_tty, KDSKBENT, &ke) < 0) { // Some keymaps might not exist, ignore errors } } printf("[+] trigger_sak mapped KEY_A to K_SAK.\n"); int ufd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); if (ufd >= 0) { if (ioctl(ufd, UI_SET_EVBIT, EV_KEY) < 0) { printf("[-] UI_SET_EVBIT failed: %s\n", strerror(errno)); } if (ioctl(ufd, UI_SET_KEYBIT, KEY_A) < 0) { printf("[-] UI_SET_KEYBIT failed: %s\n", strerror(errno)); } struct uinput_setup usetup; memset(&usetup, 0, sizeof(usetup)); usetup.id.bustype = BUS_USB; usetup.id.vendor = 0x1234; usetup.id.product = 0x5678; strcpy(usetup.name, "Virtual Keyboard"); if (ioctl(ufd, UI_DEV_SETUP, &usetup) < 0) { printf("[-] UI_DEV_SETUP failed: %s, falling back to uinput_user_dev\n", strerror(errno)); struct uinput_user_dev uud; memset(&uud, 0, sizeof(uud)); uud.id.bustype = BUS_USB; uud.id.vendor = 0x1234; uud.id.product = 0x5678; strcpy(uud.name, "Virtual Keyboard"); if (write(ufd, &uud, sizeof(uud)) < 0) { printf("[-] write uinput_user_dev failed: %s\n", strerror(errno)); } } if (ioctl(ufd, UI_DEV_CREATE) < 0) { printf("[-] UI_DEV_CREATE failed: %s\n", strerror(errno)); } usleep(100000); // Wait for input device to be registered struct input_event ie; // Press KEY_A memset(&ie, 0, sizeof(ie)); ie.type = EV_KEY; ie.code = KEY_A; ie.value = 1; if (write(ufd, &ie, sizeof(ie)) < 0) { printf("[-] write EV_KEY failed: %s\n", strerror(errno)); } memset(&ie, 0, sizeof(ie)); ie.type = EV_SYN; ie.code = SYN_REPORT; ie.value = 0; if (write(ufd, &ie, sizeof(ie)) < 0) { printf("[-] write EV_SYN failed: %s\n", strerror(errno)); } printf("[+] trigger_sak injected KEY_A.\n"); // Wait for vc_SAK workqueue to execute and kill us sleep(2); // If we survive: ioctl(ufd, UI_DEV_DESTROY); close(ufd); } else { printf("[-] trigger_sak failed to open /dev/uinput: %s\n", strerror(errno)); exit(1); } close(fd_tty); } void trigger_pty() { int ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY); if (ptmx >= 0) { int unlock = 0; if (ioctl(ptmx, TIOCSPTLCK, &unlock) < 0) { printf("[-] TIOCSPTLCK failed: %s\n", strerror(errno)); } unsigned int ptn; if (ioctl(ptmx, TIOCGPTN, &ptn) == 0) { char pts_name[32]; snprintf(pts_name, sizeof(pts_name), "/dev/pts/%u", ptn); int pts = open(pts_name, O_RDWR | O_NOCTTY); if (pts >= 0) { if (write(ptmx, "a", 1) < 0) { printf("[-] trigger_pty failed to write to ptmx: %s\n", strerror(errno)); } else { printf("[+] trigger_pty wrote to ptmx.\n"); } usleep(100000); // Wait for flush_to_ldisc workqueue to execute close(pts); } else { printf("[-] trigger_pty failed to open %s: %s\n", pts_name, strerror(errno)); } } else { printf("[-] TIOCGPTN failed: %s\n", strerror(errno)); } close(ptmx); } else { printf("[-] trigger_pty failed to open /dev/ptmx: %s\n", strerror(errno)); exit(1); } } int main(void) { printf("[+] Starting reproducer...\n"); // 1. Trigger &tty->termios_rwsem -> &port->mutex trigger_tcsets(); // 2. Trigger console_lock -> &buf->lock // Do this in a child process because __do_SAK sends SIGKILL to processes // associated with the active console. pid_t pid = fork(); if (pid < 0) { printf("[-] fork failed: %s\n", strerror(errno)); exit(1); } if (pid == 0) { trigger_sak(); exit(0); } int status; waitpid(pid, &status, 0); if (WIFSIGNALED(status)) { printf("[+] Child process for SAK killed by signal %d (expected if SAK triggered).\n", WTERMSIG(status)); } else { printf("[+] Child process for SAK exited normally.\n"); } // 3. Trigger &buf->lock -> &tty->termios_rwsem // This completes the circular dependency and triggers the lockdep splat. trigger_pty(); printf("[+] Reproducer finished.\n"); return 0; }