# DF-2002 — VERDICT

## Verdict: CONFIRMED (source-trace), HW-gated — inconclusive at runtime

The bug is real and confirmed by a complete line-by-line source trace of the
data flow from EEPROM read to the unbounded PDADC shift loop. It cannot be
exercised on this audit guest because the sink is gated behind Atheros AR9280
(Merlin) NIC hardware (the `ath` module is loaded but no AR9280 NIC is present,
and the vulnerable path runs only at NIC attach / channel change). Per the
standard HW-gated pattern, runtime reproduction is `inconclusive` /
`reproduced=0` / `impact=none`, with the primitive characterized in userspace.

## Mechanism (confirmed path:line)

1. **EEPROM read, no validation** —
   `sys/dev/netif/ath/ath_hal/ah_eeprom_v14.c:137-142`: the
   `AR_EEP_PWR_TABLE_OFFSET` case writes `*(int8_t *)val = pBase->pwr_table_offset`
   (a raw `uint8_t` EEPROM field, `ah_eeprom_v14.h:189`) with the only gate
   being `IS_VERS(>=, AR5416_EEP_MINOR_VER_21)` — an attacker-controlled EEPROM
   minor version byte.

2. **Signedness-mismatched diff computation** —
   `sys/dev/netif/ath/ath_hal/ar9002/ar9280_olc.c:188-192`: when
   `pwr_table_offset != AR5416_PWR_TABLE_OFFSET_DB (-5)`,
   `*diff = (uint16_t)(pwr_table_offset - (-5)); *diff *= 2;`. The caller
   declares `uint16_t diff` (line 345) but the formal is `int16_t *diff`
   (line 176) and the return is `int16_t` (line 173) — a signedness
   type-mismatch.

3. **Unbounded shift loop** — `ar9280_olc.c:215,226-227`:
   ```c
   #define NUM_PDADC(diff) (AR5416_NUM_PDADC_VALUES - diff)   /* 128 - diff */
   for (k = 0; k < (uint16_t)NUM_PDADC(diff); k++)
       pdadcValues[k] = pdadcValues[k + diff];
   ```
   `pdadcValues` is `static uint8_t pdadcValues[AR5416_NUM_PDADC_VALUES]`
   (128 bytes, BSS) declared at `ar9280_olc.c:288`.

4. **Overflow cases** (confirmed by the userspace harness, which is a verbatim
   copy of the two kernel functions):
   - `pwr_table_offset = -6` (byte 0xFA): `diff = -2`, `NUM_PDADC = 130`,
     writes `pdadcValues[128..129]` → **2-byte OOB write** past the BSS buffer;
     reads `pdadcValues[-2..-1]` → 2-byte OOB read.
   - `pwr_table_offset = -128` (byte 0x80): `diff = -246`, `NUM_PDADC = 374`,
     **246-byte OOB write**.
   - `pwr_table_offset = +64` (byte 0x40): `diff = 138`, `NUM_PDADC = -10`,
     `(uint16_t)(-10) = 65526`, loop writes ~64KB → kernel page-fault / panic.
   - `pwr_table_offset = +127` (byte 0x7F): `diff = 264` → ~64KB overflow.

   The `(uint16_t)` cast on the loop bound at line 226 is the silent wrap that
   turns a "negative" iteration count into a ~65K-iteration catastrophic write.

Reachable at NIC attach (`ar9280_attach.c:209` wires
`ah_setPowerCalTable = ar9280SetPowerCalTable`) and on every channel change, on
`AR_SREV_MERLIN_20_OR_LATER` hardware. No bounds check exists anywhere between
the EEPROM read (step 1) and the OOB write (step 3).

## Userspace harness result

`poc_df_pwrtable.c` is a verbatim copy of `ar9280ChangeGainBoundarySettings`
and `ar9280AdjustPDADCValues` (lines 173-237), demonstrating the kernel's own
arithmetic. Build & run:

```
=== pwr_table_offset=-6 (byte 0xFA), diff=-2, NUM_PDADC=130 ===
  canary overwrites past pdadcValues[127]: 2 bytes (...)
=== pwr_table_offset=-128 (byte 0x80), diff=-246, NUM_PDADC=374 ===
  canary overwrites past pdadcValues[127]: 246 bytes (...)
=== pwr_table_offset=64 (byte 0x40), diff=138, NUM_PDADC=-10 ===
  (skipping: would write ~64KB -> panic)
```

This proves the overflow with the kernel's exact logic. On a real AR9280 NIC
with a forged EEPROM (Variant B, byte 0x40), the in-kernel sink page-faults.

## Exploit chain

Not developed — the primitive is gated behind physical-access HW (a forged
AR9280 EEPROM on a tampered USB dongle / PCI passthrough / reflashed NIC). The
guest has no such NIC, so the in-kernel sink is unreachable here. This is a
valid hard blocker (the vulnerable code path is not exercisable on this guest
AND no in-guest harness can drive the real NIC EEPROM read path without the
hardware). The primitive is characterized at the source/userspace-harness
level: a forged EEPROM byte yields a controlled 2..246-byte BSS overwrite
(exploitable with slab/BSS grooming to corrupt adjacent HAL state / function
pointers) or a reliable ~64KB overflow → panic (DoS).

The threat model (tampered USB dongle hotplug / PCI passthrough to a VM guest
/ insider EEPROM reflash) is documented in the finding and is realistic.

## PoC changes

- Added `poc_df_pwrtable.c` — the verbatim-kernel-logic userspace harness from
  the finding markdown, made standalone-buildable.
- Added `build.sh` / `run.sh` — exact, runnable.
- Authored `fix.diff` — minimal bounds check on `diff` at the top of
  `ar9280AdjustPDADCValues` (`diff < 0 || diff >= AR5416_NUM_PDADC_VALUES`).
  This matches the finding markdown's primary recommended fix.

## Fix

`fix.diff` adds a 1-line guard at `ar9280_olc.c:216` (after the macro, before
the loops):
```c
if (diff < 0 || diff >= AR5416_NUM_PDADC_VALUES)
    return;
```
This closes both the negative-diff (forward OOB write) and the >128-diff
(uint16_t-wrap ~64KB write) cases. `git apply --check` passes. **Matches** the
finding markdown's primary proposal. Validated by a clean kernel build in
Phase 8 (combined with DF-2000/DF-2003 fixes).
