⬢ DragonFlyBSD Kernel Audit
DF-5259 / beacon_race.c
← back to finding ↓ download raw
/*
 * DF-5259 beacon mbuf UAF race - userland interleaving harness.
 *
 * Mirrors the exact code regions and lock domains of if_mtw.c:
 *
 *   mtw_update_beacon()   if_mtw.c:3829-3868  - touches rvp->beacon_mbuf
 *                          under ONLY the net80211 ic lock (modelled: icl),
 *                          calls ieee80211_beacon_update() which WRITES into
 *                          the mbuf (modelled: mb_write()).
 *   mtw_newstate() RUN     if_mtw.c:1802-1803 - m_freem(beacon_mbuf) under
 *                          ONLY the driver sc lock (modelled: scl) after
 *                          explicitly releasing the ic lock (if_mtw.c:1771).
 *   mtw_vap_delete()       if_mtw.c:890-892   - m_freem(beacon_mbuf) under
 *                          only scl.
 *
 * No common lock exists between the free path (scl) and the update path
 * (icl) - FreeBSD's run_update_beacon() wraps the same region in
 * RUN_LOCK(sc) which is exactly what the DragonFly mtw port dropped.
 *
 * The harness counts how many beacon-update writes land in an mbuf that the
 * free path has already "freed" (poison-flagged so the process survives).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/time.h>

static pthread_mutex_t icl = PTHREAD_MUTEX_INITIALIZER; /* IEEE80211_LOCK(ic) */
static pthread_mutex_t scl = PTHREAD_MUTEX_INITIALIZER; /* MTW_LOCK(sc)      */

struct mbuf {
	volatile int	freed;
	char	data[2048];
};

static struct mbuf *beacon_mbuf;
static volatile unsigned long updates, frees, uaf_writes;
static volatile int stop;

/* model of ieee80211_beacon_update() writing TIM/capinfo/seq into the mbuf */
static void
mb_write(struct mbuf *m)
{
	memset(m->data + 64, 0x5a, 32);	/* TIM update style write */
	if (__atomic_load_n(&m->freed, __ATOMIC_ACQUIRE))
		__sync_fetch_and_add(&uaf_writes, 1);
}

/* thread A: mtw_update_beacon() - ic lock domain only */
static void *
update_beacon(void *arg)
{
	while (!stop) {
		pthread_mutex_lock(&icl);			/* ic lock */
		if (beacon_mbuf == NULL) {			/* 3855 */
			beacon_mbuf = calloc(1, sizeof(*beacon_mbuf));
			if (beacon_mbuf == NULL) {
				pthread_mutex_unlock(&icl);
				continue;
			}
		}
		/* 3860: the pointer is now a LOCAL (argument evaluation);
		 * ieee80211_beacon_update() then walks the mbuf updating
		 * seq/capinfo/TIM - substantial work during which the free
		 * path (which needs only the sc lock) can run concurrently.
		 * Model that traversal window: */
		{
			volatile int j;
			for (j = 0; j < 20000; j++)
				;
		}
		mb_write(beacon_mbuf);				/* write */
		pthread_mutex_unlock(&icl);
		__sync_fetch_and_add(&updates, 1);
	}
	return (NULL);
}

/* thread B: mtw_newstate(S_RUN) 1802 / mtw_vap_delete() 891 - sc lock domain */
static void *
free_beacon(void *arg)
{
	struct mbuf *m;

	while (!stop) {
		pthread_mutex_lock(&scl);			/* sc lock */
		m = beacon_mbuf;
		beacon_mbuf = NULL;
		if (m != NULL) {
		__atomic_store_n(&m->freed, 1, __ATOMIC_RELEASE);	/* m_freem() */
			free(m);
		}
		pthread_mutex_unlock(&scl);
		__sync_fetch_and_add(&frees, 1);
		usleep(50);	/* rate-limit; real trigger: ifconfig down/destroy */
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 10;
	pthread_t ta, tb;
	struct timeval t0, t1;

	gettimeofday(&t0, NULL);
	pthread_create(&ta, NULL, update_beacon, NULL);
	pthread_create(&tb, NULL, free_beacon, NULL);
	sleep(secs);
	stop = 1;
	pthread_join(ta, NULL);
	pthread_join(tb, NULL);
	gettimeofday(&t1, NULL);

	printf("beacon updates=%lu frees=%lu UAF writes=%lu in %ld.%06lds\n",
	    updates, frees, uaf_writes,
	    t1.tv_sec - t0.tv_sec, t1.tv_usec - t0.tv_usec);
	return (uaf_writes > 0 ? 0 : 1);
}