Remote kernel panic via AE=01 unicast mesh data frame: route for addr4 never ensured, KASSERT/NULL-deref in to_fwrd/to_me
Summary
mesh_checkpseq only creates a routing entry for the AE-specific source address (mc_addr4 when AE=01), but mesh_recv_indiv_data_to_fwrd and _to_me look up qwh->i_addr4 and KASSERT-then-deref the route. A 4-address unicast AE=01 frame with a never-routed addr4 gives rt==NULL: 'panic: no route' on INVARIANTS kernels, and MESH_RT_ENTRY_LOCK(NULL) -> near-NULL lockmgr write -> fatal fault on production kernels. Variant: to_me's KASSERT(ae==AE_00||AE_10) also panics INVARIANTS for AE=01/AE=11 unicast-to-me frames. VERIFIED on stock #0: after the peering dance, ONE 54-byte unicast frame -> 'panic: no route', trace inject -> ieee80211_input_mimo -> mesh_input+0x127b, guest wedged in ddb. Remote single-frame kernel panic of any mesh node from an established peer (auto-peered from one beacon); repeatable at will. Fix validated (NULL-check and discard): same frame silently discarded, link stays up.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3287 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 2.5 KB | β raw | |
| run.sh | β | 111 B | view raw | |
| panic.txt | β | 619 B | view raw | |
| verdict.json | β | 2.6 KB | view raw | |
| manifest.json | β | 482 B | view raw |
DF-3287 β Remote kernel panic via unvalidated AE=01 unicast mesh data frame (route for addr4 never ensured; KASSERT/NULL-deref)
File: sys/netproto/802_11/wlan/ieee80211_mesh.c (pass 2 audit)
What
mesh_input's duplicate-sequence check (:1756-1768) only creates a
routing-table entry for the AE-specific source address:
if (IEEE80211_IS_MULTICAST(wh->i_addr1)) addr = wh->i_addr3;
else if (ae == IEEE80211_MESH_AE_01) addr = MC01(mc)->mc_addr4; /* proxy SA in mesh control */
else addr = qwh->i_addr4;
if (mesh_checkpseq(vap, addr, seq) != 0) ... /* adds route for `addr` only */
But the unicast forward/local paths then look up and unconditionally dereference a route for the 802.11 header's addr4:
/* mesh_recv_indiv_data_to_fwrd :1441 */
rt_meshsa = ieee80211_mesh_rt_find(vap, qwh->i_addr4);
KASSERT(rt_meshsa != NULL, ("no route"));
ieee80211_mesh_rt_update(rt_meshsa, ...); /* MESH_RT_ENTRY_LOCK(rt) */
/* mesh_recv_indiv_data_to_me :1482 */
rt = ieee80211_mesh_rt_find(vap, qwh->i_addr4);
KASSERT(rt != NULL, ("no route"));
ieee80211_mesh_rt_update(rt, ...);
An AE=01 (proxy source in mesh control) 4-address unicast data frame
whose i_addr4 is any never-routed MAC reaches these lines with
rt == NULL:
- INVARIANTS kernels:
panic: no route(proven on the stock guest). - production kernels:
MESH_RT_ENTRY_LOCK(NULL)β lockmgr write at ~NULL β fatal page fault.
A second variant: to_me KASSERTs ae == AE_00 || ae == AE_10 (:1488)
β an AE=11 or AE=01 frame addressed to the victim panics INVARIANTS
kernels even before the route deref.
Reproduced (stock kernel)
fwm: inject UCAST AE=1 data (54 bytes; addr4 unroute'd)
panic: no route
cpuid = 1
Trace beginning at frame 0xfffff8011836b4d8
mesh_input() at mesh_input+0x127b
ieee80211_input_mimo() at ieee80211_input_mimo+0x156
inject() at inject+0x75
fwm_step_sysctl() at fwm_step_sysctl+0x1d9
Debugger("panic")
(see panic.txt; single 54-byte frame from an established mesh peer β
the peer link is auto-established by the victim from one beacon.)
Build / run (repo root)
sh findings/poc/DF-3286/build.sh # shared harness sh findings/poc/DF-3287/run.sh # panics the stock guest
Threat
Remote unauthenticated-after-peering kernel panic (single frame).
802.11s mesh STAs auto-peer by default (ms_flags = AP|FWD, :683), so
"established peer" costs the attacker one beacon + two action frames.
CWE-690 / CWE-20 (missing validation β NULL deref / DoS).
DF-3287 VERDICT β REPRODUCED (panic on stock INVARIANTS kernel; single remote frame)
Reproduction
Stock kernel X86_64_GENERIC #0 (INVARIANTS), clean-snapshot guest. Mesh vap up (meshid pwn, HWMP/AIRTIME, default AP|FWD flags). Harness (fwm.ko, shared with DF-3286) performs the synchronous peering dance (beacon β OPEN β CONFIRM; victim reaches ni_mlstate=ESTABLISHED β fwm_tx log shows victim OPEN, victim CONFIRM, then the HWMP proactive PREQ that mesh_linkchange(ESTABLISHED) triggers, proving the link).
Then ONE 54-byte unicast DSTODS QoS data frame, AE=01 mesh control:
- addr3 = 00:66:66:66:66:66 (== mc_addr4 β mesh_checkpseq created a
route for it, so
rt_meshdalookup succeeds), - addr4 = 00:77:88:99:aa:bb (never routed β
rt_meshsa== NULL).
Result (serial console, vm.sh log β full text in panic.txt):
panic: no route mesh_input() at mesh_input+0x127b <- inlined to_fwrd KASSERT (:1442) ieee80211_input_mimo() at ieee80211_input_mimo+0x156 inject() at inject+0x75 <- harness (== driver RX path) fwm_step_sysctl() ...
Guest wedged in ddb; required vm.sh reset.
Root cause chain
mesh_input:1758-1768 ensures a route only for the AE-specific SA (mc_addr4 for AE=01); to_fwrd/to_me then KASSERT-and-deref a route for qwh->i_addr4 (1441-1444, 1482-1484) which for AE=01 4-address frames is attacker-chosen and routinely unrouted. INVARIANTS β KASSERT panic; production β ieee80211_mesh_rt_update(NULL) β MESH_RT_ENTRY_LOCK(NULL) β lockmgr write near NULL β fatal fault. Same class also reachable via to_me's ae KASSERT (1488) with AE=01/AE=11 unicast-to-me frames.
Impact
Remote kernel panic (DoS) from an established mesh peer; peering is auto-granted by default. Repeated at will (one frame each time).
Fix validation
fix.diff hunks #2/#3 replace both KASSERT+deref with NULL-checked discards. Fixed-kernel rerun: step 11 frame is silently discarded (error == -1 path), guest stays up, link remains established (subsequent valid frames still process). See DF-3286 run.log fix section.
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
fixedsame single AE=1 frame that panicked the stock kernel (panic: no route) is silently discarded on the fixed kernel (NO_PANIC_3287); link stays established
['fix.log: NO_PANIC_3287']
Confirmed kernel references
Detail
Exploit chain
attacker beacon -> victim auto-peers (OPENSNT) -> synchronous OPEN+CONFIRM -> ESTABLISHED -> one crafted AE=01 4-address unicast data frame (addr4 = never-routed MAC, addr3 = the mc_addr4 the seq-check routed) -> rt_find(i_addr4) == NULL -> KASSERT panic (INVARIANTS) / near-NULL lockmgr write (production) -> remote DoS
Evidence (decisive lines)
panic.txt: full console trace (panic: no route, mesh_input+0x127b) README.md root-cause cites mesh.c:1758-1768, 1441-1444, 1482-1488
PoC changes
shared harness fwm.c (DF-3286 pack); step 11 crafts the AE=01 frame with addr3==mc_addr4 so the rt_meshda lookup passes and the NULL rt_meshsa deref is reached
Verified recommended fix
NULL-check the addr4 route in to_fwrd/to_me and discard the frame instead of KASSERT+deref (fix.diff hunks 2-3)
Verdict
Single 54-byte AE=01 unicast DSTODS QoS mesh data frame from an established peer panics the stock INVARIANTS kernel with 'panic: no route' at the inlined mesh_recv_indiv_data_to_fwrd KASSERT (mesh_input+0x127b); on non-INVARIANTS builds the same path calls ieee80211_mesh_rt_update(NULL) whose MESH_RT_ENTRY_LOCK writes near NULL. Root cause: mesh_checkpseq only ensures a route for the AE-specific SA while to_fwrd/to_me deref a route for i_addr4 unconditionally.
No comments yet.