β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0731

rssadapt_tx_complete unconditionally dereferences arg2(rssi) NULL β€” every in-tree driver passes NULL β€” instant panic

Summary

ieee80211_rssadapt.c:327 int pktlen=*(int*)arg1, rssi=*(int*)arg2 β€” unconditional arg2 deref. ALL in-tree WiFi drivers (iwn iwm ral/rt2860 rt2560 rt2661 wpi bwn urtwn) call ieee80211_ratectl_tx_complete(...,&retrycnt,NULL) β€” arg2 is always NULL. rssadapt is the ONLY ratectl that interprets arg2 as mandatory int* rssi. amrr.c/sample.c treat args as retry-count only. API contract divergence: drivers supply (retrycnt,NULL) rssadapt demands (pktlen,rssi). First TX-complete callback derefs NULL panics. Also gates DF-0730 heap OOB on tx_complete path (NULL deref fires first preventing OOB). LATENT: no in-tree driver selects rssadapt. Fix: NULL-check arg2 obtain RSSI from ni->ni_ic->ic_node_getrssi.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0731 Β· 13 files
FileTypeDescriptionSize
df0731_harness.c trigger-source deterministic code-level harness reproducing rssadapt_tx_complete; -DFIX_NULL_CHECK proves the fix 7.3 KB view raw
build.sh build-script sh build.sh [buggy|fixed] 442 B view raw
run.sh run-script builds + runs both buggy and fixed variants 408 B view raw
fix.diff suggested-fix git-apply-able: NULL-check arg1/arg2 in rssadapt_tx_complete, return early 848 B view raw
README.md readme human-facing summary + reproduce steps 4.0 KB ↓ raw
VERDICT.md verdict full narrative: mechanism, reachability, fix validation 7.9 KB ↓ raw
build.log build-log buggy harness build output 124 B view raw
run.log run-log harness run (buggy exit 1 / fixed exit 0) 1.4 KB view raw
fix_build.log build-log full make -j6 nativekernel log for the single-fix kernel (NK_DONE rc=0) 5.6 MB ↓ download
fix_run.log run-log harness run on patched #1 kernel 1.4 KB view raw
env.txt environment uname/cc/ifconfig/module state/kernel sha256 for baseline #0 and patched #1 1.4 KB view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-facing summary + reproduce steps
↓ download raw

DF-0731 β€” rssadapt_tx_complete unconditional NULL-deref of arg2 (rssi)

Severity: Medium (latent). Real NULL-pointer dereference; unreachable in the default config (no driver selects wlan_rssadapt, default ratectl is AMRR, no WiFi radio on the audit guest). Would be an instant kernel panic (DoS) on any system that configures RSSADAPT with an in-tree driver.

The bug

sys/netproto/802_11/wlan/ieee80211_rssadapt.c:322-338 β€” rssadapt_tx_complete (the .ir_tx_complete entry of the rssadapt ieee80211_ratectl, registered at line 99-111) unconditionally dereferences its arg2 (the rssi pointer) and arg1 (the pktlen pointer) at line 327:

int pktlen = *(int *)arg1, rssi = *(int *)arg2;

There is no NULL check. It is reached via the net80211 dispatch inline ieee80211_ratectl_tx_complete (sys/netproto/802_11/ieee80211_ratectl.h:98-103) on every TX completion.

Every in-tree WiFi driver that reports a TX completion passes NULL (or the null-pointer constant 0) for arg2:

driver site arg2
urtwn sys/bus/u4b/wlan/if_urtwn.c:1041-1046 NULL
iwm sys/dev/netif/iwm/if_iwm.c:3561-3568 NULL
rt2661 sys/dev/netif/ral/rt2661.c:928-942 NULL
rt2860 sys/dev/netif/ral/rt2860.c:1157-1161 NULL
rt2560 sys/dev/netif/ral/rt2560.c:982-1008 NULL
wpi sys/dev/netif/wpi/if_wpi.c:2127-2131 NULL
iwn sys/dev/netif/iwn/if_iwn.c:3330-3808 NULL
bwn sys/dev/netif/bwn/bwn/if_bwn.c:6012-6078 0 (== NULL)

