# DF-0730 — `ra_rate_thresh[3][8]` heap OOB read/write (latent / code-level proof)

## Verdict: REPRODUCED (code-level harness) — bug is REAL but LATENT on this guest

The array-dimension/index mismatch described in the finding is **real and
confirmed at the source level**. It is **not** triggerable live on this audit
guest because the guest has **no WiFi radio** and the `wlan_rssadapt` module is
**not in the default kernel** and **not selected by any in-tree driver**
(default rate-adapt is AMRR). It is therefore a **latent code-correctness /
defense-in-depth** defect: a remote-triggered kernel heap OOB read+write that
would become live the moment any driver selects the RSSADAPT algorithm.

## Mechanism (every hop cited `path:line`)

1. **The array is too small for its index space.**
   - `sys/netproto/802_11/ieee80211_rssadapt.h:65-66` declares
     `uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS=3][IEEE80211_RATE_SIZE=8]`.
   - `sys/netproto/802_11/_ieee80211.h:374-375`:
     `IEEE80211_RATE_SIZE = 8`, `IEEE80211_RATE_MAXSIZE = 15`.

2. **`rs_nrates` can legitimately reach 15.**
   - `sys/netproto/802_11/wlan/ieee80211_input.c:413-439`
     (`ieee80211_setup_rates`) merges the Supported Rates IE (≤8) with the
     Extended Rates IE, capping the **total** at `IEEE80211_RATE_MAXSIZE = 15`
     (`:428-429`). A standard 802.11g AP advertises 12 rates; 15 is the spec max.
   - `sys/netproto/802_11/wlan/ieee80211_rssadapt.c:198`
     (`rssadapt_node_init`) copies the rateset verbatim (`ra->ra_rates = *rs`),
     so `ra->ra_rates.rs_nrates ∈ [0, 15]`.

3. **The rateset drives the OOB index.**
   - `rssadapt_rate` `:253-254` loops `for (rix = rs->rs_nrates-1; rix >= 0; rix--)`
     and reads `(*thrs)[rix]` — `rix` can be **14**.
   - `rssadapt_lower_rate` `:285` writes `(*thrs)[rix] = interpolate(...)`.
   - `rssadapt_raise_rate` `:310` writes `(*thrs)[rix+1]` (up to index **15**).
   - `thrs = &ra->ra_rate_thresh[bucket(pktlen)]` (`:249/:281/:301`), and
     `bucket()` returns **2** for `pktlen > 1024` (`:220-233`,
     `IEEE80211_RSSADAPT_BKT0 * 2^BKTPOWER = 128*8 = 1024`).
   - The typedef `uint16_t (*thrs)[IEEE80211_RATE_SIZE]` at `:241/:278/:297`
     bakes the wrong (8) row stride into every dereference.

4. **Worst case → heap OOB.** For `bucket=2, rix=14`:
   `(*thrs)[14]` = `ra->ra_rate_thresh[2][14]`, which is 6 × `uint16_t`
   (**12 bytes**) past `ra->ra_rate_thresh[2][7]`, i.e. **past the end of the
   `struct ieee80211_rssadapt_node`** (it is the last field). Since the node is
   `kmalloc(sizeof(...), M_80211_RATECTL)` (`:184`), this is a **heap OOB
   read+write of up to 12–14 bytes**. The harness proves it lands 10 bytes past
   the struct for `rix=14, bucket=2` (struct=104 B, write at offset 112).

## Proof (deterministic code-level harness)

`df0730_harness.c` mirrors the exact struct layout and the `rssadapt_lower_rate`
write logic, allocates the struct inside a canaried region, and triggers the
write at `bucket=2, rix=14`. Built two ways:

| Build                        | Array dim | Struct size | Write offset | Verdict                |
|------------------------------|-----------|-------------|--------------|------------------------|
| buggy  (`THRESH_COLS=8`)     | `[3][8]`  | 104 B       | 112          | **OOB, canary clobbered** (exit 1) |
| fixed  (`THRESH_COLS=15`)    | `[3][15]` | 144 B       | 140          | in-bounds, canary intact (exit 0) |

The buggy build is additionally flagged by gcc itself:
`warning: array subscript 14 is above array bounds of 'uint16_t[8]' [-Warray-bounds]`
(see `build.log`). The fixed build produces no such warning.

