DragonFlyBSD Kernel Audit
DF-0691 / mld6_race.c
← back to finding ↓ download raw
/*
 * DF-0691 harness: reproduce the NULL-deref/UAF race in mld6_input()'s
 * MLD_LISTENER_QUERY path vs in6_delmulti().
 *
 * Race (confirmed in source):
 *   mld6_input (sys/netinet6/mld6.c): line 280 ifnet_serialize_all(ifp);
 *       line 283 TAILQ iterates ifp->if_multiaddrs;
 *       line 290 in6m = (struct in6_multi *)ifma->ifma_protospec;   <-- NO NULL check
 *       line 292 IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, ...)          <-- derefs in6m
 *   in6_delmulti (sys/netinet6/in6.c:1761): runs under crit_enter() but NOT the
 *       ifnet serializer: line 1773 ifma->ifma_protospec = NULL;
 *                         line 1774 LIST_REMOVE(in6m, in6m_entry);
 *                         line 1775 kfree(in6m, M_IPMADDR);
 *                         line 1778 if_delmulti(...)  <-- only here takes serializer
 *   => window where ifma is still on the list but protospec is NULL (or freed) and
 *      mld6_input (holding the serializer) reads protospec without a NULL check =>
 *      NULL deref (or UAF if the freed in6m is reclaimed).
 *
 * The real MLD_LISTENER_QUERY handler skips the loopback interface (mld6.c:235) and
 * needs an INBOUND query on a non-loopback link; this isolated guest has no IPv6 MLD
 * querier. This harness instead replays the EXACT read pattern of mld6_input (serializer
 * + protospec read with no NULL check) against the real in6_addmulti/in6_delmulti on a
 * non-loopback interface, racing a reader vs a deleter to trigger the NULL deref.
 *
 * Run: kldload ./mld6_race.ko   -> NULL-deref panic in the reader thread.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/socket.h>
#include <sys/kthread.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <net/if.h>
#include <net/if_var.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>

static struct ifnet *race_ifp;
static struct in6_addr grp;
static volatile int race_stop = 0;
static volatile int race_hits = 0;	/* counts reader NULL-deref survivals (should not rise) */

/* Reader: mimics mld6_input MLD_LISTENER_QUERY body (mld6.c:280-296) exactly:
 * acquire serializer, iterate if_multiaddrs, read protospec with NO NULL check,
 * then dereference in6m->in6m_addr. If a concurrent in6_delmulti has NULLed
 * protospec, this derefs NULL -> page fault (the bug). */
static void
mld6_reader(void *arg)
{
	int dummy = 0;
	while (!race_stop) {
		struct ifmultiaddr *ifma;
		ifnet_serialize_all(race_ifp);
		TAILQ_FOREACH(ifma, &race_ifp->if_multiaddrs, ifma_link) {
			struct in6_multi *in6m;
			if (ifma->ifma_addr == NULL ||
			    ifma->ifma_addr->sa_family != AF_INET6)
				continue;
			/* mld6.c:290 -- NO NULL check on protospec (the bug) */
			in6m = (struct in6_multi *)ifma->ifma_protospec;
			/* mld6.c:292 -- derefs in6m->in6m_addr; if in6m==NULL => panic */
			if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &grp))
				dummy++;
			race_hits++;
		}
		ifnet_deserialize_all(race_ifp);
	}
	kprintf("DF0691: reader exiting, iterations-with-protospec-reads=%d\n", race_hits);
	kthread_exit();
}

/* Deleter: mimics a socket leaving the group -> in6_delmulti(), which NULLs
 * protospec + kfrees in6m under crit_enter() WITHOUT the ifnet serializer. */
static void
mld6_deleter(void *arg)
{
	int err;
	while (!race_stop) {
		struct in6_multi *in6m = in6_addmulti(&grp, race_ifp, &err);
		if (in6m != NULL)
			in6_delmulti(in6m);	/* NULLs protospec + kfree, no serializer */
	}
	kthread_exit();
}

static int
mld6_race_load(module_t mod, int what, void *arg)
{
	struct thread *rd = NULL, *dl = NULL;
	if (what != MOD_LOAD)
		return (0);

	race_ifp = NULL;
	ifnet_lock();
	race_ifp = ifunit("vtnet0");
	ifnet_unlock();
	if (race_ifp == NULL) {
		kprintf("DF0691: vtnet0 not found\n");
		return (0);
	}
	/* a link-local-scope multicast group */
	grp.s6_addr[0] = 0xff; grp.s6_addr[1] = 0x02;
	grp.s6_addr[15] = 0x99;

	kprintf("DF0691: racing mld6_input protospec read vs in6_delmulti on %s\n",
	    race_ifp->if_xname);
	kprintf("DF0691: expect NULL-deref panic when reader sees protospec=NULL\n");

	kthread_create(mld6_deleter, NULL, &dl, "mld6_del");
	kthread_create(mld6_reader, NULL, &rd, "mld6_rd");
	/* let them race; if no panic in a few seconds, stop (unlikely) */
	tsleep(&race_stop, 0, "mld6race", 8 * hz);
	race_stop = 1;
	tsleep(&race_stop, 0, "mld6done", 2 * hz);
	kprintf("DF0691: race window closed without NULL-deref (race not hit; rerun)\n");
	return (0);
}

static moduledata_t mld6_race_mod = { "mld6_race", mld6_race_load, NULL };
DECLARE_MODULE(mld6_race, mld6_race_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
MODULE_VERSION(mld6_race, 1);