So the very first TX completion after ieee80211_ratectl_set(vap, IEEE80211_RATECTL_RSSADAPT) page-faults in-kernel β†’ fatal trap 12 β†’ panic β†’ DoS. (arg1/pktlen is always non-NULL in practice β€” drivers pass &retrycnt/ &ackfailcnt/&ntries β€” so only the arg2 deref is the live trigger, but the fix guards both for defense-in-depth.)

Why latent on this guest (no live trigger)

  • No WiFi radio: ifconfig -l β‡’ vtnet0 lo0; no wlan_* modules loaded.
  • wlan_rssadapt is optional wlan_rssadapt (sys/conf/files:1655), not in the default X86_64_GENERIC kernel.
  • Default ratectl is AMRR (sys/netproto/802_11/wlan/ieee80211_ratectl.c:121-122); RSSADAPT must be explicitly selected.
  • Therefore the dispatch never targets rssadapt_tx_complete live. The bug is proved deterministically by the code-level harness (df0731_harness.c), exactly the same approach used for the sibling finding DF-0730.

Impact

Pure NULL-deref at a fixed kernel address β‡’ panic / DoS. No memory corruption, no primitive derivable β‡’ no escalation chain (Phase 6 N/A).

Files

  • df0731_harness.c β€” deterministic code-level harness reproducing rssadapt_tx_complete; instruments the line-327 deref. -DFIX_NULL_CHECK adds the proposed guard so the same harness proves bug + fix.
  • build.sh [buggy|fixed] β€” builds the harness (buggy default / fixed).
  • run.sh β€” builds + runs both variants.
  • fix.diff β€” git-apply-able fix: NULL-check arg1/arg2, return early.
  • VERDICT.md β€” full narrative + mechanism + citations.
  • build.log / run.log β€” baseline (buggy) build+run on the unpatched #0.
  • fix_build.log β€” full make -j6 nativekernel log for the single-fix kernel.
  • fix_run.log β€” harness run on the patched #1 kernel (buggy+fixed variants).
  • env.txt β€” guest uname / cc / module state / kernel sha256.

Reproduce

sh build.sh buggy && ./df0731_harness    # exit 1: NULL-DEREF / line 327 reached *(int *)arg2
sh build.sh fixed && ./df0731_harness    # exit 0: NO DEREF / NULL guard returned early

