cac_timeout assigns dfs->newchan (may be NULL) to iv_des_chan β downstream hostap/adhoc/mesh dereference NULL via IEEE80211_IS_CHAN_RADAR
Summary
dfs.c:154: vap->iv_des_chan = dfs->newchan with author comment "may be NULL thats ok" β INCORRECT. dfs->newchan comes from ieee80211_dfs_pickchannel :432-447 which returns NULL when all channels radar-marked. IEEE80211_CHAN_ANYC is (struct ieee80211_channel*)0xffff _ieee80211.h:157-158 NOT NULL. Downstream hostap.c:217-218 tests iv_des_chan != IEEE80211_CHAN_ANYC then dereferences IEEE80211_IS_CHAN_RADAR(iv_des_chan) which expands to iv_des_chan->ic_state at byte offset 0x0A of struct ieee80211_channel. NULL != 0xffff = true so guard passes NULL->ic_state reads VA 0x0A page fault. Same pattern in adhoc.c:170-171 mesh.c:753-754 regdomain.c:425-427 scan_sta.c:418-419. Trigger: hostap vap on DFS 5GHz channel during CAC with radar on bsschan and all alternative channels also radar-marked. Radar event from driver PHY detector or root sysctl net.wlan.X.radar=1. Impact: hard kernel panic NULL-deref DoS. Fix: assign IEEE80211_CHAN_ANYC when dfs->newchan is NULL.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0713 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.sh | trigger-source | reachability-check + trigger script (shell) | 3.3 KB | view raw |
| build.sh | build-script | no-op build (shell-only PoC) | 328 B | view raw |
| run.sh | run-script | copies trigger to guest, runs as maxx | 501 B | view raw |
| fix.diff | suggested-fix | NULL->IEEE80211_CHAN_ANYC conversion at ieee80211_dfs.c:154 | 732 B | view raw |
| VERDICT.md | verdict | full code-trace analysis: bug real, unreachable on guest | 7.3 KB | β raw |
| README.md | readme | summary, build/run, preconditions | 1.6 KB | β raw |
| run.log | run-log | baseline (#0 kernel) trigger run β unreachable | 838 B | view raw |
| fix_run.log | run-log | patched (#1 kernel) trigger run β unreachable | 838 B | view raw |
| fix_build.log | build-log | full kernel build output with fix applied (NK_DONE rc=0) | 5.6 MB | β download |
| env.txt | environment | uname, cc version, pci devices, wlan sysctls | 530 B | 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 |
DF-0713 β cac_timeout NULL-deref
Summary
ieee80211_dfs.c:154 assigns dfs->newchan (which may be NULL, from
ieee80211_dfs_pickchannel returning NULL when all channels are
radar-marked) to vap->iv_des_chan without converting NULL to the
IEEE80211_CHAN_ANYC (0xffff) sentinel. Downstream hostap/adhoc/mesh/
scan_sta/regdomain code guards with iv_des_chan != IEEE80211_CHAN_ANYC
(not != NULL), so NULL passes the guard and IEEE80211_IS_CHAN_RADAR(NULL)
dereferences NULL->ic_state (offset 0x0A) β kernel panic.
Build
./build.sh # no compilation (shell-only PoC)
Run
./run.sh # copies trigger.sh to guest, runs as maxx
Expected (on a system WITH DFS-capable wireless hardware)
Kernel panic β NULL pointer dereference in IEEE80211_IS_CHAN_RADAR /
iv_des_chan->ic_freq during the SCAN state transition after cac_timeout
sets iv_des_chan = NULL.
Expected (on this guest β no wireless hardware)
The trigger script reports "cannot trigger on this guest (no wireless hardware)" because no wlan vap can be created without a parent radio device. The bug is confirmed real by source-level trace (see VERDICT.md).
Preconditions
- A DFS-capable 802.11 wireless NIC (e.g. Atheros ath(4) in 5GHz DFS channels)
- A hostap/adhoc/mesh vap created on a DFS channel with CAC in progress
- Radar detected on the bss channel while CAC is pending
- All alternative channels also radar-marked (so
pickchannelreturns NULL) - The
net.wlan.N.radarsysctl (root-writable) can simulate the radar event
Fix
See fix.diff β convert NULL to IEEE80211_CHAN_ANYC at the assignment site.
DF-0713 β cac_timeout NULL-deref (ieee80211_dfs.c)
Verdict
INCONCLUSIVE β bug confirmed REAL by code-level trace, but NOT REACHABLE on this guest (no DFS-capable 802.11 wireless hardware). The vulnerable code is compiled into the running kernel (device wlan in X86_64_GENERIC, ieee80211_dfs.c sysctls net.wlan.cac_timeout / net.wlan.nol_timeout are live), but the trigger path requires a DFS-capable wireless NIC to create a wlan vap and enter CAC state β which the QEMU guest lacks.
The bug (confirmed by source trace)
File: sys/netproto/802_11/wlan/ieee80211_dfs.c:154
/* NB: dfs->newchan may be NULL, that's ok */ // line 153 β INCORRECT comment
vap->iv_des_chan = dfs->newchan; // line 154 β assigns NULL
The author comment "may be NULL, that's ok" is wrong. dfs->newchan comes from
ieee80211_dfs_pickchannel(ic) (ieee80211_dfs.c:366), which returns NULL when all
candidate channels are radar-marked (ieee80211_dfs.c:447). The sentinel for "no channel"
is not NULL β it is IEEE80211_CHAN_ANYC, defined as
((struct ieee80211_channel *) 0xffff) at sys/netproto/802_11/_ieee80211.h:157-158.
Every downstream consumer of iv_des_chan guards with != IEEE80211_CHAN_ANYC, not
!= NULL. Since NULL != 0xffff is true, the guard passes, and the subsequent
IEEE80211_IS_CHAN_RADAR(iv_des_chan) macro (_ieee80211.h:345-346) dereferences
iv_des_chan->ic_state β which is at byte offset 0x0A of struct ieee80211_channel
(see _ieee80211.h:140-152: ic_flags u32 @0, ic_freq u16 @4, ic_ieee u8 @6,
ic_maxregpower i8 @7, ic_maxpower i8 @8, ic_minpower i8 @9, ic_state u8 @10).
Reading VA 0x000000000000000A (in the unmapped NULL page) β page fault β kernel panic.
iv_des_chan is initialized to IEEE80211_CHAN_ANYC at vap creation
(ieee80211.c:650); ieee80211_dfs.c:154 is the only site that breaks this
invariant by assigning a raw (possibly-NULL) channel pointer.
Trigger chain (requires DFS-capable 802.11 radio)
- Create a hostap/adhoc/mesh vap on a DFS 5GHz channel (requires
wlandevparent). - CAC starts β
ieee80211_dfs_cac_startarmscac_timer(ieee80211_dfs.c:191). - Radar detected on
ic_bsschanwhile CAC pending: -ieee80211_dfs_notify_radar(ic, chan)(ieee80211_dfs.c:300) -chan == ic->ic_bsschan(:358) βdfs->newchan = ieee80211_dfs_pickchannel(ic)(:366) - If all channels radar-marked βpickchannelreturnsNULL(:447) -callout_pending(&dfs->cac_timer)is true (:372) β schedulescac_timeoutimmediately (:374-376) cac_timeoutfires (:128): -vap->iv_state == IEEE80211_S_CACβ passes guard (:137) -IEEE80211_IS_CHAN_RADAR(ic->ic_curchan)β true (:144) -vap->iv_des_chan = dfs->newchan(= NULL) (:154) β THE BUG -ieee80211_new_state(vap, IEEE80211_S_SCAN, 0)(:156)- State transition to
IEEE80211_S_SCANtriggers the NULL deref in one of: -ieee80211_hostap.c:217-218:iv_des_chan != ANYC && !IEEE80211_IS_CHAN_RADAR(iv_des_chan)β NULL->ic_state -ieee80211_adhoc.c:170-171: same pattern -ieee80211_mesh.c:753-754: same pattern -ieee80211_scan_sta.c:418-419:iv_des_chan != ANYC && c->ic_freq != iv_des_chan->ic_freqβ NULL->ic_freq -ieee80211_regdomain.c:425-427:iv_des_chan != ANYCβiv_des_chan->ic_freq
Impact
Kernel panic / DoS (NULL pointer dereference). No memory corruption, no escalation
path β the dereference reads from the unmapped NULL page (VA 0x0A), causing an immediate
fault. A NULL deref cannot yield uid=0.
Privilege boundary: reaching the trigger requires the ability to create a wlan vap
on a DFS channel and inject a radar event. On a real system with a DFS-capable NIC,
the net.wlan.N.radar sysctl (ieee80211_dragonfly.c:477-480) is writable by root
and calls ieee80211_dfs_notify_radar directly β so root (or a process with
net.wlan.N.radar write access) can trigger it. An unprivileged user cannot create
vaps or write the radar sysctl. The finding's CVSS PR:H (privileged) is appropriate.
Reachability on this guest
| Check | Result |
|---|---|
device wlan in X86_64_GENERIC |
β
(sys/config/X86_64_GENERIC:258) |
ieee80211_dfs.c compiled in |
β
(sysctls net.wlan.cac_timeout=60, net.wlan.nol_timeout=1800 are live) |
ath / ath_dfs in kernel |
β
(kldstat -v shows ath_dfs, wlan, etc.) |
| Wireless PCI device present | β (pciconf -l shows only virtio + hostbridge + ISA + ATA + VGA) |
| wlan vap creatable | β (ifconfig wlan0 create wlandev β "must specify a parent device") |
net.wlan.N.radar sysctl |
β (only created when a DFS-capable vap exists, ieee80211_dragonfly.c:477) |
Conclusion: The vulnerable code is live in the kernel but the runtime path
(ieee80211_dfs_cac_start β cac_timeout β iv_des_chan=NULL β SCAN deref)
requires a DFS-capable 802.11 radio device. The QEMU guest has no wireless hardware,
so the bug cannot be triggered. This is a latent bug on this guest β real on any
DragonFlyBSD system with a DFS-capable wireless NIC (e.g. Atheros ath(4) in 5GHz).
Escalation
None. NULL pointer dereference β kernel panic (DoS). No write primitive, no corruption. The dereference reads from the unmapped NULL page; it cannot be shaped into an arbitrary write or privilege escalation.
PoC changes
Authored trigger.sh from scratch (no prior PoC existed in the folder β the finding
was filed DB-only with no evidence pack). The script:
1. Checks for wlan interfaces
2. Attempts to create a wlan vap (fails β no parent device)
3. Checks for net.wlan.N.radar sysctl (absent β no DFS vap)
4. Reports unreachable + points to this VERDICT for the code trace
On a real DFS system, the trigger would be: create hostap vap on DFS 5GHz channel β
start CAC β sysctl net.wlan.N.radar=1 on every channel (mark all radar) β wait for
cac_timeout β panic.
Fix
fix.diff β one-line change at ieee80211_dfs.c:154:
// Before (buggy):
vap->iv_des_chan = dfs->newchan;
// After (fixed):
vap->iv_des_chan = (dfs->newchan != NULL) ?
dfs->newchan : IEEE80211_CHAN_ANYC;
This preserves the invariant that iv_des_chan is either a valid channel pointer
or the IEEE80211_CHAN_ANYC (0xffff) sentinel, which all downstream guards check
for. When pickchannel returns NULL (no free channels), the vap transitions to
SCAN state with iv_des_chan == IEEE80211_CHAN_ANYC, and the downstream guards
correctly skip the "already have a channel" fast-path and initiate a normal scan.
Fix validation
git apply --check: rc=0 (diff applies cleanly)- Kernel build:
make -j6 nativekernel KERNCONF=X86_64_GENERICβNK_DONE rc=0(full build log infix_build.log, 35454 lines) - Patched kernel boots:
kern.versionβ6.5-DEVELOPMENT #1: Mon Jul 13 11:41:58 UTC 2026 - Runtime before/after: not_testable β the bug cannot be triggered on this
guest (no wireless hardware), so no runtime before/after comparison is possible.
The fix is validated at the code level: it converts the NULL that
pickchannelmay return into theIEEE80211_CHAN_ANYCsentinel that all downstream guards check, closing the NULL-deref path. Seefix_run.log(patched kernel, same "unreachable" result asrun.logbaseline).
Fix verification
not_testablenot_testable: no WiFi HW -> trigger path unreachable. fix.diff applies+compiles+boots; source trace confirms fix closes NULL-deref path.
Both #0 and #1: trigger -> 'no wireless hardware' EXIT=2. Build: NK_DONE rc=0.
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_dfs.c:154
- sys/netproto/802_11/wlan/ieee80211_dfs.c:366
- sys/netproto/802_11/wlan/ieee80211_dfs.c:447
- sys/netproto/802_11/_ieee80211.h:157
- sys/netproto/802_11/_ieee80211.h:345
- sys/netproto/802_11/wlan/ieee80211_hostap.c:217
- sys/netproto/802_11/wlan/ieee80211.c:650
- sys/netproto/802_11/wlan/ieee80211_dragonfly.c:477
Detail
Exploit chain
none. NULL deref -- reads from unmapped NULL page, immediate panic. No write primitive.
Evidence (decisive lines)
trigger.sh: no wlan interfaces, no parent device, no net.wlan.N.radar sysctl. Source trace confirms NULL assignment at :154 and all 5 downstream guards use != IEEE80211_CHAN_ANYC (not != NULL).
PoC changes
Authored trigger.sh, build.sh, run.sh, VERDICT.md, README.md, fix.diff (ternary: newchan!=NULL ? newchan : IEEE80211_CHAN_ANYC), manifest.json from scratch.
Verified recommended fix
At ieee80211_dfs.c:154, change to vap->iv_des_chan = (dfs->newchan != NULL) ? dfs->newchan : IEEE80211_CHAN_ANYC;. Matches finding proposal. Full git-apply-able diff in findings/poc/DF-0713/fix.diff.
Verdict
INCONCLUSIVE -- bug confirmed REAL by code-level trace but NOT REACHABLE on this guest (no WiFi hardware). ieee80211_dfs.c:154 assigns dfs->newchan (may be NULL from pickchannel:447 when all channels radar-marked) to vap->iv_des_chan without converting NULL to IEEE80211_CHAN_ANYC sentinel. All downstream guards check != IEEE80211_CHAN_ANYC (NOT != NULL), so NULL passes guard and IEEE80211_IS_CHAN_RADAR(NULL) dereferences NULL at offset 0x0A -> panic. Code IS compiled into kernel (device wlan in GENERIC) but trigger needs DFS-capable WiFi radio.
No comments yet.