Remote kernel panic: hwmp_send_action KASSERT/NULL-deref when a proactive/propagated/intermediate PREP is sent along a never-validated zero nexthop
Summary
A single crafted HWMP PREQ action frame (target=ff:ff:ff:ff:ff:ff with TO bit, origseq=0, metric=0) from an auto-peered mesh neighbor makes hwmp_recv_preq's proactive branch send a PREP to rtorig->rt_nexthop of an originator route that exists but was never marked VALID - a fresh M_ZERO route whose nexthop is 00:00:00:00:00:00. hwmp_send_action resolves find_txnode(00:00:00:00:00:00) to NULL and hits KASSERT(ni != NULL) - panic on INVARIANTS kernels; ic = ni->ni_ic NULL-derefs on production. Same sink reachable via PREP propagation and intermediate PREP reply. VERIFIED on stock INVARIANTS guest with the fwm harness (beacon+OPEN+CONFIRM dance then one injected PREQ): 'panic: null node', backtrace hwmp_send_action+0x4ee <- hwmp_recv_action_meshpath <- mesh_input <- ieee80211_input_mimo, guest wedged in ddb. Any default-config mesh vap (AP|FWD by default); adjacent-network attacker with auto-granted peering kills the victim kernel with one 65-byte frame, repeatable - remote DoS of every mesh node in range. Not a write primitive - DoS ceiling. Fix validated (NULL-check + ENOENT in hwmp_send_action, rebuilt kernel): same PoC twice -> no panic, PREQ still forwarded.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3327 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.1 KB | β raw | |
| VERDICT.md | β | 3.3 KB | β raw | |
| fwm.c | β | 26.9 KB | view raw | |
| Makefile | β | 150 B | β download | |
| build.sh | β | 87 B | view raw | |
| run.sh | β | 111 B | view raw | |
| build.log | β | 5.6 KB | view raw | |
| run.log | β | 1.2 KB | view raw | |
| panic.txt | β | 762 B | view raw | |
| env.txt | β | 300 B | view raw | |
| fix.diff | β | 764 B | view raw | |
| fix_build.log | β | 364 B | view raw | |
| fix_run.log | β | 164 B | view raw | |
| verdict.json | β | 4.1 KB | view raw |
DF-3327 β Remote kernel panic in hwmp_send_action: PREP sent along a never-validated (zero) nexthop
File: sys/netproto/802_11/wlan/ieee80211_hwmp.c
Severity: High (unauthenticated adjacent-network DoS, default-config mesh vap)
Verdict: REPRODUCED β panic: null node on the stock INVARIANTS kernel; fix validated.
What
A single broadcast-target HWMP PREQ action frame makes hwmp_recv_preq()
emit a proactive PREP addressed to the nexthop of an originator route that
exists but was never marked VALID. Such a route is M_ZERO-allocated
(ieee80211_mesh.c:203-215), so its nexthop is 00:00:00:00:00:00.
hwmp_send_action() resolves that address with
ieee80211_mesh_find_txnode(), which returns NULL for an unrouted address
(ieee80211_mesh.c:1028-1037), then hits
KASSERT(ni != NULL, ("null node")) (ieee80211_hwmp.c:636) β panic on
INVARIANTS kernels; on production kernels the next line (ic = ni->ni_ic,
ieee80211_hwmp.c:637) is a NULL deref β fatal page fault. Same sink is
reachable from PREP propagation (ieee80211_hwmp.c:1558) and the
intermediate PREP reply (ieee80211_hwmp.c:1303).
Why the route stays INVALID: for a fresh originator route hr_seq == 0; a
PREQ with origseq == 0 and metric not better than 0 satisfies neither
update branch (ieee80211_hwmp.c:1090-1102) nor the discard branch
(:1103-1106), so processing falls through with the route INVALID, yet the
proactive branch (:1216-1255) still sends a PREP to
rtorig->rt_nexthop == 00:00:00:00:00:00.
Reproduce
Requires the repo-root working dir and the QEMU guest (see dfbsd-qemu/).
sh findings/poc/DF-3327/build.sh # compile extended fwm.ko in guest sh findings/poc/DF-3327/run.sh # boot mesh vap, peer, fire step 30
Expected (stock kernel): serial console shows
fwm: inject HWMP PREQ origseq=0 targ=bc TO (proactive PREP to zero nexthop)
panic: null node
hwmp_send_action() at hwmp_send_action+0x4ee
hwmp_recv_action_meshpath() at ...
Debugger("panic")
and the guest wedges in ddb (reset afterwards: dfbsd-qemu/vm.sh reset with-src).
Fix validation
fix.diff adds a NULL check in hwmp_send_action() (drop + ENOENT).
Applied in-guest, make -j6 nativekernel KERNCONF=X86_64_GENERIC +
make installkernel, reboot into kernel #1, same PoC (twice): no panic,
guest stays up, PREQ still fully processed (forwarded broadcast PREQ
visible in kern.fwm_txlog). See fix_run.log, fix_build.log.
Harness
fwm.c extends the DF-3286/3287 shared software-radio KLD. New step 30
builds the 65-byte action frame:
802.11 hdr | cat=13 (MESH) code=1 (HWMP) | PREQ IE (130) len=37: flags=0 hop=0 ttl=32 id=0x41414141 orig=02:00:00:00:e0:01 (fresh) origseq=0 lifetime=5000 metric=0 tcount=1 target: flags=0x01 (TO) addr=ff:ff:ff:ff:ff:ff seq=0
Peer establishment (step 4: beacon + OPEN + CONFIRM) is required because
ieee80211_recv_action() gates CAT_MESH frames on an ESTABLISHED link
(ieee80211_action.c:243-256); peering is auto-granted on default-config
mesh vaps (ieee80211_mesh.c:683 β MESHFLAGS_AP|MESHFLAGS_FWD).
DF-3327 VERDICT β REPRODUCED (remote kernel panic, single HWMP PREQ action frame)
Runs
| run | kernel | input | result |
|---|---|---|---|
| baseline | stock X86_64_GENERIC #0 (INVARIANTS), clean with-src snapshot |
step 4 (peering dance) + one step-30 PREQ | panic: null node, backtrace hwmp_send_action β hwmp_recv_action_meshpath β mesh_recv_mgmt β mesh_input β ieee80211_input_mimo, guest wedged in ddb (panic.txt, run.log/console) |
| fixed | #1 with fix.diff (NULL check in hwmp_send_action) |
same, step 30 fired twice | no panic, NO_PANIC_HERE / STILL_UP_AFTER_SECOND_PREQ printed, RC=0, guest up; PREQ still fully processed (forwarded broadcast PREQ in txlog, fix_run.log) |
Why it reproduces (path:line)
ieee80211_action.c:243-256β CAT_MESH action frames dispatch once the attacker link is ESTABLISHED (auto-peered via the fwm dance; no user auth on default open mesh).ieee80211_hwmp.c:417-474β the PREQ IE (len=37, tcount=1) parses cleanly;verify_mesh_preq_lenpasses (:328).ieee80211_hwmp.c:1055-1079βrt_add(orig 02:00:00:00:e0:01)β M_ZERO route (ieee80211_mesh.c:203-215): INVALID, nexthop 0, metric 0,hr_seq0.ieee80211_hwmp.c:1089-1102βorigseq=0is neither GT the stored 0 nor (equal-seq β§) better-metric β no update, route stays INVALID.:1103-1106β neither discard clause fires (no target route, originator route not VALID) β processing continues.:1216-1242β target is broadcast + TO bit β proactive branch;(rt_flags & VALID) == 0β "reply with PREP" β:1254hwmp_send_prep(vap, rtorig->rt_nexthop /* 00:00:00:00:00:00 */, &prep).ieee80211_hwmp.c:627βieee80211_mesh_find_txnode(vap, 00:β¦:00)β no route for the zero MAC β NULL (ieee80211_mesh.c:1030-1033).ieee80211_hwmp.c:636βKASSERT(ni != NULL, ("null node"))β panic (stock INVARIANTS kernel). On production kernels:637ic = ni->ni_icis a NULL-page deref β fatal fault either way.
Same sink, two more remote routes: PREP propagation
(ieee80211_hwmp.c:1521-1558, rtorig exists-but-invalid) and the
intermediate PREP reply (:1271-1303).
Impact
Remote (adjacent, unauthenticated after auto-peering) kernel panic of any default-config mesh node, one frame per panic, repeatable at will. Not a memory-corruption primitive (NULL deref), so DoS is the ceiling.
Fix validation
fix.diff (NULL check + drop + ENOENT in hwmp_send_action) β applied in
guest /usr/src, make -j6 nativekernel KERNCONF=X86_64_GENERIC (0 errors),
make installkernel, reboot, same PoC twice: panic gone, guest healthy,
PREQ still forwarded (behavior preserved). Baseline vs patched transcript
in run.log / fix_run.log.
Notes
- The panic occurred inside the
sysctl kern.fwm_step=30write (synchronous RX injection), matching theieee80211_input_mimoframes in the backtrace. - Guest reset to clean
with-srcsnapshot after both runs.
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 applied to guest /usr/src, make -j6 nativekernel (clean), make installkernel, reboot into #1: the same step-30 PREQ fired twice causes no panic (RC=0, guest stays up) and normal PREQ processing/forwarding is preserved (txlog shows the forwarded broadcast PREQ).
findings/poc/DF-3327/fix_build.log, findings/poc/DF-3327/fix_run.log
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:636
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:627
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1241
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1254
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1090
- sys/netproto/802_11/wlan/ieee80211_hwmp.c:1521
- sys/netproto/802_11/wlan/ieee80211_mesh.c:203
- sys/netproto/802_11/wlan/ieee80211_mesh.c:1028
Detail
Exploit chain
fwm software-radio injects 65-byte action frame after the beacon/OPEN/CONFIRM auto-peering dance -> ieee80211_input_mimo -> mesh_input -> hwmp_recv_action_meshpath -> verify+parse PREQ -> hwmp_recv_preq: rt_add(orig) M_ZERO route stays INVALID (origseq==stored 0, metric not better) -> proactive branch hwmp_send_prep(nexthop=00:00:00:00:00:00) -> hwmp_send_action: find_txnode returns NULL -> KASSERT 'null node' panic (guest wedged in ddb). One frame per panic, repeatable.
Evidence (decisive lines)
["panic.txt: 'panic: null node' + backtrace hwmp_send_action+0x4ee <- hwmp_recv_action_meshpath <- mesh_input <- ieee80211_input_mimo, preceded by 'fwm: inject HWMP PREQ origseq=0 targ=bc TO'", 'run.log: baseline session transcript (ssh hung at step-30 sysctl, RC=124)', 'fix_build.log: make -j6 nativekernel + installkernel of fix.diff (kernel #1)', 'fix_run.log: same PoC twice on fixed kernel - NO_PANIC_HERE / STILL_UP_AFTER_SECOND_PREQ, RC=0, guest up, forwarded PREQ still in txlog']
PoC changes
Extended the DF-3286 shared fwm.c harness with step 30 (PREQ builder), plus steps 31/32 for the sibling DF-3328 PoC and kern.fwm_ttl. The seed sketch in this finding did not exist; the frame layout was derived from ieee80211_mesh.h IE constants (PREQ=130, len=26+11, TCNT offset 27).
Verified recommended fix
NULL-check the find_txnode result in hwmp_send_action and drop the frame (return ENOENT); see fix.diff.
Verdict
A single broadcast-target HWMP PREQ action frame (origseq=0, metric=0, target=ff:ff:ff:ff:ff:ff with the TO bit) from an auto-peered mesh neighbor leaves the originator route INVALID with an all-zero nexthop, and hwmp_recv_preq's proactive branch (ieee80211_hwmp.c:1241-1254) still sends a PREP to that nexthop; hwmp_send_action resolves ieee80211_mesh_find_txnode(00:00:00:00:00:00) to NULL and hits KASSERT(ni != NULL) (:636) on INVARIANTS kernels (NULL-deref at :637 on production). Reproduced as 'panic: null node' with a hwmp_send_action backtrace on the stock INVARIANTS kernel; fix.diff (NULL check + drop) validated: no panic, guest healthy, PREQ still forwarded.
No comments yet.