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

rssadapt_sysctl_interval dereferences vap->iv_rs freed by rssadapt_deinit during the vap-teardown window before ieee80211_sysctl_vdetach removes the oid β€” unprivileged 4-byte kernel-heap UAF read (world-readable sysctl), root 4-byte UAF write; amrr twin is the default ratectl

Summary

rssadapt_deinit frees vap->iv_rs without clearing it; it is called from every wifi driver's vap_delete BEFORE ieee80211_vap_detach, whose LAST step removes the world-readable CTLFLAG_RW oid net.wlan.N.rssadapt_rate_interval. Across that teardown window the sysctl handler dereferences the dangling pointer: unpriv 'sysctl net.wlan.N...' racing 'ifconfig wlanN destroy' reads 4 bytes of a freed, size-class-reused chunk (heap info-leak gadget); the root write path stores 4 controlled bytes into the freed chunk. The IDENTICAL pattern in the default algorithm amrr (amrr_deinit; SYSCTL_ADD_UINT pointing directly into the chunk) is reachable on any real wifi system. Proven via code-level harness: freed chunk handed straight back to a replacement object (same address); unpriv read returns replacement-object bytes; root write lands in the replacement ('RESULT: BUG REPRODUCED'); fixed variant (clear iv_rs + ENXIO guard) clean. Latent for rssadapt exactly as DF-0730/0731. Fix: clear the pointer at deinit + guard the handler (mirror both in ieee80211_amrr.c).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3460 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 4.6 KB ↓ raw
df3460_harness.c β€” 6.4 KB view raw
build.sh β€” 93 B view raw
run.sh β€” 89 B view raw
run.log β€” 750 B view raw
run.fixed.log β€” 410 B view raw
fix.diff β€” 943 B view raw
fix_module_build.log β€” 966 B view raw
env.txt β€” 377 B view raw
verdict.json β€” 4.9 KB view raw

DF-3460 β€” rssadapt sysctl interval handler UAF across vap teardown

File: sys/netproto/802_11/wlan/ieee80211_rssadapt.c Verdict: REPRODUCED (code-level harness; module latent on this guest β€” see DF-0730 reachability notes). Severity filed: Low (latent) β€” the amrr twin of this pattern (ieee80211_amrr.c:390-403, plus SYSCTL_ADD_UINT pointing directly into the freed chunk) is the DEFAULT ratectl and would be reachable on any real WiFi system.

What the bug is

Lifetime chain on this tree:

  1. vap create β†’ ieee80211_ratectl_init() (ieee80211_ratectl.c:119-135) β†’ rssadapt_init() kmallocs vap->iv_rs AND registers the world-readable sysctl net.wlan.N.rssadapt_rate_interval (rssadapt_sysctlattach, :356-362).
  2. vap destroy β†’ every in-tree wifi driver's *_vap_delete() (e.g. if_iwm.c:6486-6493) calls ieee80211_ratectl_deinit(vap) β†’ rssadapt_deinit() (:151-155) β†’ IEEE80211_FREE(vap->iv_rs) β€” iv_rs is not cleared.
  3. Only much later, ieee80211_vap_detach() β†’ … β†’ ieee80211_sysctl_vdetach(vap) (ieee80211.c:827 β€” the last vdetach, after ether_ifdetach, ieee80211_stop, task drains, node detach) removes the oid.

For the entire window between (2) and (3) the oid stays registered and world-readable, and rssadapt_sysctl_interval() (:340-353) does

struct ieee80211_rssadapt *rs = vap->iv_rs;   /* dangling */
int msecs = ticks_to_msecs(rs->interval);     /* UAF READ  */
... sysctl_handle_int(...)                    /* 4 bytes -> any user */
rssadapt_setinterval(vap, msecs);             /* UAF WRITE (root) */
  • Unprivileged read: sysctl net.wlan.N.rssadapt_rate_interval during ifconfig wlanN destroy returns 4 bytes read out of a freed (and typically already-reused β€” kmalloc size-class reuse is cross-type in DFly) heap chunk β†’ kernel-heap info-leak gadget.
  • Root write: sysctl -w in the same window writes 4 controlled bytes into the freed/reused chunk.

Reproduce

./build.sh          # buggy (as shipped)
./run.sh            # β†’ RESULT: BUG REPRODUCED …
./build.sh fixed    # deinit NULLs iv_rs + handler guards
./run.sh            # β†’ RESULT: FIXED β€” no UAF read/write

