# DF-3631 — iwm_read_firmware TLV walk: size_t underflow on `len -= roundup2(tlv_len, 4)` walks the parser past the firmware image

## Where
`sys/dev/netif/iwm/if_iwm.c:563-910` (`iwm_read_firmware`), specifically
`:620` (`len = fw->fw_fp->datasize - sizeof(*uhdr)`), `:606-608`
(unvalidated 4-byte magic read), `:630-638` (the TLV walk).

## What
```c
len = fw->fw_fp->datasize - sizeof(*uhdr);      /* :620 — underflows if datasize < 88 */

while (len >= sizeof(*tlv)) {
        len -= sizeof(*tlv);
        tlv = (const void *)data;
        tlv_len = le32toh(tlv->length);
        ...
        if (len < tlv_len) { ...error... }      /* :630 — the ONLY bound check */
        len -= roundup2(tlv_len, 4);             /* :637 — UNDERFLOW:
                                                     len == tlv_len and tlv_len%4 != 0
                                                     -> len = ~SIZE_MAX */
        data += sizeof(*tlv) + roundup2(tlv_len, 4);  /* :638 — advances past end */
        switch (...) { ... }                     /* next iteration reads tlv header OOB */
}
```
Two reachable underflows, both leaving the firmware image buffer:

1. **Trailing unaligned TLV**: a final TLV whose `length` is not 4-aligned
   and whose data ends exactly at end-of-file passes the `len < tlv_len`
   check (len == tlv_len), then `len -= roundup2(tlv_len, 4)` subtracts
   more than remains → `len` wraps to ~SIZE_MAX and `data` points past the
   buffer. The loop keeps parsing TLV headers from kernel heap memory
   beyond the `firmware` image; whatever it "finds" can pass the per-type
   checks (e.g. `IWM_UCODE_TLV_SEC_RT` only needs `tlv_len >= 4`), causing
   `iwm_firmware_store_section()` to record a data pointer *outside* the
   image which `iwm_pcie_load_section()` later memcpys and DMAs to the
   device (`:2396-2446`) — heap OOB read with attacker-steered length.
2. **Short file**: `datasize < sizeof(struct iwm_tlv_ucode_header)` (88)
   underflows `len` immediately (`:620`); and the magic check at `:607`
   dereferences `*(const uint32_t *)fw->fw_fp->data` before any size check
   (fault/OOB read on a <4-byte file).

This is the iwm counterpart of iwn's DF-3577 (TLV `len>=4` lesson), with a
different mechanism (roundup2 underflow rather than missing per-case check).

## Threat model
Crafted firmware file (`firmware_get(sc->cfg->fw_name)`, `:585`). Installing
one requires root (kld module under /boot, or loader(8) paths) — a
privileged-file-replacement / supply-chain trust boundary, hence Low
severity, consistent with DF-3577 (Low).

## Reproduce (logic-level, executed on the audit guest)
`tlv_underflow.c` in this pack is a faithful userland port of the exact
loop (`:619-638` verbatim, same types, same checks) with the crafted image
placed at a guard page:

- Case A: 113-byte image, final TLV type=SEC_RT(19) len=5 at image end →
  iter 2 prints `remaining len field = 18446744073709551613
  (0xfffffffffffffffd)` then **SIGSEGV (exit 139)** reading the next TLV
  header past the image (run.log).
- Case B: 40-byte file (< 88-byte header) → immediate underflow →
  **SIGSEGV (139)** (run.log).
- Fixed variant (`tlv_underflow_fixed.c`: check `len < roundup2(tlv_len,4)`
  + header-size guard): both cases return clean errors, **exit 0**
  (run_fixed.log) — the exact arithmetic of fix.diff validated.

## Fix
`fix.diff` — header-size guard before the magic read, and
`if (len < roundup2(tlv_len, 4))` as the bound check (subsumes the
`len < tlv_len` check).
