# DF-3459 — VERDICT

**Status: REPRODUCED** (code-level harness, latent module on this guest)
**Impact: panic (INVARIANTS via iwm KASSERT; production via ~+8GiB wild access in
rssadapt_lower_rate) / 2-byte wild heap write where chunk+8GiB is mapped**
**Confidence: certain** (source-level arithmetic; harness run on the audit guest)

## How it reproduces

`df3459_harness.c` replicates the exact struct layout (with DF-0730's resize
already applied — 15 columns — so the only negative-index effect demonstrated is
this finding's) and the exact code paths:

* `rssadapt_rate()` loop as shipped (ieee80211_rssadapt.c:253-255);
* `:258` `rs_rates[rix]` read; `:259` `ra_rix` store;
* `rssadapt_lower_rate()` as shipped (:279 `u_int rix`, :283 `rix = ra->ra_rix`,
  :284/:285 `(*thrs)[rix]` read+write).

Buggy run (`run.log`, guest `DragonFly dfbsd 6.5-DEVELOPMENT #0`, cc 8.3):

```
rssi=0 fresh node : rssadapt_rate -> rix=-1  ra_rix=-1  ni_txrate=0x08 (bogus: rs_nrates byte 8)
  -> rs_rates[-1] read returned the rs_nrates byte (1): in-struct OOB read confirmed
  -> iwm_setrates KASSERT(rix >= 0 && rix < nrates): VIOLATED  (INVARIANTS kernel would PANIC here)
lower_rate with ra_rix=-1: u_int rix = 0xffffffff -> access at chunk+0x200000032 (wild +7 GiB)
  -> WILD ACCESS confirmed: kernel equivalent is a ~+8GiB READ+WRITE from the rssadapt_node chunk -> kernel page fault -> PANIC
RESULT: BUG REPRODUCED — rix=-1: in-struct rs_rates[-1] read, iwm KASSERT violation, and u_int-indexed ~+8GiB wild read+write in rssadapt_lower_rate()
```

The wild access is performed in a forked child with a SIGSEGV trapper so the
harness can *report* the fault the kernel cannot catch. The computed address
`chunk+0x200000032` = offsetof(ra_rate_thresh)=52 + 2×0xffffffff — exactly the
kernel's arithmetic.

## Why rix = -1 is reachable in the field (not just theory)

* `node_getrssi()` (ieee80211_node.c:1146-1155) returns **0** while
  `ni_avgrssi == IEEE80211_RSSI_DUMMY_MARKER` (node created, no RX frame sampled
  yet) and clamps negative RSSI to 0. Fresh node + zeroed thresholds (M_ZERO,
  ieee80211_rssadapt.c:184-185) → `(*thrs)[rix] < (rssi << 8)` is `0 < 0` for
  every row → loop exhausts → **rix = -1 on the first rate query of every new
  node with an unsampled RSSI**.
* Sustained-decay scenario: `rssadapt_lower_rate` raises thresholds toward the
  failure-time `rssi<<8` with `interpolate()` = `(4*old+4*new)/8`, which converges
  to exactly `rssi<<8`; the comparison is strict `<`, so once every row ≥ current
  `rssi<<8` the next `rssadapt_rate` again exhausts the loop.

## Consequence chain (as shipped)

1. `rix = -1` returned by `.ir_rate` → `iwm_setrates(sc, in, -1)`
   (if_iwm.c:3540-3544, :3574-3578, :4646) → `KASSERT(rix >= 0 && rix < nrates)`
   (if_iwm.c:4366) → **fatal kernel panic on INVARIANTS kernels**. Other drivers
   ignore the return (`(void) ieee80211_ratectl_rate(...)` — rt2560/2661/2860,
   wpi, iwn).
2. `ra_rix = -1` stored → next failed TX → `rssadapt_lower_rate`:
   `u_int rix = 0xffffffff` → `(*thrs)[rix]` = read+write at `chunk+0x1fffffffe`
   (~+8 GiB). Unmapped KVA → page fault → **panic on production kernels**.
   Mapped (large-RAM direct map) → **2-byte wild write** with value
   `interpolate(last_thr, rssi<<8)` (rssi is the remote peer's signal strength —
   semi-attacker-influenced).
3. `ni_txrate = rs_nrates & 0x7f` (bogus rate code, in-struct read at :206 for
   empty ratesets too).

## Latency caveat (why Medium, not High)

Identical to DF-0730/DF-0731: `wlan_rssadapt` is `optional` (sys/conf/files:1655),
NOT in X86_64_GENERIC, no in-tree driver calls
`ieee80211_ratectl_set(vap, RSSADAPT)` (default is AMRR,
ieee80211_ratectl.c:121-122), and the audit guest has no WiFi radio
(`ifconfig -l` → vtnet0 lo0). Runtime verification on this guest is therefore a
hard blocker; the code-level harness plus source trace is the strongest
demonstration available. **If a driver wires RSSADAPT up, this is a
remote-triggerable (any TX failure on a fresh/zero-RSSI node) ~+8GiB wild write —
High.**

## Exploit chain

Not developed: module cannot be loaded/selected on the audit guest (no radio, no
selector). In a hypothetical wired-up deployment the primitive would be:
fresh-node RSSI=0 → forced TX failure (remote deauth/flood) → wild 2-byte write at
`ra+0x1fffffffe` — a fixed-offset corruption with a bounded controlled value; on
typical RAM footprints the offset lands in unmapped KVA → DoS (panic) is the
realistic ceiling.

## Fix validation

* `fix.diff`: clamp `rix = 0` when the pick loop exhausts (:253), bail on empty
  ratesets in `rssadapt_node_init`, and defensively refuse to adapt when
  `ra_rix < 0` in `rssadapt_lower_rate` (the u_int hazard).
* Applied to the guest's /usr/src copy: `wlan_rssadapt` module rebuilds clean
  (`make` rc=0, full kernel `-Werror` CFLAGS — `fix_module_build.log`).
* Harness `fixed` mode: rix=0, no KASSERT violation, no fault, canaries intact
  (`run.fixed.log`).
* Guest source restored to pristine afterwards (sha256 verified against the
  audit tree).

## Kernel references

sys/netproto/802_11/wlan/ieee80211_rssadapt.c:202-206 (node_init pick loop / empty rateset)
sys/netproto/802_11/wlan/ieee80211_rssadapt.c:253-259 (rate pick loop, rs_rates[rix], ra_rix store)
sys/netproto/802_11/wlan/ieee80211_rssadapt.c:279,283-286 (u_int rix; wild read+write)
sys/netproto/802_11/wlan/ieee80211_node.c:1146-1155 (node_getrssi returns 0 / clamps)
sys/dev/netif/iwm/if_iwm.c:3540,3574,4366,4646 (KASSERT consumer)
sys/netproto/802_11/ieee80211_ratectl.h:91-95 (unclamped ir_rate return)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
