// https://syzkaller.appspot.com/bug?id=13233e6b4dfcfd19caefeb2f9191b3b3fb9acecf #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #ifndef TCSETS #define TCSETS 0x5402 #endif int master_fd, slave_fd; int stop_flag = 0; void *writer_thread(void *arg) { char write_buf[8192]; char read_buf[8192]; memset(write_buf, 'a', sizeof(write_buf)); while (!__atomic_load_n(&stop_flag, __ATOMIC_ACQUIRE)) { /* * Write to master to fill the flip buffers and trigger flush_to_ldisc. * Read from slave to free up receive_room, allowing the cycle to repeat. */ write(master_fd, write_buf, sizeof(write_buf)); read(slave_fd, read_buf, sizeof(read_buf)); } return NULL; } void *ioctl_thread(void *arg) { struct termios t1, t2; if (tcgetattr(slave_fd, &t1) < 0) { printf("[-] tcgetattr failed: %s\n", strerror(errno)); return NULL; } t1.c_iflag |= IXON; /* Enable flow control lookahead */ t1.c_lflag &= ~ICANON; /* Disable canonical mode to allow lookahead */ t2 = t1; t1.c_cc[VSTART] = 0x11; t2.c_cc[VSTART] = 0xaf; while (!__atomic_load_n(&stop_flag, __ATOMIC_ACQUIRE)) { /* * Continuously overwrite tty->termios using the TCSETS ioctl directly. * This avoids glibc's tcsetattr() which might use TCGETS or TCSETS2, * ensuring we hit the exact tty_set_termios() write path. */ ioctl(slave_fd, TCSETS, &t1); ioctl(slave_fd, TCSETS, &t2); } return NULL; } int main() { master_fd = posix_openpt(O_RDWR | O_NOCTTY); if (master_fd < 0) { printf("[-] posix_openpt failed: %s\n", strerror(errno)); exit(1); } if (grantpt(master_fd) < 0) { printf("[-] grantpt failed: %s\n", strerror(errno)); exit(1); } if (unlockpt(master_fd) < 0) { printf("[-] unlockpt failed: %s\n", strerror(errno)); exit(1); } char *slave_name = ptsname(master_fd); if (!slave_name) { printf("[-] ptsname failed: %s\n", strerror(errno)); exit(1); } slave_fd = open(slave_name, O_RDWR | O_NOCTTY); if (slave_fd < 0) { printf("[-] open slave failed: %s\n", strerror(errno)); exit(1); } /* Make both ends non-blocking to prevent deadlocks during aggressive I/O */ int flags = fcntl(master_fd, F_GETFL, 0); if (fcntl(master_fd, F_SETFL, flags | O_NONBLOCK) < 0) { printf("[-] fcntl master failed: %s\n", strerror(errno)); exit(1); } flags = fcntl(slave_fd, F_GETFL, 0); if (fcntl(slave_fd, F_SETFL, flags | O_NONBLOCK) < 0) { printf("[-] fcntl slave failed: %s\n", strerror(errno)); exit(1); } printf("[+] Setup complete, starting threads...\n"); pthread_t t_writers[2]; pthread_t t_ioctls[2]; for (int i = 0; i < 2; i++) { if (pthread_create(&t_writers[i], NULL, writer_thread, NULL) != 0) { printf("[-] pthread_create writer failed: %s\n", strerror(errno)); exit(1); } if (pthread_create(&t_ioctls[i], NULL, ioctl_thread, NULL) != 0) { printf("[-] pthread_create ioctl failed: %s\n", strerror(errno)); exit(1); } } /* Run for a few seconds to trigger KCSAN */ sleep(5); __atomic_store_n(&stop_flag, 1, __ATOMIC_RELEASE); for (int i = 0; i < 2; i++) { pthread_join(t_writers[i], NULL); pthread_join(t_ioctls[i], NULL); } close(slave_fd); close(master_fd); printf("[+] Reproducer finished.\n"); return 0; }