# DF-3578 — VERDICT

**Status: inconclusive (runtime; no iwn/4965 hardware on guest), source-certain.**
**Impact: kernel panic (#DE) at device bring-up. Reproduced: the fault primitive.**

## Root cause (verified in-tree)

- EEPROM bulk-read without validation: if_iwn.c:2314
  (`iwn_read_prom_data(sc, IWN4965_EEPROM_BANDS, sc->bands, sizeof sc->bands)`).
- Interpolation divisor from two raw EEPROM bytes: if_iwn.c:5707-5715 calls
  `interpolate(chan, chans[0].num, ..., chans[1].num, ..., 1)`; the macro
  (if_iwn.c:5633-5637) divides by `(x2) - (x1)` — zero when both sample
  slots carry the same channel number.
- Reachability: `iwn4965_set_txpower` is called from `iwn_config`
  (if_iwn.c:6798, every device bring-up), `iwn_auth` (if_iwn.c:7199) and
  `iwn4965_power_calibration` (if_iwn.c:5619, on temperature change).
  Nothing between the EEPROM read and the division validates the sample
  channel numbers.

Threat model: the malicious input is the card's EEPROM/OTPROM — i.e. a
malicious or tampered Mini-PCIe card inserted into the machine, matching the
audit strategy's "EEPROM parse — attacker-controlled on malicious card"
surface. Severity Low (physical insertion / root-controlled flashing), but
the fault itself is unconditional once such EEPROM contents exist.

## Reproduction

`iwn_eeprom_div0.c` executes the verbatim `fdivround`/`interpolate` macros
with the two divisor operands taken from `volatile` stores (modeling
`iwn_read_prom_data()` output — runtime-opaque to the compiler):

- control (36/64): prints `control (nums 36/64): power=40 OK`.
- malicious (36/36): prints the marker, then **SIGFPE exit 136**, 4/4 runs
  (run.log + run.2.log stress 3/3).

PoC iteration note (recorded for honesty): the first harness version kept the
sample values in a plain local struct; gcc 8.3 -O2 scalar-replaced the whole
struct, constant-folded the compile-time-visible 0/0 UB, and the process
*survived* — a classic way a div-by-zero PoC silently degrades. Moving the
values behind `volatile` restored the real hardware `idiv` fault. The kernel
always performs the real division (values come from DMA/MMIO reads).

## Why not "reproduced"

Same reason as DF-3576: the guest is virtio-only (no Intel WiFi hardware),
so the in-kernel path (EEPROM read → set_txpower) cannot be driven. The
arithmetic primitive is proven deterministically; the chain is proven by
source.

## Fix

`fix.diff` guards the interpolation: only interpolate when
`chans[1].num > chans[0].num`, else fall back to `chans[0]`'s sample values.
(Belt-and-braces: validate the whole `sc->bands` table after
`iwn4965_read_eeprom` — check `lo <= chans[i].num <= hi` and increasing
sample channel numbers.)

## 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.
