⬢ DragonFlyBSD Kernel Audit
DF-4624 / ar9285_harness.c
← back to finding ↓ download raw
/*
 * DF-4624 verbatim-extraction harness for
 *   ar9285GetGainBoundariesAndPdadcs()  (sys/dev/netif/ath/ath_hal/ar9002/ar9285_reset.c:636-801)
 *
 * Trigger: v4k EEPROM modalHeader.xpdGain == 0  ->  ar9285SetPowerCalTable's
 * xpd-gain mask loop (ar9285_reset.c:593-602) leaves numXpdGain == 0.
 * With numXpdGains == 0:
 *   - ar9285_reset.c:719  reads uninitialized stack  minPwrT4[0]
 *   - ar9285_reset.c:796-798 tail backfill runs with k == 0 and evaluates
 *     pPDADCValues[k-1]  ==  pdadcValues[-1]  (the caller's
 *     `static uint8_t pdadcValues[128]` at ar9285_reset.c:570) -- a 1-byte
 *     OOB read *below* the array, replicated into all 128 PDADC values and
 *     programmed to the PHY by ar5416WritePdadcValues().
 *
 * The kernel function is compiled VERBATIM (build.sh seds it out of
 * /usr/src on the guest).  HALASSERT is compiled out, mirroring a release
 * kernel (AH_ASSERT undefined, ah_internal.h:665-676).
 *
 * Modes:
 *   1  adjacency flavor: marker byte placed at pdadc[-1]; expect all 128
 *      PDADC entries == marker after the call.
 *   2  fault flavor: pdadc placed at a fresh page start with a PROT_NONE
 *      guard page below; expect SIGSEGV with si_addr == (base - 1).
 *   3  uninitialized-stack flavor: stack poisoned with two different
 *      patterns before the call; *pMinCalPower output must track the
 *      poison (ar9285_reset.c:719 uninit read).
 *   4  fixed flavor (validation): early bail on numXpdGains == 0; sentinel
 *      PDADC values untouched, minPower sentinel untouched.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>
#include <unistd.h>

/* ---- kernel/HAL type shims ---- */
typedef int HAL_BOOL;
#define AH_TRUE  1
#define AH_FALSE 0
#define AH_MAX(a, b) ((a) > (b) ? (a) : (b))
#define AH_MIN(a, b) ((a) < (b) ? (a) : (b))
#define HALASSERT(x)                     /* no-op in release kernels */
#define OS_MEMZERO(p, n) memset((p), 0, (n))

/* constants (ah_eeprom_v14.h / ah_eeprom_v4k.h, verbatim values) */
#define AR5416_PD_GAINS_IN_MASK          4
#define AR5416_PD_GAIN_ICEPTS            5
#define AR5416_NUM_PDADC_VALUES          128
#define AR5416_MAX_PWR_RANGE_IN_HALF_DB  64
#define AR5416_MAX_RATE_POWER            63
#define AR5416_BCHAN_UNUSED              0xFF
#define AR5416_4K_NUM_PD_GAINS           2
#define AR5416_4K_EEP_PD_GAIN_BOUNDARY_DEFAULT 58
#define FREQ2FBIN(x, y) ((y) ? ((x) - 2300) : (((x) - 4800) / 5))

struct ath_hal { int dummy; };
struct ieee80211_channel {
	uint16_t ic_freq;
	uint32_t ic_flags;
};
#define IEEE80211_CHAN_2GHZ 0x0080
#define IEEE80211_IS_CHAN_2GHZ(_c) (!!((_c)->ic_flags & IEEE80211_CHAN_2GHZ))

typedef struct {
	uint16_t synth_center;
	uint16_t ctl_center;
	uint16_t ext_center;
} CHAN_CENTERS;

/* AR9285 is Merlin 2.0-or-later, 5416 v2.0-or-later */
#define AR_SREV_5416_V20_OR_LATER(_ah) 1
#define AR_SREV_MERLIN_20_OR_LATER(_ah) 1

