DF-3408 / tun_eault_leak.c
/* * DF-3408 -- tunwrite() spare-mbuf leak on EFAULT (DF-3406 tap twin). * * tunwrite() (sys/net/tun/if_tun.c:843) assembles an mbuf chain: * * 870: MGETHDR(m, M_WAITOK, MT_DATA); * 875: while (error == 0 && uio->uio_resid > 0) { * 876: m->m_len = szmin(MHLEN, uio->uio_resid); * 877: error = uiomove(mtod(m, caddr_t), m->m_len, uio); * 878: *mp = m; * 879: mp = &m->m_next; * 880: if (uio->uio_resid > 0) * 881: MGET(m, M_WAITOK, MT_DATA); * 882: } * 883: if (error) { * 884: if (top) * 885: m_freem(top); <-- frees only the linked chain * * If uiomove() faults partway (resid still > 0), the conditional MGET on * line 880-881 allocates one more mbuf that is never linked (*mp = m at * line 878 runs only on the next loop iteration, which never happens * because the loop condition sees error != 0). m_freem(top) cannot reach * it. Exactly one mbuf leaks per faulted write. * * This PoC writes a 2-page buffer whose second page is PROT_NONE: the * first page copies fine, the second faults -> EFAULT with resid > 0. * * Measure with `netstat -m | head -1` before/after: "mbufs in use" * climbs by ~ (number of faulted writes); the control run (both pages * readable) leaks nothing. * * Build: cc -O2 -o tun_eault_leak tun_eault_leak.c * Run: ./tun_eault_leak <iterations> (as root) */ #include <sys/types.h> #include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { long iters = 30000; long ok = 0, faulted = 0; long i; size_t pagesz = (size_t)sysconf(_SC_PAGESIZE); char *buf; int fd; if (argc > 1) iters = atol(argv[1]); buf = mmap(NULL, 2 * pagesz, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (buf == MAP_FAILED) { perror("mmap"); return 1; } memset(buf, 0x41, 2 * pagesz); if (argc > 2 && strcmp(argv[2], "control") == 0) { /* control: both pages readable -> writes succeed, no leak */ } else { if (mprotect(buf + pagesz, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 1; } } fd = open("/dev/tun", O_RDWR); if (fd < 0) { perror("open /dev/tun"); return 1; } for (i = 0; i < iters; i++) { ssize_t n = write(fd, buf, 2 * pagesz); if (n < 0) faulted++; else ok++; } printf("writes: ok=%ld faulted=%ld (of %ld)\n", ok, faulted, iters); close(fd); return 0; } |