(Built -O0: at -O2 gcc CSEs the post-free load with the pre-free one β€” UB elision the kernel does not perform across the real sysctl dispatch.)

Fix validation

  • fix.diff β€” git apply-clean; patched wlan_rssadapt module builds clean with full kernel -Werror CFLAGS (fix_module_build.log, rc=0).
  • Harness fixed mode: handler refuses (ENXIO) during the window; no freed-chunk read or write.
VERDICT.md
↓ download raw

DF-3460 β€” VERDICT

Status: REPRODUCED (code-level harness, latent module on this guest) Impact: leak (unprivileged 4-byte kernel-heap UAF read via world-readable sysctl during vap teardown; root 4-byte UAF write in the same window) Confidence: certain (teardown order proven from source; harness run on the audit guest)

The window is real β€” source trace

iwm_vap_delete()                       if_iwm.c:6486-6493
 └─ ieee80211_ratectl_deinit(vap)      ieee80211_ratectl.h:69-72
     └─ rssadapt_deinit(vap)           ieee80211_rssadapt.c:151-155
         └─ IEEE80211_FREE(vap->iv_rs) … iv_rs NOT cleared
 └─ ieee80211_vap_detach(vap)          ieee80211.c:752…
     β”œβ”€ ether_ifdetach / ieee80211_stop / draintask / …
     β”œβ”€ ieee80211_node_vdetach         ieee80211.c:826
     └─ ieee80211_sysctl_vdetach(vap)  ieee80211.c:827   <- oid removed only HERE

Everything between the IEEE80211_FREE and ieee80211_sysctl_vdetach β€” interface detach, state stop, task drains, node table teardown β€” is the window in which the oid net.wlan.N.rssadapt_rate_interval remains registered (world-readable, CTLFLAG_RW) while its handler (rssadapt_sysctl_interval, ieee80211_rssadapt.c:340-353) dereferences the dangling vap->iv_rs.

Harness demonstration

