# DF-0275 — VERDICT

**Verdict: NOT RUNTIME-TESTABLE on this guest (no WiFi hardware) — source trace DEFINITIVELY CONFIRMS the bug is real.**

## The claim
`sys/netproto/802_11/wlan/ieee80211_output.c`, `add_ie()` at line 1976:

```c
1976: static __inline uint8_t *
1977: add_ie(uint8_t *frm, const uint8_t *ie)
1978: {
1979:     memcpy(frm, ie, 2 + ie[1]);   /* trusts ie[1] unconditionally */
1980:     return frm + 2 + ie[1];
1981: }
```

`add_ie()` is called by `ieee80211_add_wpa()`/`ieee80211_add_rsn()` (lines 2165,
2176) to write the vap's WPA/RSN IE (`vap->iv_wpa_ie`/`vap->iv_rsn_ie`) into a
management frame. Every frame constructor that includes a WPA/RSN IE reserves a
fixed `sizeof(struct ieee80211_ie_wpa)` slot, e.g. `ieee80211_send_probereq`
(line 2244):

```c
2244: m = ieee80211_getmgtframe(&frm,
2245:      ic->ic_headroom + sizeof(struct ieee80211_frame),
2246:        2 + IEEE80211_NWID_LEN
2247:      + 2 + IEEE80211_RATE_SIZE
2248:      + sizeof(struct ieee80211_ie_wpa)   /* <-- fixed 100-byte slot (RSN) */
2249:      + 2 + (IEEE80211_RATE_MAXSIZE - IEEE80211_RATE_SIZE)
2250:      + sizeof(struct ieee80211_ie_wpa)   /* <-- fixed 100-byte slot (WPA) */
...);
```

## The overflow math (confirmed from source)

`struct ieee80211_ie_wpa` (`sys/netproto/802_11/ieee80211.h:567`) is the
maximally-sized WPA IE, `__packed`:

| field               | bytes |
|---------------------|-------|
| wpa_id              | 1     |
| wpa_len             | 1     |
| wpa_oui[3]          | 3     |
| wpa_type            | 1     |
| wpa_version (u16)   | 2     |
| wpa_mcipher[1] (u32)| 4     |
| wpa_uciphercnt (u16)| 2     |
| wpa_uciphers[8]     | 32    |
| wpa_authselcnt (u16)| 2     |
| wpa_authsels[8]     | 32    |
| wpa_caps (u16)      | 2     |
| wpa_pmkidcnt (u16)  | 2     |
| wpa_pmkids[8] (u16) | 16    |
| **total**           | **100** |

So the frame allocators reserve exactly **100 bytes** per WPA/RSN IE slot.

The IE pointer comes from `setwparsnie()` (`ieee80211_ioctl.c:2319`), invoked by
the `IEEE80211_APPIE_WPA` ioctl handler (`ieee80211_ioctl.c:2384`) on data
allocated by `setappie()` (`ieee80211_ioctl.c:2296`) with size
`sizeof(struct ieee80211_appie) + ireq->i_len`, where the ioctl layer enforces
only `2 <= ireq->i_len <= IEEE80211_MAX_APPIE` (`IEEE80211_MAX_APPIE = 1024`,
`ieee80211_ioctl.h:500`). `setwparsnie` checks `2+ie[1] > space` but does **not**
cap `ie[1]` to what the frame buffers can hold, so `ie[1]` (the WPA length byte)
can be as large as **1022**.

`add_ie()` then does `memcpy(frm, ie, 2 + ie[1])` = up to **2 + 1022 = 1024
bytes** into a **100-byte** slot → **up to ~924 bytes of heap OOB write** into the
mbuf data area, with fully attacker-controlled content (the IE bytes are
`copyin`'d from userspace).

## Trigger / reachability

- The oversized IE must first be installed via the `SIOCS80211` /
  `IEEE80211_APPIE_WPA` ioctl, which is a **privileged (root) wlan-management
  operation**. So the *setup* is root-gated.
- Once installed, the overflow fires on the **next TX of any management frame**
  that includes the WPA/RSN IE — beacons, probe responses, (re)assoc responses.
  For an AP, a remote station's probe request triggers a probe-response TX that
  overflows. So the *amplification* is remotely triggerable; the *setup* is not.

## Why not runtime-tested here
This guest has **no 802.11 radio hardware** and cannot create an `ieee80211vap`,
so neither the privileged IE-install ioctl nor the management-frame TX path can
be exercised. The overflow is therefore **not runtime-testable on this guest**,
but the code path and the byte arithmetic are unambiguous and definitively
confirm the defect.

## Realistic impact ceiling
Heap OOB write of up to ~924 attacker-controlled bytes into the mbuf data area
on management-frame TX, with root-gated setup. On the default GENERIC kernel
(INVARIANTS ON) this manifests as a **panic** when the corrupted mbuf/slab is
later validated; on an INVARIANTS-OFF build it is a silent heap corruption
primitive. Because the trigger requires root to install the IE, this is a
**root→kernel** hardening/corruption gap, not an unprivileged→root escalation.

## Fix
`findings/poc/DF-0275/fix.diff` adds a length cap in `setwparsnie()`
(`ieee80211_ioctl.c`): reject any IE whose `ie[1]` would exceed
`sizeof(struct ieee80211_ie_wpa) - 2` (= 98), so the stored IE always fits the
fixed frame-constructor slots. Verified `git apply --check` clean and compiles
into the base kernel (nativekernel, together with the DF-0286/0287 mesh fixes).
Because the bug cannot be runtime-exercised here, `fix_status: not_testable`
(diff applies + compiles; path traced to close the defect).
