# DF-3363 — `maxrate()` heap OOB read via unvalidated `htcap_ie` from the saved beacon IE blob

File: `sys/netproto/802_11/wlan/ieee80211_scan_sta.c` (pass 2)

## What

`sta_add()` saves the beacon's entire IE stream verbatim into an
exactly-sized `kmalloc(ies_len)` blob
(`ieee80211_ies_init`, ieee80211_node.c:938-964, called from
ieee80211_scan_sta.c:380). `ieee80211_ies_expand()`
(ieee80211_node.c:983-1024 — "we don't do any validity checking of ie
lengths") then sets `se->se_ies.htcap_ie` for **any** `IEEE80211_ELEMID_HTCAP`
IE regardless of its declared length. `parse_beacon`'s HTCAP length
validation (ieee80211_input.c:733-739) only NULLs `sp->htcap`, which
`sta_add` never uses.

`maxrate()` (ieee80211_scan_sta.c:773-810, reached from
`select_bss -> sta_compare` whenever two candidates' RSSI are within 5)
dereferences that pointer without any length check:

- `htcap->hc_mcsset[12]` — IE offset **17**
- `isclr(htcap->hc_mcsset, i)` for i in [0,31] — offsets 5-8
- `le16dec(&htcap->hc_cap)` — offsets 2-3

A beacon whose (TLV-well-formed) HTCAP IE declares len < 16 and is the
**last** IE of the blob therefore makes the kernel read up to **16 bytes
past the end of the heap allocation**.

## Reproduced (stock INVARIANTS kernel #0)

Beacon B advertises only 1/2/5.5 Mbps and ends in a 2-byte HTCAP IE
(id 45, len 0) inside an exactly-64-byte blob. Beacon A advertises
54 Mbps with no HT IE. Both at rssi 30. Heap grooming places 0xff-filled
chunks after B's blob:

- groom 0xff past the allocation: **B (5.5 Mbps phantom "MCS31 HT40") is
  selected and joined over A (54 Mbps)** — maxrate consumed the
  out-of-bounds bytes as HT capabilities.
- groom 0x00: **A wins** (correct).
- valid 26-byte zero HTCAP: **A wins** (control).

B.blob and its 0xff neighbor were verified to differ by exactly 0x40
(64) — the OOB bytes ARE the neighbor chunk. Kernel-to-kernel
differential on identical beacons: only the heap bytes past the
allocation changed the association decision.

## Threat

Unauthenticated 802.11 beacon/probe-response (attacker within radio
range) during any scan; no special vap configuration. Impact: bounded
heap OOB read (≤16 bytes) that steers AP selection — a kernel-heap
content oracle with control-flow influence; no direct disclosure or
panic observed.

## Build / run (repo root)

```
sh findings/poc/DF-3363/build.sh     # builds /root/fwscan/fwscan.ko in guest
sh findings/poc/DF-3363/run.sh       # fresh vap per variant; see run*.log
```

Fix validated in-guest (kernel #2): see `fix_validation.log` and
`fix.diff` (also covers DF-3364/DF-3365 sites).