df3460_harness.c replicates the lifetime with the exact handler code. Buggy run (run.log, guest DragonFly 6.5-DEVELOPMENT #0):

attach          : iv_rs=0x8004902c0 interval=500
read (alive)    : reader gets 500  (ok)
deinit          : iv_rs freed, pointer still 0x8004902c0 (dangling)
window          : chunk reused by poison obj at 0x8004902c0
read (window)   : unprivileged reader gets 0xdeadbeef from FREED chunk
  -> UAF READ confirmed: reader saw the replacement object's bytes (4-byte kernel-heap info leak)
write (window)  : poison.marker2 (offset 8, = interval) now 0x0000012c (was 0xDEADBEEF)
  -> UAF WRITE confirmed: root sysctl write landed in the replacement object
RESULT: BUG REPRODUCED β€” sysctl handler dereferences freed iv_rs across vap teardown

The allocator handed the freed 16-byte chunk straight back to the replacement object (same address), exactly what DFly's kmalloc size-class zones do cross-type β€” so the unprivileged reader observes bytes of an unrelated live kernel object, and the root writer corrupts one.

Fixed run (run.fixed.log): deinit clears iv_rs; the handler returns ENXIO during the window; no freed-chunk read or write.

Why Low (and the amrr twin that matters more)

Same latency as DF-0730/DF-0731: rssadapt is not selectable in-tree and the guest has no radio. HOWEVER the identical pattern exists in the default algorithm:

  • amrr_sysctl_interval() (ieee80211_amrr.c:390-403) derefs vap->iv_rs freed by amrr_deinit (ieee80211_amrr.c:135) under the same driver teardown order, and
  • amrr_sysctlattach() (ieee80211_amrr.c:405-417) registers amrr_max_sucess_threshold / amrr_min_sucess_threshold as SYSCTL_ADD_UINT pointing directly into the chunk β€” unprivileged reads of those oids in the window UAF-read the freed chunk, root writes UAF-write it.

The amrr twin belongs to ieee80211_amrr.c's own audit (DF-3454 there covers a different defect); it is called out here so it is not lost. On any real WiFi-equipped DragonFly system the amrr version of this window is reachable today.

Fix validation

  • fix.diff: rssadapt_deinit frees only if non-NULL and clears vap->iv_rs; rssadapt_sysctl_interval returns ENXIO when iv_rs == NULL.
  • git apply-clean against sys/; patched wlan_rssadapt module rebuilds clean under full kernel -Werror CFLAGS (fix_module_build.log, rc=0).
  • Harness fixed mode: handler refuses during the window.
  • Guest source restored to pristine (sha256 verified) β€” same one-line fix should be applied to ieee80211_amrr.c by its owner.

Kernel references

sys/netproto/802_11/wlan/ieee80211_rssadapt.c:151-155 (deinit: free without clearing) sys/netproto/802_11/wlan/ieee80211_rssadapt.c:340-353 (handler derefs vap->iv_rs) sys/netproto/802_11/wlan/ieee80211_rssadapt.c:356-362 (oid registration, CTLFLAG_RW) sys/dev/netif/iwm/if_iwm.c:6486-6493 (deinit before vap_detach) sys/netproto/802_11/wlan/ieee80211.c:752,826-827 (sysctl ctx torn down last) sys/netproto/802_11/wlan/ieee80211_amrr.c:135,390-417 (reachable amrr twin)

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applies cleanly (git apply --check on the guest's /usr/src); the patched wlan_rssadapt module rebuilds with full kernel -Werror CFLAGS (rc=0, fix_module_build.log); harness fixed-mode shows the handler refusing (ENXIO) during the teardown window with no freed-chunk read or write (run.fixed.log).

['findings/poc/DF-3460/fix.diff', 'findings/poc/DF-3460/fix_module_build.log', 'findings/poc/DF-3460/run.fixed.log']
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

['findings/poc/DF-3460/run.log β€” buggy run: freed chunk reused at the same address; unprivileged read returns replacement-object bytes (0xdeadbeef); root write lands 0x12c into the replacement object', 'findings/poc/DF-3460/run.fixed.log β€” deinit clears iv_rs; handler returns ENXIO during the window; no UAF', 'findings/poc/DF-3460/fix_module_build.log β€” patched wlan_rssadapt.ko builds clean, full kernel -Werror CFLAGS, rc=0', 'findings/poc/DF-3460/df3460_harness.c β€” exact handler/lifetime replica (built -O0: gcc -O2 CSEs the post-free load away β€” UB elision the kernel does not perform)']

PoC changes

Harness is self-authored (no seed). Iterations: (1) nested /* inside the header comment broke the build; (2) poison initially written at the wrong offset (interval is at struct offset 8) β€” now the whole replacement object is poisoned and marker2 (offset 8) checked; (3) build switched to -O0 because gcc -O2 CSEs the post-free load with the pre-free one (UB elision), hiding the reuse β€” the kernel performs no such elision across the real sysctl dispatch.

Verified recommended fix

rssadapt_deinit: free only if non-NULL and clear vap->iv_rs; rssadapt_sysctl_interval: return ENXIO when iv_rs == NULL β€” and mirror both one-liners in ieee80211_amrr.c β€” see findings/poc/DF-3460/fix.diff.

Verdict

REPRODUCED (code-level harness; module latent on this guest). rssadapt_deinit (ieee80211_rssadapt.c:151-155) frees vap->iv_rs without clearing it, and is invoked from every wifi driver's vap_delete (e.g. if_iwm.c:6486-6493 ieee80211_ratectl_deinit -> ieee80211_vap_detach) while the sysctl oid net.wlan.N.rssadapt_rate_interval (CTLFLAG_RW, world-readable; registered at :356-362) is only removed by ieee80211_sysctl_vdetach at ieee80211.c:827 β€” the LAST step of vap_detach. Across that whole teardown window the handler (rssadapt_sysctl_interval :340-353) dereferences the dangling iv_rs: an unprivileged sysctl net.wlan.N.rssadapt_rate_interval racing ifconfig wlanN destroy reads 4 bytes of a freed (kmalloc size-class reused) chunk β€” a kernel-heap info-leak gadget; the root write path stores 4 controlled bytes into the freed chunk via rssadapt_setinterval. Harness demonstrates both with the chunk handed straight back to a replacement object (same address). Latent for rssadapt (optional module, no selector, no radio) β€” but the IDENTICAL pattern in the DEFAULT algorithm amrr (ieee80211_amrr.c:135 amrr_deinit; :390-403 amrr_sysctl_interval; :405-417 SYSCTL_ADD_UINT pointing directly into the chunk) is reachable on any real WiFi system and should be fixed in the same stroke.