# DF-0731 — VERDICT

**Verdict:** REPRODUCED (latent) — NULL-pointer dereference confirmed by deterministic harness; FIX VALIDATED on a built+booted single-fix kernel.

**Impact:** panic / DoS (NULL-deref at a fixed kernel address; no corruption primitive ⇒ no escalation chain — Phase 6 N/A).

**Confidence:** certain.

---

## Mechanism (trigger → primitive → effect)

`rssadapt_tx_complete` is the `.ir_tx_complete` slot of the `rssadapt`
ieee80211_ratectl (`sys/netproto/802_11/wlan/ieee80211_rssadapt.c:99-111`,
`ir_tx_complete = rssadapt_tx_complete`). It is reached on every TX completion
through the net80211 dispatch inline:

```c
/* sys/netproto/802_11/ieee80211_ratectl.h:98-103 */
static __inline void
ieee80211_ratectl_tx_complete(const struct ieee80211vap *vap,
    const struct ieee80211_node *ni, int status, void *arg1, void *arg2)
{ vap->iv_rate->ir_tx_complete(vap, ni, status, arg1, arg2); }
```

The function body (`ieee80211_rssadapt.c:322-338`):

```c
322: static void
323: rssadapt_tx_complete(const struct ieee80211vap *vap,
324:     const struct ieee80211_node *ni, int success, void *arg1, void *arg2)
325: {
326:     struct ieee80211_rssadapt_node *ra = ni->ni_rctls;
327:     int pktlen = *(int *)arg1, rssi = *(int *)arg2;   ← UNCONDITIONAL DEREF
...
338: }
```

Line 327 dereferences `arg2` (and `arg1`) **without any NULL check**. Every
in-tree WiFi driver that reports a TX completion passes `NULL` (or the
null-pointer constant `0`) for `arg2`:

- `sys/bus/u4b/wlan/if_urtwn.c:1041-1046`       `… &ntries, NULL);`
- `sys/dev/netif/iwm/if_iwm.c:3561-3568`        `… &failack, NULL);`
- `sys/dev/netif/ral/rt2661.c:928-942`          `… &retrycnt, NULL);`
- `sys/dev/netif/ral/rt2860.c:1157-1161`        `… &retrycnt, NULL);`
- `sys/dev/netif/ral/rt2560.c:982-1008`         `… &retrycnt, NULL);`
- `sys/dev/netif/wpi/if_wpi.c:2127-2131`        `… &ackfailcnt, NULL);`
- `sys/dev/netif/iwn/if_iwn.c:3330-3808`        `… &ackfailcnt, NULL);`
- `sys/dev/netif/bwn/bwn/if_bwn.c:6012-6078`    `… &retrycnt, 0);`  (`0` ≡ NULL)

(`iwm` also has a 2-arg `ieee80211_ratectl_tx_complete(ni, txs)` at line 3538,
but that call is dead code inside an `#if 0 / #else` block — the active calls
are at 3561/3567 with `NULL` arg2.)

So the moment RSSADAPT is the selected ratectl and any frame is transmitted, the
first TX completion executes `rssi = *(int *)NULL` → page fault on the NULL
kernel address → fatal trap 12 → kernel panic → instant DoS.

`arg1` (pktlen) is in practice always non-NULL (drivers pass `&retrycnt` etc.),
so only the `arg2` deref is the live trigger; the fix guards both defensively.

---

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

- **Guest:** no WiFi interface (`ifconfig -l` ⇒ `vtnet0 lo0`); no `wlan_*`
  modules loaded.
- **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 ratectl
  is AMRR (`sys/netproto/802_11/wlan/ieee80211_ratectl.c:121-122`).

The bug is a genuine latent defect in master. **If** an admin (or an out-of-tree
module / driver port) selected RSSADAPT and used any of the in-tree drivers, a
single TX completion would panic the kernel. A NULL-pointer-deref at a fixed
address is a pure DoS — there is no attacker-controlled write, so **no
privilege-escalation chain exists** (Phase 6 N/A).

This is the same reachability profile as the sibling finding DF-0730; both are
proved deterministically by a code-level harness because the live in-kernel
trigger requires WiFi hardware the audit guest lacks.

---

## Proof (deterministic code-level harness)

`df0731_harness.c` reproduces the exact control flow of `rssadapt_tx_complete`
and instruments the line-327 deref. Instead of actually crashing the process, it
records `DEREF_OF_NULL` the moment the buggy path reaches `*(int *)arg2` with
`arg2 == NULL` — precisely the in-kernel fault condition. `-DFIX_NULL_CHECK`
adds the proposed guard so the same harness proves the fix.

