# DF-0365 — ieee80211_media_setup unbounded rate-set merge overflows rs_rates[15]

## Verdict
**LATENT — code-level buffer overflow confirmed by source trace;
NOT reachable on this guest (no WiFi hardware).**

The loop in `ieee80211_media_setup` collects unique legacy rates into
`struct ieee80211_rateset allrates` whose `rs_rates[]` is exactly
`IEEE80211_RATE_MAXSIZE = 15` bytes (`sys/netproto/802_11/_ieee80211.h:375`),
but the loop has **no check** that `allrates.rs_nrates < 15` before
writing `rs_rates[j]` and incrementing `rs_nrates`. With all of
11a + 11b + 11g + HALF + QUARTER modes advertised by the same NIC
(`ic_modecaps` bits), the union of distinct rates is 17 values —
overflowing the 15-slot array by 2 bytes (stack write past
`rs_rates[14]`), and the follow-on loop at 1595–1603 OOB-reads the
same slots.

## Bug mechanism (source trace)
File: `sys/netproto/802_11/wlan/ieee80211.c`, function
`ieee80211_media_setup` (around line 1564).

```c
1564: memset(&allrates, 0, sizeof(allrates));
1565: for (mode = IEEE80211_MODE_AUTO; mode < IEEE80211_MODE_11NA; mode++) {
1566:     if (isclr(ic->ic_modecaps, mode))
1567:         continue;
1568:     addmedia(media, caps, addsta, mode, IFM_AUTO);
       ...
1571:     rs = &ic->ic_sup_rates[mode];
1572:     for (i = 0; i < rs->rs_nrates; i++) {
       ...
1581:         r = rate & IEEE80211_RATE_VAL;
1582:         for (j = 0; j < allrates.rs_nrates; j++)
1583:             if (allrates.rs_rates[j] == r)
1584:                 break;
1585:         if (j == allrates.rs_nrates) {
1586:             /* unique, add to the set */
1587:             allrates.rs_rates[j] = r;     // <-- j can be 15 or 16
1588:             allrates.rs_nrates++;         // <-- and beyond, no bound
1589:         }
       ...
1594: }
1595: for (i = 0; i < allrates.rs_nrates; i++) {    // OOB read
       ...
1603: }
```

The header (`_ieee80211.h:375`):
```c
#define IEEE80211_RATE_MAXSIZE  15  /* max rates we'll handle */
struct ieee80211_rateset {
    u_int8_t   rs_nrates;
    u_int8_t   rs_rates[IEEE80211_RATE_MAXSIZE];
};
```

## Distinct-rate math (matches finding)
- 11a:    {6,9,12,18,24,36,48,54}                     = 8 values
- 11b:    {2,4,11,22}                                  = 4 values (3 new: 2,4,11,22 — 22 not in a)
- 11g:    union(11a ∪ {2,4,11,22}) — same set as 11a+b
- HALF:   {3,4.5,6,9,12,18,24,27,36} in 0.5 Mb units   → adds 27 (=54/2) and may add others
- QUARTER:{1.5,2.25,3,4.5,6,9,12,13.5,18,27}           → adds more
Union = 17 distinct values > 15 slots ⇒ 2-byte stack overflow.

## Effects if reachable
- Stack buffer overflow at vap-attach time.
- Triggerable by `PRIV_DRIVER` (root) when configuring a multi-mode NIC.
- Stack-protector canary ⇒ deterministic panic (no info-leak / no
  controlled RIP from this alone — but corruption of stack frames
  above allrates could theoretically be exploited if the canary is
  bypassed).

## Why not testable on this guest
Same as DF-0352: the audit guest has **no WiFi hardware** and no
`wlan` module loaded. `ieee80211_media_setup` runs only at
`ieee80211_vap_attach` time, which requires a real WiFi driver
instance. There is no way for an unprivileged user (or even root
without WiFi HW) to exercise this path on the guest.

## Recommended fix
Add a bounds check in the rate-collection loop:
```c
if (j == allrates.rs_nrates) {
    if (allrates.rs_nrates >= IEEE80211_RATE_MAXSIZE)
        break;  /* or continue; */
    allrates.rs_rates[j] = r;
    allrates.rs_nrates++;
}
```
See `fix.diff` for the git-apply-able version.

## Files in this folder
- `fix.diff`     — bounds check before write/increment
- `VERDICT.md`   — this file
- `manifest.json`
- `env.txt`
