# DF-1157 — `wpi_rx_done` reads `tail` and sets `m_len` from unbounded firmware `head->len` (OOB heap read / oversized mbuf)

## Verdict
**Source-confirmed; NOT reproduced at runtime on this guest (no Intel 3945ABG
wpi adapter).** The bug is a genuine unbounded-firmware-length OOB read +
oversized-mbuf-walk, confirmed by line-by-line source trace. It is the wpi twin
of DF-1123 (iwn). `wpi` **is** compiled into `X86_64_GENERIC:272`, so the code
path exists in the live kernel image, but the RX interrupt handler is only
registered when a wpi adapter attaches — and this QEMU/KVM guest has no WiFi
hardware (`pciconf` shows no 0x028000 device). This is the **valid hard
blocker: runtime-unreachable on this guest (no matching hardware)** — a latent
HW-gated read. The fix was validated to apply + compile + boot clean.

## Mechanism (trigger → primitive → effect)
In `wpi_rx_done()` (`sys/dev/netif/wpi/if_wpi.c`), the RX frame is received
into `data->m`, a `MJUMPAGESIZE` (= `PAGE_SIZE` = **4096**) jumbo cluster
(`if_wpi.c:1069,1990`; `sys/param.h:473`):
```
   if_wpi.c:1961  if (stat->len > WPI_STAT_MAXLEN) goto fail1;   // stat bounded to 20
   if_wpi.c:1967  head = (struct wpi_rx_head *)((caddr_t)(stat+1) + stat->len);
   if_wpi.c:1968  len  = le16toh(head->len);          // firmware u16, NO upper bound
   if_wpi.c:1969  tail = (struct wpi_rx_tail *)((caddr_t)(head+1) + len);
   if_wpi.c:1970  flags = le32toh(tail->flags);        // <-- DEREF tail BEFORE any bound
   ...
   if_wpi.c:1984  if (len < sizeof(struct ieee80211_frame_ack)) ...   // LOWER bound, AFTER tail deref
   ...
   if_wpi.c:2028  m->m_pkthdr.len = m->m_len = len;    // unbounded -> ieee80211_input walk
```
- `head->len` is a firmware-controlled `uint16_t` (0..65535) with **no upper
  bound**. The space from `head+1` to the end of the 4096-byte cluster is
  ~4036 bytes (after `stat->len ≤ 20`), so any `len > ~4036` makes `tail`
  point **past the 4KB cluster**.
- `flags = le32toh(tail->flags)` at `:1970` **dereferences that OOB `tail`
  before** the lower-bound check at `:1984` → **OOB heap read / info leak** (or
  fault).
- `m_len = len` at `:2028` then hands `ieee80211_input` an mbuf claiming an
  oversized body → walks past the cluster.

## Threat model / reachability
- **Attacker:** a buggy or hostile wpi firmware/PHY, or a malicious 802.11 AP
  (firmware inconsistency). AV:A/AC:H per the CVSS. Reached on the wpi RX
  interrupt path.
- **On this guest:** NOT reachable — no wpi adapter, so the handler never
  registers. wpi is in GENERIC so the vulnerable code ships in the default
  kernel, but it is inert without the hardware.

## Exploit chain
None — valid hard blocker (HW-gated). No unprivileged-guest syscall drives the
wpi RX path; the only inputs are a firmware/PHY inconsistency or a crafted RF
frame, neither of which is present on the guest.

## PoC changes
Authored from scratch. Deliverables: `fix.diff` (upper-bound `head->len`
against the cluster before the tail deref), `build.sh`, `run.sh` (document the
runtime-unreachable status), `VERDICT.md`, `manifest.json`, `env.txt`,
`fix_validation.txt`, `fix_build.log`.

## Recommended fix
`fix.diff` inserts, right after `len = le16toh(head->len)` (`if_wpi.c:1968`)
and **before** computing/dereferencing `tail`, an upper bound against the RX
cluster:
```c
if ((caddr_t)(head + 1) + len + sizeof(struct wpi_rx_tail) >
    mtod(data->m, caddr_t) + MJUMPAGESIZE) {
	DPRINTF(sc, WPI_DEBUG_RECV, "%s: frame too long: %d\n", __func__, len);
	goto fail1;
}
```
This matches the iwn (DF-1123) fix shape. With the guard, `(head+1)+len+tail`
can never exceed the 4KB cluster, so the tail deref and the oversized `m_len`
are both eliminated.

## Fix validation (Phase 8)
- `fix.diff` applies cleanly (`git apply --check` OK; Hunk #1 @1966).
- Combined single-fix kernel build (DF-1157 + DF-1172 fixes together, warm obj): `make -j6 nativekernel KERNCONF=X86_64_GENERIC` → **NK_DONE rc=0**; `if_wpi.o` compiled clean under `-Werror`; booted `#1: Fri Jul 17 06:10:36 UTC 2026`.
- The bound check is **unconditional** (not under `WPI_DEBUG`); the `DPRINTF` message string is absent from the binary only because `DPRINTF` is a no-op unless `WPI_DEBUG` is defined (`if_wpi_debug.h:25,141`).
- `fix_status: not_testable` for runtime: no wpi adapter on the guest to drive the RX path. Validated at apply + compile + boot level.

## Kernel references (confirmed)
- `sys/dev/netif/wpi/if_wpi.c:1968` — `len = le16toh(head->len)` (unbounded)
- `sys/dev/netif/wpi/if_wpi.c:1969-1970` — tail computed/derefed before bound check
- `sys/dev/netif/wpi/if_wpi.c:1984` — lower-bound check (too late)
- `sys/dev/netif/wpi/if_wpi.c:2028` — `m_len = len` (oversized mbuf walk)
- `sys/dev/netif/wpi/if_wpi.c:1069,1990` — RX buffer = `MJUMPAGESIZE`
- `sys/param.h:473` — `MJUMPAGESIZE = PAGE_SIZE = 4096`
- `sys/dev/netif/wpi/if_wpireg.h:306` — `WPI_STAT_MAXLEN 20`
- `sys/config/X86_64_GENERIC:272` — `device wpi`