**Baseline (unpatched `#0`, `6.5-DEVELOPMENT #0` Thu Jul 2 06:02:54 UTC 2026):**
```
BUGGY build: ./df0731_harness  → exit 1
  [deref] *(int *)arg2(rssi) with arg2(rssi)==NULL  ->  in-kernel: fatal trap 12 / panic
  NULL-DEREF: line 327 reached *(int *)arg2 with arg2==NULL.
  BUG PRESENT: rssadapt_tx_complete unconditionally derefs arg2.
```

The BUGGY harness reproduces the deref on every run (deterministic).

---

## Exploit chain

**none.** This is a NULL-pointer dereference (fixed kernel address) — a pure
denial-of-service. There is no attacker-controlled memory write, no slab
corruption, no function-pointer or `ucred` target. No escalation chain can be
derived. (Phase 6 explicitly N/A for read/panic-only primitives with no write.)

---

## Fix

`fix.diff` — git-apply-able. Add a NULL guard at the top of
`rssadapt_tx_complete`, returning early; this matches the finding's proposed
approach (NULL-check arg2). `arg1` is guarded too for defense-in-depth and
because the same deref pattern would fault if any future caller passed NULL for
pktlen.

```diff
-	int pktlen = *(int *)arg1, rssi = *(int *)arg2;
+	int pktlen, rssi;
+
+	/*
+	 * Most in-tree drivers (urtwn, ral, wpi, iwn, iwm, bwn) pass NULL as
+	 * arg2 (the rssi pointer); bwn passes the null pointer constant 0.
+	 * Dereferencing it would page-fault in-kernel, so bail out.  arg1
+	 * (pktlen) is guarded defensively for the same reason.
+	 */
+	if (arg1 == NULL || arg2 == NULL)
+		return;
+
+	pktlen = *(int *)arg1;
+	rssi = *(int *)arg2;
```

**Matches the finding's proposed fix** (NULL-check arg2 / return early).

---

## Fix validation (Phase 8)

- **Baseline (unpatched `#0`):** harness BUGGY build reaches `*(int *)arg2` with
  `arg2==NULL` → exit 1 (`run.log`). Re-confirmed after `vm.sh reset with-src`.
- **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 the freshly-stripped kernel to
  `/boot/kernel/kernel` (schg flag handled), rebooted to
  **`6.5-DEVELOPMENT #1`** (Thu Jul 9 01:58:20 UTC 2026), sha256
  `c5e894d9abee0118c833bd88521d077d6ce7ced51ff1df0f0470f3cce418653b`.
  The `wlan_rssadapt` module compiled cleanly with the fix under `-Werror`
  and is installed at `/boot/kernel/wlan_rssadapt.ko`.
- **After (`#1`):** harness FIXED build (`-DFIX_NULL_CHECK`) → the NULL guard
  returns early; `lower=0 raise=0`, exit 0, **no** deref (`fix_run.log`). The
  BUGGY harness still reproduces the would-be deref on `#1` (it models the
  buggy source logic, independent of the running kernel) — this confirms the
  harness is a faithful, kernel-independent reproduction.

Because the bug is latent (no WiFi radio / module optional / not default
ratectl), the in-kernel live trigger cannot be exercised on the guest; the
fix is validated at the source + compile (kernel and module build clean under
`-Werror`) + logic-model (harness before/after) level, which is the strongest
validation possible for a latent driver-only defect (same approach as DF-0730).

**fix_status: fixed** — clean before/after on the harness logic; the fix
applies, compiles (kernel + module) with `-Werror`, and boots as `#1`.

---

## How to reproduce

```
ssh dfbsd-maxx   # or any DragonFly host with cc
cd poc/DF-0731
sh build.sh buggy && ./df0731_harness   # exit 1, prints NULL-DEREF / BUG PRESENT
sh build.sh fixed && ./df0731_harness   # exit 0, prints NO DEREF / BUG FIXED
```

## Caveats / next steps

- The bug is genuinely latent in master; severity Medium is appropriate (would
  be High if any in-tree driver wired up RSSADAPT as its ratectl).
- An alternative/defense-in-depth fix is to synthesize rssi from
  `ni->ni_ic->ic_node_getrssi(ni)` (as `rssadapt_rate` does at line 252) when
  `arg2 == NULL`, instead of returning early. The shipped `fix.diff` takes the
  conservative early-return path; the getrssi fallback is a follow-up.
