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)
PoC verification
Evidence pack
findings/poc/DF-3460 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
- vap create β
ieee80211_ratectl_init()(ieee80211_ratectl.c:119-135) βrssadapt_init()kmallocsvap->iv_rsAND registers the world-readable sysctlnet.wlan.N.rssadapt_rate_interval(rssadapt_sysctlattach, :356-362). - vap destroy β every in-tree wifi driver's
*_vap_delete()(e.g. if_iwm.c:6486-6493) callsieee80211_ratectl_deinit(vap)βrssadapt_deinit()(:151-155) βIEEE80211_FREE(vap->iv_rs)βiv_rsis not cleared. - 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_intervalduringifconfig wlanN destroyreturns 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 -win 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; patchedwlan_rssadaptmodule builds clean with full kernel-WerrorCFLAGS (fix_module_build.log, rc=0).- Harness
fixedmode: handler refuses (ENXIO) during the window; no freed-chunk read or write.
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) derefsvap->iv_rsfreed byamrr_deinit(ieee80211_amrr.c:135) under the same driver teardown order, andamrr_sysctlattach()(ieee80211_amrr.c:405-417) registersamrr_max_sucess_threshold/amrr_min_sucess_thresholdasSYSCTL_ADD_UINTpointing 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_deinitfrees only if non-NULL and clearsvap->iv_rs;rssadapt_sysctl_intervalreturns ENXIO wheniv_rs == NULL.git apply-clean against sys/; patchedwlan_rssadaptmodule rebuilds clean under full kernel-WerrorCFLAGS (fix_module_build.log, rc=0).- Harness
fixedmode: 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
fixedfix.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']
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:151
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:340
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:344
- sys/netproto/802_11/wlan/ieee80211_rssadapt.c:356
- sys/dev/netif/iwm/if_iwm.c:6486
- sys/netproto/802_11/wlan/ieee80211.c:826
- sys/netproto/802_11/wlan/ieee80211.c:827
- sys/netproto/802_11/wlan/ieee80211_amrr.c:135
- sys/netproto/802_11/wlan/ieee80211_amrr.c:390
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.
No comments yet.