# DF-2256 — ar9300_calibration_data_read_array ignores read failures, leaves destination uninitialized

**File:** `sys/contrib/dev/ath/ath_hal/ar9300/ar9300_eeprom.c` (CONTRIB, vendored QCA HAL)
**Severity:** Low · **Verdict:** REPRODUCED (failure-ignoring API + uninitialized
kernel-stack bytes consumed by the parser + amplifier for DF-2255) · **Fix:** VALIDATED

## The bug

`ar9300_calibration_data_read_array()` (ar9300_eeprom.c:3696-3706) loops
single-byte reads but casts each result to `(void)` and unconditionally
returns `AH_TRUE`. When a per-byte read fails (address underflow below 0 on
OTP/1-2KB-EEPROM systems, or a dead/absent device), the destination slot is
left with whatever was on the kernel stack, and every caller proceeds as if
the buffer were valid:

* the blank-check `word[0..3] == blank` (ar9300_eeprom.c:3905) compares
  garbage;
* `ar9300_compression_header_unpack()` derives `code/length/...` from that
  garbage — a stale header can synthesize `length = 2047` and drive the
  2053-byte read of DF-2255 (the amplifier);
* the checksum loop and the `mchecksum` read at `word[4+length],
  word[5+length]` consume uninitialized slots (on 2 KB devices the five
  highest slots are always unreadable).

## Rig

Same verbatim-code kernel-compiled rig as DF-2255 (shared harness, sha256
gate, fake 4 KB/2 KB EEPROM device, sentinel-filled kernel stack). Triggers:
`sysctl -w kern.df2255.rig=arrfail|uninit|otp2k` (root); evidence prefixed
`DFH:` on console/dmesg.

## Reproduce

```
cd /root/df2255 && sh build.sh && reboot      # rig kernel (see build.sh)
sysctl -w kern.df2255.rig=arrfail             # API-level: failure ignored
sysctl -w kern.df2255.rig=uninit              # stale header drives length
sysctl -w kern.df2255.rig=otp2k               # 2KB device: uninit consumed
dmesg | grep DFH:
```

**Expected (baseline):**
`arrfail` → 13/13 failed-address slots keep the pre-call sentinel while the
function returns `AH_TRUE` (`*** DF-2256 CONFIRMED ***`);
`uninit` → a device that goes dark leaves `word[0..3]` stale, the parser
unpacks `length=2047` from the uninitialized stack and issues a 2053-byte
read into `word[2048]` (`*** DF-2256 AMPLIFIER CONFIRMED ***`);
`otp2k` → on a 2 KB device `word[2048..2052]` stay uninitialized and are
consumed by the checksum/mchecksum compare.

## Fix validation

`fix.diff`: `read_array` propagates the first per-byte failure as `AH_FALSE`,
and both call sites in `ar9300_eeprom_restore_internal_address()` break out
of the block scan on failure (never parse a partially-filled `word[]`).
After the fix (kernel #9): `arrfail` returns `AH_FALSE` with the slots never
written, `uninit` aborts after 1 failed read (no garbage-driven parse), and
the legitimate path is untouched (`chk` still restores 16/16 marker bytes).
Differential: with only the DF-2255 fix applied, DF-2256 stays live — the
findings are independent.

## Threat model (honest)

Same input class as DF-2255 (card EEPROM/OTP at attach): no unprivileged
local path. The claimed userspace readout of uninit-stained data via
`HAL_DIAG_EEPROM` requires a non-default kernel (`AH_PRIVATE_DIAG` +
`ATH_DIAGAPI`, not in stock configs) AND a ~2^-16 checksum coincidence over
garbage, so the leak remains theoretical; the verified impact is
correctness/uninitialized-data-consumption plus the DF-2255 amplifier.
Low severity is correct.

## Upstream

FreeBSD head (2026-10-17, copy in `logs/`) still ships the `(void)` pattern
at its lines 3759/3959/3979 — no divergent upstream fix; `fix.diff` is
novel and upstream-contributable.

## Files

Same layout as the DF-2255 pack (shared rig): `df2255_harness.c`,
`extract.sh`, `build.sh`, `run.sh`, `build.log`, `run.log`, `fix.diff`,
`fix_build.log`, `env.txt`, `logs/`, `VERDICT.md`, `manifest.json`,
`verdict.json`.