typedef struct {
	uint8_t pwrPdg[AR5416_4K_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS];
	uint8_t vpdPdg[AR5416_4K_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS];
} __attribute__((packed)) CAL_DATA_PER_FREQ_4K;

static void
ar5416GetChannelCenters_shim(struct ath_hal *ah,
    const struct ieee80211_channel *chan, CHAN_CENTERS *centers)
{
	/* minimal 20MHz 2GHz model; the extraction only uses synth_center */
	centers->synth_center = centers->ctl_center = chan->ic_freq;
	centers->ext_center = chan->ic_freq + 10;
}
#define ar5416GetChannelCenters(a, b, c) ar5416GetChannelCenters_shim(a, b, c)

/* ---- verbatim helpers from ah.c (extracted by build.sh) ---- */
#include "ah_extracted.inc"

/* ---- verbatim kernel function under test (extracted by build.sh) ---- */
#include "ar9285_extracted.inc"

/* ---- fault-flavor plumbing ---- */
static sigjmp_buf fault_jmp;
static volatile uintptr_t fault_addr;
static void *expect_page;
static int expect_fault;

static void
fault_handler(int sig, siginfo_t *si, void *uc)
{
	fault_addr = (uintptr_t)si->si_addr;
	if (expect_fault && si->si_addr == (char *)expect_page - 1)
		siglongjmp(fault_jmp, 1);
	_exit(9);
}

static CAL_DATA_PER_FREQ_4K rawdata[3];
static uint8_t bChans[3];
static struct ieee80211_channel chan = { 2437, IEEE80211_CHAN_2GHZ };
static uint16_t gainBoundaries[AR5416_PD_GAINS_IN_MASK];

static void
fill_dataset(void)
{
	int i, j;

	/* A plausible benign 2GHz cal dataset; irrelevant when numXpdGains==0
	 * but kept faithful. Pier fbins increasing, none UNUSED. */
	bChans[0] = 10; bChans[1] = 60; bChans[2] = 137; /* FREQ2FBIN(2437,1) */
	for (i = 0; i < 3; i++) {
		for (j = 0; j < AR5416_4K_NUM_PD_GAINS; j++) {
			int k;
			for (k = 0; k < AR5416_PD_GAIN_ICEPTS; k++) {
				rawdata[i].pwrPdg[j][k] = 20 + 10 * k + j;
				rawdata[i].vpdPdg[j][k] = 40 + 10 * k + j;
			}
		}
	}
}

/* Poison the stack BELOW the current frame so the callee's locals
 * (minPwrT4/maxPwrT4) land on a known pattern: recurse ~128 KB deep
 * filling each frame with the poison byte, unwind, then call the
 * target -- its frame reuses the freshly poisoned pages. */
static void
spill(uint8_t poison, int depth)
{
	volatile uint8_t buf[8192];

	memset((void *)buf, poison, sizeof(buf));
	__asm__ __volatile__("" :: "r"(buf) : "memory");
	if (depth > 0)
		spill(poison, depth - 1);
}

static void
run_poisoned(uint8_t poison, int16_t *minPower, uint8_t *pdadc)
{
	spill(poison, 16);
	ar9285GetGainBoundariesAndPdadcs(NULL, &chan, rawdata, bChans, 3,
	    4, minPower, gainBoundaries, pdadc, 0 /* numXpdGains == 0 !! */);
}

