# DF-4624 VERDICT — REPRODUCED (harness-level, verbatim extraction)

**Status:** reproduced / impact: dos (OOB read + uninit read, hardware-gated
trigger) / confidence: certain.

## Root cause (traced line by line)

1. `ar9285SetPowerCalTable` (ar9285_reset.c:563) reads
   `xpdMask = pEepData->modalHeader.xpdGain` (:580) straight from the v4k
   EEPROM (`uint8_t xpdGain`, ah_eeprom_v4k.h:93). Nothing between
   `v4kEepromAttach`'s checksum check and here rejects `xpdGain == 0`.
2. The xpd-gain loop (:593-602) only increments `numXpdGain` for set bits;
   `xpdGain == 0` leaves **numXpdGain == 0**. The `HALASSERT(0)` + `break`
   at :595-598 guards the **> 2** case only — and `HALASSERT` is a no-op in
   release builds anyway (ah_internal.h:665-676) — the `break` itself is
   dead text for the ==0 case.
3. `ar9285GetGainBoundariesAndPdadcs` (:636) then runs with numXpdGains==0:
   * :683/:692 table loops never execute → `minPwrT4[]/maxPwrT4[]` stay
     **uninitialized stack**; :719 `*pMinCalPower = (int16_t)(minPwrT4[0] / 2)`
     reads them.
   * :722 gain loop never executes → `i == 0`, `k == 0`.
   * :791-794 boundary backfill is in-bounds (gainBoundaries[4], fills 4).
   * :796-798 `while (k < 128) pPDADCValues[k] = pPDADCValues[k-1];` runs
     with **k == 0** → first iteration reads **pPDADCValues[-1]** — one byte
     below `static uint8_t pdadcValues[128]` (:570) — and replicates it into
     all 128 entries.
4. `ar5416WritePdadcValues` (:628) then programs those 128 values into the
   AR9285 PHY PDADC table.

## How it was proven (guest, DragonFly 6.5-DEVELOPMENT)

`build.sh` seds **the verbatim function** (ar9285_reset.c:636-801) plus its
`ah.c` helpers out of the guest's `/usr/src` and compiles them into the
harness with `HALASSERT` compiled out (release-kernel shape). GCC itself
emits `-Wuninitialized` for `minPwrT4[0]` on this extraction (build.log).

* **Mode 1 (adjacency):** marker byte 0x5A at `pdadcValues[-1]` → after the
  call **all 128** PDADC entries are 0x5A (non-marker entries: 0). run.log
* **Mode 2 (fault):** array at a fresh page with a PROT_NONE guard page
  below → SIGSEGV with `si_addr == base-1` **exactly**. run.log
* **Mode 3 (uninit):** -O0 build; a 128 KB stack spill of 0xA7 produces
  `*pMinCalPower == 0x0053`, of 0x12 produces `0x0009` — exactly
  `minPwrT4[0]/2` for each poison; the value flows out to the caller
  (there: `tMinCalPower`). run_O0_mode3.log

## Why not uid0 / not a bigger impact

The sink is the radio register table (`ar5416WritePdadcValues`), not
userspace-visible memory; the [-1] byte cannot be read back. The trigger
needs an AR9285/AR9271 device whose EEPROM image is attacker-supplied
(same precondition as the already-filed DF-1768/DF-1769). Ceiling: radio
misprogramming / RF compliance / correctness — classified dos-grade with
Low severity, memcorrupt bucket, mirroring the sibling DF-4620 (Low).

## Distinctness from the known findings on this file

* DF-1768 (lines 650-782): unbounded **pwrPdg delta** → FillVpdTable
  overflow + `sizeCurrVpdTable` uint8 wrap at :766 → far-OOB reads.
  DF-4624's trigger is `xpdGain == 0` (no curve needed at all) and its
  lines are :719/:796-798, outside DF-1768's cited range.
* DF-1769 (lines 607-612): chain loop `AR5416_MAX_CHAINS` on a 1-chain
  struct. Not involved here.
* The ar5413-family DF-4619 pattern (unbounded `kk` writes) is **absent**
  in ar9285: all three write loops are `k < AR5416_NUM_PDADC_VALUES - 1`
  bounded (:760, :770, :781). The DF-4622 flat-curve `[size-1]/[size-2]`
  reads exist at :774 but are DF-1768's :766 truncation family (same line
  family, same missing-validation root cause) — not re-filed.

## Fix validation

`build_fixed.sh` applies `fix.diff` (early bail when `numXpdGains == 0`,
zeroing the PDADC table) to a **scratch copy** — `sys/` is never touched —
re-extracts, rebuilds, reruns: pdadc zeroed, marker never read, minPower
sentinel 0x7c7c preserved, guard-page flavor no longer faults
(fix_build.log). `fix_status: fixed`, baseline reproduced / patched clean.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