Expected

  • Buggy build (matches master): prints NULL-DEREF: line 327 reached *(int *)arg2 with arg2==NULL / BUG PRESENT, exit 1.
  • Fixed build: prints NO DEREF: NULL-arg guard returned early / BUG FIXED, exit 0.
  • On the patched single-fix kernel (6.5-DEVELOPMENT #1, sha256 c5e894d9…) the source-level guard is present and the wlan_rssadapt module compiles cleanly with -Werror.
VERDICT.md verdict full narrative: mechanism, reachability, fix validation
↓ download raw

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:

/* 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):

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:

(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.

-   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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED the fix. fix.diff applies cleanly (git apply --check OK), the single-fix kernel built with make -j6 nativekernel (NK_DONE rc=0, fix_build.log) with the wlan_rssadapt module compiling cleanly under -Werror, the stripped kernel installed to /boot/kernel/kernel (schg flag handled), and the guest rebooted to 6.5-DEVELOPMENT #1 (sha256 c5e894d9...). On the patched #1 kernel: the BUGGY harness still reproduces the would-be deref because it is a kernel-independent userspace model of the buggy source logic (exit 1), confirming the harness is faithful; the FIXED harness (-DFIX_NULL_CHECK, modeling fix.diff) returns early with no deref (exit 0). Because the bug is latent (no WiFi radio / optional module / default ratectl=AMRR), the in-kernel live tx-complete path cannot be triggered on the guest, so validation is at source + compile + logic-model level -- the strongest validation possible for a latent driver-only defect (same approach as sibling DF-0730). The fix closes the bug: the running kernel's rssadapt.c now guards both pointers before the deref.

baseline #0 BUGGY harness: 'NULL-DEREF: line 327 reached *(int *)arg2 with arg2==NULL.' / 'BUG PRESENT: rssadapt_tx_complete unconditionally derefs arg2.' BUGGY_EXIT=1. patched #1 FIXED harness: 'NO DEREF: NULL-arg guard returned early; line-327 load never reached.' / 'rssi callbacks reached: lower=0 raise=0 (must be 0/0).' / 'BUG FIXED: rssadapt_tx_complete no longer derefs NULL arg2/arg1.' FIXED_EXIT=0. patched kernel booted: '6.5-DEVELOPMENT #1: Thu Jul 9 01:58:20 UTC 2026'. fixed rssadapt.c source on #1 has the guard: 'if (arg1 == NULL || arg2 == NULL) return;' (verified by reading /usr/src/sys/netproto/802_11/wlan/ieee80211_rssadapt.c).
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Thu Jul 9 01:58:20 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (sha256 c5e894d9abee0118c833bd88521d077d6ce7ced51ff1df0f0470f3cce418653b)

Confirmed kernel references

Detail

Exploit chain

none. This is a NULL-pointer dereference (fixed kernel address) producing a deterministic panic -- a pure denial-of-service. There is no attacker-controlled memory write, no slab corruption, and no function-pointer/ucred target reachable, so no privilege-escalation chain can be derived. Phase 6 is explicitly N/A for read/panic-only primitives with no write capability. (No exploit.c written; only the trigger harness df0731_harness.c.)

Evidence (decisive lines)

BUGGY harness on baseline #0: '[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.' BUGGY_EXIT=1. FIXED harness: 'NO DEREF: NULL-arg guard returned early; line-327 load never reached.' 'rssi callbacks reached: lower=0 raise=0 (must be 0/0).' FIXED_EXIT=0. fix_build.log: '=== NK_DONE rc=0 ==='. Patched kernel booted: 'DragonFly 6.5-DEVELOPMENT #1: Thu Jul 9 01:58:20 UTC 2026' sha256 c5e894d9abee0118c833bd88521d077d6ce7ced51ff1df0f0470f3cce418653b.

PoC changes

Created the entire findings/poc/DF-0731/ evidence pack from scratch (folder did not exist). Authored df0731_harness.c (deterministic code-level harness reproducing rssadapt_tx_complete; instruments the line-327 deref, -DFIX_NULL_CHECK models the fix), build.sh (buggy|fixed), run.sh (both variants), fix.diff (NULL-check arg1/arg2, return early), README.md, VERDICT.md, manifest.json, env.txt.

Verified recommended fix

In rssadapt_tx_complete (sys/netproto/802_11/wlan/ieee80211_rssadapt.c:327), replace the unconditional int pktlen = *(int *)arg1, rssi = *(int *)arg2; with a NULL guard that returns early when arg1==NULL || arg2==NULL, then performs the two derefs. arg1 is guarded defensively alongside arg2. Matches the finding's proposed fix (NULL-check arg2 / return early). Full git-apply-able diff in findings/poc/DF-0731/fix.diff.

Verdict

REPRODUCED (latent). The bug is real: rssadapt_tx_complete (sys/netproto/802_11/wlan/ieee80211_rssadapt.c:322-338) at line 327 unconditionally dereferences arg2 (and arg1) with no NULL check: int pktlen = *(int *)arg1, rssi = *(int *)arg2;. It is the .ir_tx_complete slot of the rssadapt ratectl (registered ieee80211_rssadapt.c:99-111) reached on every TX completion via the dispatch inline ieee80211_ratectl.h:98-103. Every in-tree WiFi driver passes NULL (or the null-pointer constant 0) for arg2: urtwn:1041, iwm:3561, rt2661:928, rt2860:1157, rt2560:982, wpi:2127, iwn:3330-3808 all pass NULL; bwn:6012-6078 passes 0. So the first TX completion after RSSADAPT is selected page-faults on a NULL kernel pointer -> fatal trap 12 -> panic -> DoS. Confirmed deterministically by a code-level harness (df0731_harness.c) because the bug is latent on the audit guest: wlan_rssadapt is optional (sys/conf/files:1655, not in default GENERIC), default ratectl is AMRR (ieee80211_ratectl.c:121-122), and there is no WiFi radio (ifconfig -l => vtnet0 lo0). A NULL-deref at a fixed address is a pure DoS.