# DF-3577 — 4-byte heap OOB read in iwn_read_firmware_tlv() (IWN_FW_TLV_PHY_CALIB)

## What this is

Evidence pack for an out-of-bounds read in the iwn firmware TLV parser,
`sys/dev/netif/iwn/if_iwn.c:8315-8320`:

```c
case IWN_FW_TLV_PHY_CALIB:
        tmp = le32toh(*ptr);          /* if_iwn.c:8316 — no len check! */
        if (tmp < 253) {
                sc->reset_noise_gain = tmp;
                sc->noise_gain = tmp + 1;
        }
        break;
```

The generic TLV bound check (`ptr + len > end`, if_iwn.c:8280) accepts any
`len`, including 0..3 — but the case unconditionally loads 4 bytes at `ptr`.
When a PHY_CALIB TLV with `len < 4` sits at the end of the firmware image,
the load reads up to 4 bytes **past the end of the kernel heap allocation**
holding the image (`fw->data` from `firmware_get()`, if_iwn.c:8375-8385).
The sibling case `IWN_FW_TLV_FLAGS` (if_iwn.c:8327-8331) shows the intended
pattern — it rejects `len < sizeof(uint32_t)`.

## What this pack proves

`iwn_tlv_oob.c` is a **line-for-line copy** of the parsing loop of
`iwn_read_firmware_tlv()` (if_iwn.c:8271-8358; diagnostics dropped,
`sc->` fields modelled as locals). The crafted image is 96 bytes:
88-byte TLV header + one 8-byte TLV (type=15 PHY_CALIB, alt=0, **len=0**)
ending exactly at the end of the buffer.

- **guard-page mode** (default): the image is placed flush against a
  `PROT_NONE` page → the 4-byte load at `end` faults: **SIGSEGV, exit 139,
  deterministic 4/4 runs**.
- **ASAN mode**: provided for environments with libasan (the audit guest's
  base gcc 8.3 has none — `cannot find -lasan` — so guard mode is primary).
- **fixed variant** (`iwn_tlv_oob_fixed.c`): adds the one-line guard
  `if (len < sizeof(uint32_t)) break;` (mirroring the FLAGS case). It parses
  the same image and **returns cleanly — no fault** (see `run.log`).
  This is the fix diff validated at harness level.

## Build / run (on the DragonFly guest)

```
cc -O2 -Wall -o iwn_tlv_oob        iwn_tlv_oob.c
cc -O2 -Wall -o iwn_tlv_oob_fixed  iwn_tlv_oob_fixed.c
./iwn_tlv_oob guard        # expect: exit 139 (SIGSEGV at guard page)
./iwn_tlv_oob_fixed guard  # expect: clean return, no fault
```

## Impact

Kernel heap OOB read of ≤4 bytes; the value only conditions
`sc->reset_noise_gain` / `sc->noise_gain` (calibration command codes sent to
the NIC) — no demonstrated path back to userspace. Attacker = whoever
controls the firmware file (`/boot/firmware/iwn*-fw`, root-installed) or a
corrupted download. Severity Low, but it is a genuine memory-safety defect
and a correctness bug (garbage calib codes).

## Fix

`fix.diff` — add the same length guard the FLAGS case already has.