int
main(int argc, char **argv)
{
	int mode = argc > 1 ? atoi(argv[1]) : 1;
	int16_t minPower;
	int i, bad;

	fill_dataset();
	memset(gainBoundaries, 0, sizeof(gainBoundaries));

	if (mode == 1 || mode == 3 || mode == 4) {
		/* adjacency flavor: pdadc at page2, marker at last byte page1 */
		uint8_t *pages = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
		    MAP_PRIVATE | MAP_ANON, -1, 0);
		uint8_t *pdadc;

		if (pages == MAP_FAILED) { perror("mmap"); return 9; }
		pdadc = pages + 4096;
		*(pdadc - 1) = 0x5A;		/* marker below the array */
		memset(pdadc, 0xAA, 128);	/* sentinel */

		if (mode == 3) {
			minPower = 0x7C7C;
			run_poisoned(0xA7, &minPower, pdadc);
			printf("mode3 poison=0xA7: minPower=0x%04x "
			    "(minPwrT4[0]~=0x%02x) pdadc[0]=0x%02x\n",
			    (uint16_t)minPower, 0xA7, pdadc[0]);
			{
				int16_t mp2 = 0x7C7C;
				uint8_t *pd2 = malloc(256);
				memset(pd2, 0xAA, 256);
				run_poisoned(0x12, &mp2, pd2 + 64);
				printf("mode3 poison=0x12: minPower=0x%04x "
				    "(minPwrT4[0]~=0x%02x)\n",
				    (uint16_t)mp2, 0x12);
				free(pd2);
			}
			if (pdadc[0] == 0x5A)
				printf("mode3: pdadc[0] == marker 0x5A "
				    "(OOB read replicated)\n");
		} else {
			minPower = 0x7C7C;
			ar9285GetGainBoundariesAndPdadcs(NULL, &chan, rawdata,
			    bChans, 3, 4, &minPower, gainBoundaries, pdadc,
			    0 /* numXpdGains == 0 !! */);

			bad = 0;
			for (i = 0; i < 128; i++)
				if (pdadc[i] != 0x5A)
					bad++;
			printf("minPower=0x%04x gainBoundaries[0]=%u "
			    "pdadc[0]=0x%02x pdadc[127]=0x%02x "
			    "(non-marker entries: %d)\n",
			    (uint16_t)minPower, gainBoundaries[0],
			    pdadc[0], pdadc[127], bad);
			if (mode == 1 && bad == 0 && pdadc[0] == 0x5A) {
				printf("DF-4624 REPRODUCED: all 128 PDADC "
				    "values sourced from pdadcValues[-1] "
				    "(ar9285_reset.c:797 with k==0)\n");
				return 0;
			}
			if (mode == 4 && bad == 128 && pdadc[0] == 0x00) {
				printf("DF-4624 FIX VALIDATED: pdadc zeroed, "
				    "no [-1] read, minPower sentinel "
				    "0x%04x preserved\n", (uint16_t)minPower);
				return 0;
			}
		}
		return 1;
	}

	if (mode == 2) {
		/* fault flavor: pdadc at page start, PROT_NONE guard below */
		uint8_t *pages = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
		    MAP_PRIVATE | MAP_ANON, -1, 0);
		uint8_t *pdadc;
		struct sigaction sa;

		if (pages == MAP_FAILED) { perror("mmap"); return 9; }
		if (mprotect(pages, 4096, PROT_NONE) != 0) {
			perror("mprotect"); return 9;
		}
		pdadc = pages + 4096;
		memset(pdadc, 0xAA, 128);

		memset(&sa, 0, sizeof(sa));
		sa.sa_sigaction = fault_handler;
		sa.sa_flags = SA_SIGINFO;
		sigaction(SIGSEGV, &sa, NULL);
		expect_page = pdadc;
		expect_fault = 1;

		if (sigsetjmp(fault_jmp, 1) == 0) {
			minPower = 0x7C7C;
			ar9285GetGainBoundariesAndPdadcs(NULL, &chan,
			    rawdata, bChans, 3, 4, &minPower, gainBoundaries,
			    pdadc, 0 /* numXpdGains == 0 !! */);
			printf("no fault -- not reproduced\n");
			return 1;
		}
		expect_fault = 0;
		printf("SIGSEGV si_addr=%p expected=%p (base-1)\n",
		    (void *)fault_addr, (void *)(pdadc - 1));
		if (fault_addr == (uintptr_t)(pdadc - 1)) {
			printf("DF-4624 REPRODUCED: fault at exactly "
			    "pdadcValues[-1] (1 byte below the array, "
			    "ar9285_reset.c:797)\n");
			return 0;
		}
		return 1;
	}

	fprintf(stderr, "usage: %s <1|2|3|4>\n", argv[0]);
	return 2;
}