DF-3406 / tapleak.c
/* * tapleak.c -- DF-3406: one mbuf leaked per EFAULTing tapwrite * * tapwrite() (sys/net/tap/if_tap.c:917) allocates the NEXT chain mbuf at * the bottom of the loop *before* re-checking the loop condition: * * while (error == 0 && uio->uio_resid > 0) { * m->m_len = min(mlen, resid); * error = uiomove(...); <-- may fault * *mp = m; mp = &m->m_next; * if (uio->uio_resid > 0) { * MGET(m, M_WAITOK, MT_DATA); <-- allocated even though * mlen = MLEN; error != 0 now * } * } * if (error) { ... m_freem(top); return error; } <-- fresh m never freed * * A write() whose buffer faults part-way (uiomove EFAULT with resid>0) * leaks exactly one MGET mbuf per call. Repeat and watch netstat -m. * * build: cc -o tapleak tapleak.c * run : ./tapleak <iterations> (default 100000) */ #include <sys/types.h> #include <sys/fcntl.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { struct ifreq ifr; size_t pg = 4096; char *buf; int s, fd, i, n = 100000, ok = 0, efault = 0; if (argc > 1) n = atoi(argv[1]); /* create + open tap5 */ s = socket(AF_INET, SOCK_DGRAM, 0); memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, "tap5", IFNAMSIZ); ioctl(s, SIOCIFDESTROY, &ifr); /* clean slate */ if (ioctl(s, SIOCIFCREATE, &ifr) != 0 && errno != EEXIST) { perror("SIOCIFCREATE"); return 1; } fd = open("/dev/tap5", O_RDWR); if (fd < 0) { perror("open /dev/tap5"); return 1; } /* 2 pages, second PROT_NONE: writes fault when they reach page 2 */ buf = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (buf == MAP_FAILED) { perror("mmap"); return 1; } if (mprotect(buf + pg, pg, PROT_NONE) != 0) { perror("mprotect"); return 1; } for (i = 0; i < n; i++) { ssize_t r = write(fd, buf, pg + 128); /* crosses into bad page */ if (r < 0) { if (errno == EFAULT) efault++; } else ok++; } printf("writes: ok=%d efault=%d (each EFAULT leaked one m_get mbuf)\n", ok, efault); close(fd); memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, "tap5", IFNAMSIZ); ioctl(s, SIOCIFDESTROY, &ifr); return (0); } |