## Reachability / threat model (why "latent")

- **Guest:** no WiFi interface (`ifconfig -l` ⇒ `vtnet0 lo0`); no `wlan_*`
  modules loaded. Net80211 is never instantiated with a live vap.
- **Default kernel:** `wlan_rssadapt` is `optional wlan_rssadapt`
  (`sys/conf/files:1655`) — **not** compiled into `X86_64_GENERIC`.
- **No driver selects it:** no in-tree driver calls
  `ieee80211_ratectl_set(vap, IEEE80211_RATECTL_RSSADAPT)`. The default is
  AMRR (`ieee80211_ratectl.c:122`; `ieee80211.c:671` seeds NONE).

**If** a driver (in-tree port or out-of-tree module) selected RSSADAPT **and** a
peer advertised >8 rates (trivial — every 11g AP), then a single TX-complete on
a >1024-byte frame would corrupt the per-node slab object. With an attacker
controlling the advertised rates and frame timing, this is a **remote,
unauthenticated kernel heap OOB read+write** — a serious corruption primitive.
Today it is dormant.

## Exploit chain

`none` — the bug is **not reachable from an unprivileged user on this guest**
(no WiFi radio, module not loaded/selected). This is a valid hard blocker
(dead/unreachable-at-runtime on this guest AND not selectable in the default
config), so no `uid=0` chain is attempted or possible here. The primitive is
characterized at the code level: it is a 12–14-byte heap OOB write past a
`kmalloc(M_80211_RATECTL)` per-node object, attacker-shapeable via the peer's
rate IE, on the `wlan_rssadapt` TX-complete path.

## Fix

Resize the array to match the index space and update the three typedef sites:

```diff
-	uint16_t		ra_rate_thresh[IEEE80211_RSSADAPT_BKTS]
-					      [IEEE80211_RATE_SIZE];
+	uint16_t		ra_rate_thresh[IEEE80211_RSSADAPT_BKTS]
+					      [IEEE80211_RATE_MAXSIZE];
```
and the three `uint16_t (*thrs)[IEEE80211_RATE_SIZE];` → `[IEEE80211_RATE_MAXSIZE]`
in `ieee80211_rssadapt.c` (lines 241, 278, 297).

Full diff in `fix.diff`. **Matches the finding's proposed fix.**

## Fix validation (Phase 8)

- **Baseline (unpatched `#0`):** buggy harness → OOB confirmed, canary clobbered,
  gcc `-Warray-bounds` warning (see `build.log`, `run.log`).
- **Patched single-fix kernel:** applied `fix.diff` to `/usr/src`, built
  `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (`fix_build.log`,
  `=== NK_DONE rc=0 ===`), installed via `make installkernel`, rebooted to
  `6.5-DEVELOPMENT #1` (Thu Jul 9 01:07:37 UTC 2026), sha256
  `d28b91cedf66…`.
- **After:** harness built against the fixed dim (`THRESH_COLS=15`) → write
  in-bounds (offset 140 < struct end 144), canary intact, exit 0, **no**
  `-Warray-bounds` warning (`fix_run.log`).

The fix **closes** the OOB: struct grows 104→144 B (array +42 B), the worst-case
`rix=14` write now lands inside the array, and the compiler no longer flags an
array-bounds violation.

## How to reproduce

```
ssh dfbsd-maxx   # or any DragonFly host with cc
cd poc/DF-0730
sh build.sh buggy && ./df0730_harness   # exit 1, prints OOB / canary clobbered
sh build.sh fixed && ./df0730_harness   # exit 0, prints IN-BOUNDS / canary intact
```

## Caveats / next steps

- The bug is genuinely latent in master; severity Medium is appropriate (would
  be High/Critical if any driver wired up RSSADAPT).
- A defense-in-depth alternative is to also cap `rix` to
  `IEEE80211_RATE_SIZE-1` in the loops, but resizing the array is the correct
  root-cause fix and is what the finding proposes.
- Note for future runs: on DragonFly, `cp kernel.stripped /boot/kernel/kernel`
  is rejected by the loader ("Unable to load /kernel/kernel"); use
  `make installkernel` instead (it installs the full debug kernel ELF).
