โฌข DragonFlyBSD Kernel Audit
DF-4893 / trigger.c
โ† back to finding โ†“ download raw
/*
 * DF-4893 โ€” documentary trigger for the ah_osdep.c ALQ TOCTOU (LATENT).
 *
 * The race (FreeBSD-inherited, AH_DEBUG_ALQ block):
 *   CPU0 (register access path):  ah_osdep.c:297/:336/:353
 *        if (ath_hal_alq) { ale = ath_hal_alq_get(ah);   // :264/:276
 *                            alq_get(ath_hal_alq, ALQ_NOWAIT); ... }
 *   CPU1 (root): sysctl hw.ath.hal.alq=0 -> ath_hal_setlogging(0)
 *        alq_close(ath_hal_alq); ath_hal_alq = NULL;    // :230-232, unlocked
 *   -> CPU0 passes a freed `struct alq` to alq_get/alq_post => UAF write.
 *
 * THIS PROGRAM CANNOT RUN ON STOCK DRAGONFLY: the sysctl oid only exists
 * when the kernel is built with AH_DEBUG_ALQ, whose option mapping is
 * commented out (sys/conf/options:551) and whose alq(9) prerequisite does
 * not exist in the tree. It is kept here as the exact reproducer shape for
 * the day the block is revived (or for FreeBSD, where it builds).
 *
 * Shape: two threads โ€”
 *   A: sysctl("hw.ath.hal.alq", 0/1) toggling in a tight loop (needs root);
 *   B: generate ath register traffic (needs an Atheros NIC; e.g. flood
 *      raw 802.11 frames so the HAL takes reg_read/reg_write on the hot
 *      path โ€” any channel scan/reset/calibration storm works).
 * Win condition: panic/UAF splat in alq_get/alq_post with ath_hal_alq
 * freed by the concurrent alq_close.
 *
 * Build: cc -o trigger trigger.c   (userland; run.sh checks preconditions
 * first and refuses to proceed on stock DF).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/sysctl.h>

#ifndef OID_DNE
#define OID_DNE 2 /* ENOENT-class for sysctl */
#endif

static int oid_exists(const char *name)
{
	int v = 0;
	size_t l = sizeof(v);
	return sysctlbyname(name, &v, &l, NULL, 0) != -1;
}

int main(int argc, char **argv)
{
	int have_alq    = oid_exists("hw.ath.hal.alq");
	int have_log    = oid_exists("hw.ath.hal.alq_logfile");

	(void)argv;

	printf("DF-4893 precondition probe\n");
	printf("  hw.ath.hal.alq          : %s\n", have_alq ? "PRESENT" : "ABSENT (AH_DEBUG_ALQ not compiled)");
	printf("  hw.ath.hal.alq_logfile  : %s\n", have_log ? "PRESENT" : "ABSENT");

	if (!have_alq) {
		/*
		 * Stock DragonFly always lands here: the ALQ sysctls live inside
		 * #ifdef AH_DEBUG_ALQ (ah_osdep.c:209-256) and the option cannot
		 * be enabled (sys/conf/options:551 commented out; no alq(9) in
		 * tree; ath_hal_reg_whilst_asleep also missing unless AH_DEBUG).
		 * The race target therefore does not exist in the running kernel.
		 */
		fprintf(stderr,
		    "TARGET ABSENT: AH_DEBUG_ALQ block is compiled out. The\n"
		    "ath_hal_alq TOCTOU is latent-only on this system; see\n"
		    "findings/poc/DF-4893/VERDICT.md.\n");
		exit(3);
	}

	/*
	 * Reachable only on a hand-built kernel (or FreeBSD): become the
	 * toggler thread and hammer enable/disable while a second process
	 * drives ath register traffic (needs real Atheros NIC + root).
	 */
	if (geteuid() != 0) {
		fprintf(stderr, "toggler thread requires root\n");
		exit(4);
	}
	fprintf(stderr, "target PRESENT: would toggle hw.ath.hal.alq vs. "
	    "concurrent HAL register traffic โ€” see header comment\n");
	return 0;
}