_db_show_mesh invokes ieee80211_mesh_rt_update() from DDB: takes lockmgr lock and mutates mesh route state inside a read-only debugger pretty-printer
| Field | Value |
|---|---|
| ID | DF-0606 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:L/A:H |
| CWE | CWE-820 Mismatched Access to a Resource; CWE-667 Improper Locking |
| File | sys/netproto/802_11/wlan/ieee80211_ddb.c |
| Lines | 897-911 |
| Area | netproto/802_11 (DDB pretty-printer for mesh) |
| Confidence | certain |
| Discovered | 2026-07-02 |
| Reported | pending |
Summary
The DDB mesh-route display routine _db_show_mesh() calls the live kernel
helper ieee80211_mesh_rt_update(rt, 0) purely to obtain the route's
lifetime for printing (line 908). That helper acquires the per-route
lockmgr lock MESH_RT_ENTRY_LOCK (LK_EXCLUSIVE) and mutates the
route entry (rt_updtime, rt_flags &= ~VALID, rt_lifetime = 0). A
debugger display function must be non-locking and read-only; this one both
blocks on a lockmgr lock and silently corrupts the exact data structure it
is displaying, which can deadlock or double-panic the kernel debugger
(destroying the panic forensic state) and invalidates routes in the mesh
table.
Root cause
sys/netproto/802_11/wlan/ieee80211_ddb.c:908 β inside the
TAILQ_FOREACH(rt, &ms->ms_routes, rt_next) loop of _db_show_mesh
(ieee80211_ddb.c:897), the code does:
907: db_printf("\tlifetime: %u lastseq: %u priv: %p\n",
908: ieee80211_mesh_rt_update(rt, 0), /* line 908 */
909: rt->rt_lastmseq, rt->rt_priv);
ieee80211_mesh_rt_update() (wlan/ieee80211_mesh.c:266-303) is not a
query accessor; it is a state-mutating update routine:
- line 275: MESH_RT_ENTRY_LOCK(rt); β lockmgr(&(rt)->rt_lock,
LK_EXCLUSIVE) (ieee80211_dragonfly.h:607)
- line 274: now = ticks; β reads a global, time-dependent value
- line 284: rt->rt_updtime = now; β writes the route entry
- lines 285-298: if timesince >= rt_lifetime (which, with
new_lifetime==0, is the common case for any aged route), it
clears IEEE80211_MESHRT_FLAGS_VALID from rt->rt_flags and zeros
rt->rt_lifetime β writes
- line 300: MESH_RT_ENTRY_UNLOCK(rt);
Two independent defects stem from this single call site:
(a) Locking from DDB. lockmgr acquisition from the kernel debugger
(panic context, other CPUs stopped via stop_cpus, scheduler halted) is
undefined behavior. If the thread that panicked already held rt->rt_lock
(the mesh path that processes PREQ/PREP/routes regularly holds it β see
ieee80211_mesh.c:1145,1437,1443,1484,2011,3556 calling
ieee80211_mesh_rt_update in normal tx/rx), the ddb call recurses into
lockmgr on a lock owned by the stopped current-thread context; if it was
held by a different (now-stopped) CPU, lockmgr attempts to block, but the
scheduler is frozen in ddb, producing a deadlock or a
"locking against myself"/recursive-lock panic that re-enters the trap
path and converts a recoverable panic into an unrecoverable
double-fault/reboot.
(b) State mutation. Even if the lock succeeds, the helper writes
rt_updtime, rt_flags (clearing VALID), and rt_lifetime on every
route it touches. So running show mesh <addr> (or show vap <mesh_vap>
m, or show com <addr> a which both recurse through
_db_show_vap:499 β _db_show_mesh) silently corrupts the mesh routing
table: aged routes are invalidated, timestamps are bumped, and the forensic
snapshot the operator is trying to capture is destroyed by the act of
capturing it.
Threat model & preconditions
- Attacker position: privileged β either console / kdb access, or
sysctl debug.kdb.enter=1(root), or a prerequisite memory-corruption primitive that triggers a wlan panic withdebugger_on_panic=1. - Privileges gained or impact:
- (a) A:H β the ddb session hangs (
lockmgrdeadlock in the frozen scheduler) or double-faults to an immediate reboot (Fatal double fault/re-entering trap from inside ddb), destroying the kdb session plus all in-panic forensic context (registers, stack, slab state). - (b) I:L β silently mutates
rt_updtime/rt_flags/rt_lifetimeacross the whole mesh routing table, so even a "successful" dump leaves the kernel's routing state altered when the operator later continues. - Required config or capabilities:
IEEE80211_SUPPORT_MESHcompiled in (part of the standardwlan_meshmodule) and an MBSS vap with peers (ms_routesnon-empty). Requires DDB in the kernel config (standard on DragonFly). - Reachability:
show vap <mesh-vap-addr> morshow com <mesh-ic-addr> aat thedb>prompt, where<mesh-vap-addr>is anieee80211vapwhoseiv_opmode == IEEE80211_M_MBSSandiv_mesh != NULL. The loop body executes once per entry inms->ms_routes, i.e. once per known mesh peer/proxy β any active MBSS vap with at least one route triggers it.
Proof of concept
PoC: findings/poc/DF-0606/repro.sh (shell). On DragonFlyBSD with a
mesh-capable wlan adapter (or a VM passed one):
# (1) Load wlan + wlan_mesh, create a mesh vap, bring it up with at
# least one peer/route:
kldload wlan wlan_mesh
ifconfig wlan0 create wlandev wifi0 wlanmode mesh
ifconfig wlan0 up
# (join an existing MBSS peer so ms_routes is non-empty)
# (2) Trigger DDB entry. Simplest: as root,
sysctl debug.kdb.enter=1
# OR provoke any wlan panic while mesh is up with debugger_on_panic=1.
# (3) At the db> prompt:
db> show vap <mesh-vap-addr> m
# or
db> show com <mesh-ic-addr> a
Expected outcome
One of:
- The ddb session hangs (lockmgr deadlock).
- Double-fault to immediate reboot (Fatal double fault/re-entering trap
from inside ddb).
- If it returns: running the command twice shows rt_lifetime collapsing
to 0 and rt_flags losing VALID on previously-good routes, proving
the display mutated the table.
Impact
- Blast radius: any DragonFly system running an MBSS mesh vap that enters DDB (operator-triggered or panic-triggered) where someone tries to triage the mesh state.
- Severity rationale: Medium. Privileged (root/console) trigger, deterministic, high availability impact (forced reboot / destroyed forensic state) plus integrity loss on mesh routing. Non-default wifi config (MBSS), hence Medium rather than High. CVSS 3.1 base β 6.2.
- Reliability: 100% once
_db_show_meshis invoked on a mesh vap with at least one route.
Recommended fix
DDB display functions must be lock-free and non-mutating. Read the stored
rt->rt_lifetime field directly instead of invoking the update helper.
rt_lifetime (uint32_t, ieee80211_mesh.h:436) holds the last-computed
lifetime and is the correct field for a read-only display; it does not
require the lock for a best-effort ddb snapshot (the same way every other
field in this routine β rt_dest, rt_nexthop, rt_metric, rt_lastmseq,
rt_priv β is already read without the lock).
--- a/sys/netproto/802_11/wlan/ieee80211_ddb.c
+++ b/sys/netproto/802_11/wlan/ieee80211_ddb.c
@@ -905,7 +905,7 @@
#endif
db_printf("\tlifetime: %u lastseq: %u priv: %p\n",
- ieee80211_mesh_rt_update(rt, 0),
+ rt->rt_lifetime,
rt->rt_lastmseq, rt->rt_priv);
i++;
}
Defense-in-depth (optional, separate): also clamp keylen in
_db_show_key (ieee80211_ddb.c:776:
if (keylen > IEEE80211_KEYBUF_SIZE) keylen = IEEE80211_KEYBUF_SIZE;) and
add the missing if (nt->nt_keyixmap != NULL) guard in
_db_show_node_table (ieee80211_ddb.c:706) to match the production callers
at ieee80211_node.c:1679/1807/1883 β but the mesh lock/mutation is the
actionable security/correctness defect.
References
sys/netproto/802_11/wlan/ieee80211_mesh.c:266-303βieee80211_mesh_rt_updateis the mutating helper being misused.sys/netproto/802_11/wlan/ieee80211_dragonfly.h:607βMESH_RT_ENTRY_LOCKβlockmgr(LK_EXCLUSIVE).sys/netproto/802_11/ieee80211_mesh.h:436βrt_lifetimeis the correct read-only field.
Timeline
- 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
- 2026-07-02 Reported to DragonFlyBSD security contact (pending).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0606 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | original PoC README + verifier notes | 1.8 KB | β raw |
| VERDICT.md | verdict | full narrative: bug, disassembly proof, fix validation | 8.1 KB | β raw |
| repro.sh | trigger-source | theoretical live-DDB procedure (requires WiFi hardware) | 2.0 KB | view raw |
| verify.sh | trigger-source | disassembly-level verifier (baseline/patched) via objdump+addr2line | 4.4 KB | view raw |
| build.sh | build-log | build.sh (no-op; trigger is a debugger command, not a binary) | 543 B | view raw |
| run.sh | build-log | run.sh: dispatches to /root/verify.sh on the guest | 551 B | view raw |
| fix.diff | suggested-fix | git-apply-able: read rt->rt_lifetime directly instead of calling mutating helper | 389 B | view raw |
| baseline_verify.log | run-log | verify.sh baseline on #0 kernel: 1 call inside _db_show_mesh (BUG PRESENT) | 2.6 KB | view raw |
| patched_verify.log | run-log | verify.sh patched on #1 kernel: 0 calls inside _db_show_mesh (BUG GONE) | 2.4 KB | view raw |
| patched_disasm.txt | panic-signature | disassembly of patched _db_show_mesh loop: mov 0x70(%r12),%esi (rt_lifetime) instead of callq ieee80211_mesh_rt_update | 1.1 KB | view raw |
| fix_build.log | build-log | full untrimmed nativekernel + installkernel output (rc=0) | 5.7 MB | β download |
| env.txt | environment | baseline #0 uname, cc, sysctls, GENERIC DDB+SUPPORT_MESH | 294 B | view raw |
| patched_env.txt | environment | patched #1 uname + kernel sha256 | 350 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-0606 β PoC: _db_show_mesh invokes state-mutating helper from DDB
Privileged local DDB-safety defect. _db_show_mesh() calls
ieee80211_mesh_rt_update(rt, 0) which acquires a lockmgr lock and
mutates the route entry β both unsafe from DDB (panic context with frozen
scheduler). Result: deadlock, double-panic / forced reboot, or silent
corruption of the mesh routing table.
Files
repro.shβ shell driver (load wlan_mesh, create mesh vap, trigger DDB, runshow vap <addr> m).- (added by per-PoC verifier) full C harness / ddb script,
build.sh,run.sh,run.log,VERDICT.md,manifest.json,fix.diff.
Build & run
kldload wlan wlan_mesh
ifconfig wlan0 create wlandev wifi0 wlanmode mesh
ifconfig wlan0 up
# (join an existing MBSS peer so ms_routes is non-empty)
sysctl debug.kdb.enter=1
# at the db> prompt:
db> show vap <mesh-vap-addr> m
Expected outcome
One of:
- The ddb session hangs (lockmgr deadlock in the frozen scheduler).
- Double-fault to immediate reboot (Fatal double fault / re-entering trap
from inside ddb).
- If it returns: running the command twice shows rt_lifetime collapsing
to 0 and rt_flags losing VALID on previously-good routes, proving
the display mutated the table.
Notes for the per-PoC verifier
- Requires
wlan_meshmodule loaded and an MBSS vap with at least one route inms_routes. Verify withifconfig wlan0 list meshbefore triggering DDB. - The
<mesh-vap-addr>can be found by walking the ifnet list in ddb (show all/ifps) or by inspecting the ic's vap TAILQ. - Verify the fix with
git apply findings/poc/DF-0606/fix.diff(readrt->rt_lifetimedirectly instead of callingieee80211_mesh_rt_update); after the fix,show vap <addr> mshould return cleanly without mutating the table or deadlocking.
DF-0606 β Verdict
Verdict
REPRODUCED (disassembly-confirmed) β FIX VALIDATED.
This is a DDB-context privileged debugger defect, not a memory-corruption
primitive reachable from userspace. There is no uid=0 escalation chain β
the trigger requires either console/kdb access or a prerequisite panic that
drops to db> with debug.debugger_on_panic=1. The realistic impact ceiling
is DDB deadlock / forced reboot / destruction of in-panic forensic state
plus silent corruption of the live mesh routing table.
The bug (confirmed at source + disassembly)
_db_show_mesh() β the DDB pretty-printer registered as show mesh <addr>
(DB_SHOW_ALL_COMMAND(mesh, ...) at ieee80211_ddb.c:203) and reached
recursively from show vap <addr> m (via _db_show_vap calling
_db_show_mesh at ieee80211_ddb.c:499) β calls
ieee80211_mesh_rt_update(rt, 0) purely to obtain the route lifetime for
printing:
907: db_printf("\tlifetime: %u lastseq: %u priv: %p\n",
908: ieee80211_mesh_rt_update(rt, 0), /* <-- bug */
909: rt->rt_lastmseq, rt->rt_priv);
ieee80211_mesh_rt_update() (ieee80211_mesh.c:266-303) is not a
read-only accessor:
ieee80211_mesh.c:275βMESH_RT_ENTRY_LOCK(rt);βlockmgr(&(rt)->rt_lock, LK_EXCLUSIVE)(ieee80211_dragonfly.h:607), a sleepable lockmgr lock. Acquiring this from DDB (panic context, other CPUsstop_cpus'd, scheduler frozen) is forbidden: if the lock is already held by the stopped current thread,lockmgrrecurses into its ownlk_sharedowner check and panics ("locking against itself"); if held by another (frozen) CPU,lockmgrattempts to block, but the scheduler is halted in ddb β deadlock or double-fault β forced reboot.ieee80211_mesh.c:274,284,290,291β even if the lock succeeds, the helper mutatesrt_updtime, clearsIEEE80211_MESHRT_FLAGS_VALIDfromrt_flags, and zerosrt_lifetimeon any aged route. The display routine therefore silently corrupts the very routing table it is dumping.
Disassembly proof in the running #0 kernel
$ nm /boot/kernel/kernel | grep ' T ieee80211_mesh_rt_update$' ffffffff80776ba0 T ieee80211_mesh_rt_update $ objdump -d /boot/kernel/kernel.debug | grep 'callq.*<ieee80211_mesh_rt_update>$' ffffffff8075d060: e8 3b 9b 01 00 callq ffffffff80776ba0 <ieee80211_mesh_rt_update> ... $ addr2line -f -e /boot/kernel/kernel.debug 0xffffffff8075d060 _db_show_mesh /usr/src/sys/netproto/802_11/wlan/ieee80211_ddb.c:907
β confirmed: the call at 0xffffffff8075d060 lives inside _db_show_mesh at
ieee80211_ddb.c:907-908. verify.sh baseline enumerates every call site:
1 call inside _db_show_mesh (the bug) + 23 legitimate runtime calls in
hwmp_*, mesh_recv_mgmt, mesh_recv_indiv_data_*, mesh_ioctl_get80211,
mesh_rt_flush_invalid, ieee80211_mesh_forward_to_gates (where lockmgr
is appropriate).
Reachability
options DDB and options IEEE80211_SUPPORT_MESH are both in
sys/config/X86_64_GENERIC β mesh support and the ddb command are compiled
into the default kernel (verified: ieee80211_mesh_rt_update is statically
linked, not a kld module). debug.debugger_on_panic=1 on the running guest.
The ddb commands show mesh <addr>, show vap <addr> m, and
show com <addr> a are registered and reach _db_show_mesh.
Why no live DDB exercise on this guest
Triggering the bug end-to-end requires an ieee80211vap with
iv_opmode == IEEE80211_M_MBSS and iv_mesh != NULL whose ms_routes
TAILQ has at least one entry β i.e. a real WiFi adapter running an MBSS
mesh vap with at least one peer/route. This KVM audit guest has only
vtnet0 and lo0 (no wlan device); a wlan mesh vap cannot be created
without WiFi hardware. Therefore the live ddb path could not be exercised
end-to-end; the disassembly trace + addr2line proof is the authoritative
reproduction, which is conclusive: the offending call instruction is
present in the running #0 kernel at the exact cited source line.
Exploit chain
Not applicable (no userspace primitive). This is a DDB-context locking
defect. The "exploit" is db> show mesh <addr> at the ddb prompt β a
privileged operator action. Impact ceiling: ddb deadlock / double-fault
reboot / mesh table corruption. No uid=0 escalation chain exists or
applies for this finding (privileged-context-only reachability).
Fix (validated)
DDB display functions must be lock-free and non-mutating. Read the stored
rt->rt_lifetime field directly (uint32_t, ieee80211_mesh.h:436) β
exactly as every other field in this routine (rt_dest, rt_nexthop,
rt_metric, rt_lastmseq, rt_priv) is already read without the lock.
fix.diff:
--- a/sys/netproto/802_11/wlan/ieee80211_ddb.c
+++ b/sys/netproto/802_11/wlan/ieee80211_ddb.c
@@ -905,7 +905,7 @@
#endif
db_printf("\tlifetime: %u lastseq: %u priv: %p\n",
- ieee80211_mesh_rt_update(rt, 0),
+ rt->rt_lifetime,
rt->rt_lastmseq, rt->rt_priv);
i++;
}
This matches the finding markdown's ## Recommended fix proposal verbatim.
Fix validation (Phase 8) β built + booted + verified
| Step | Kernel | ieee80211_mesh_rt_update calls inside _db_show_mesh |
Result |
|---|---|---|---|
baseline (#0, audit-source, unpatched) |
sha256 (stripped) b18d2eb8β¦ (BuildID) |
1 at 0xffffffff8075d060 β ieee80211_ddb.c:907 |
BUG PRESENT |
patched (#1, single-fix) |
sha256 90c7e3cd1b0fca68β¦ |
0 | BUG GONE |
Patched disassembly of the same loop body (0xffffffff8075d058):
ffffffff8075d04e: mov 0x80(%r12),%rcx # rt->rt_priv ffffffff8075d056: mov 0x74(%r12),%edx # rt->rt_lastmseq ffffffff8075d058: mov 0x70(%r12),%esi # rt->rt_lifetime <-- read directly ffffffff8075d062: mov $0xffffffff80cd1090,%rdi # format string ffffffff8075d069: callq <db_printf> # NO callq ieee80211_mesh_rt_update
The call is gone; the field is read directly. verify.sh patched confirms
0 calls inside _db_show_mesh vs 23 legitimate runtime calls still
present (unaffected).
PoC changes
verify.sh(new) β disassembly-level verifier: enumerates everycallq ieee80211_mesh_rt_updatein/boot/kernel/kernel.debug, resolves each to its source function/line viaaddr2line -f, and asserts the count inside_db_show_meshis>=1(baseline) or==0(patched). This is the authoritative reproduction of the DDB-context bug on a guest that lacks WiFi hardware.repro.sh(new) β the theoretical live-DDB procedure for a system with a real mesh vap; not runnable on this VM, kept as the documented operator procedure.fix.diff(new) βgit apply-able unified diff; matches the finding proposal verbatim.
Kernel references (confirmed during verification)
sys/netproto/802_11/wlan/ieee80211_ddb.c:907-908β buggy call sitesys/netproto/802_11/wlan/ieee80211_ddb.c:202-213βDB_SHOW_ALL_COMMAND(mesh, db_show_mesh)registersshow mesh <addr>sys/netproto/802_11/wlan/ieee80211_ddb.c:497-499β_db_show_vaprecurses into_db_show_meshfor a mesh vapsys/netproto/802_11/wlan/ieee80211_mesh.c:266-303β the mutating helpersys/netproto/802_11/wlan/ieee80211_mesh.c:275βMESH_RT_ENTRY_LOCK(rt)= sleepable lockmgrsys/netproto/802_11/ieee80211_dragonfly.h:607β#define MESH_RT_ENTRY_LOCK(rt) lockmgr(&(rt)->rt_lock, LK_EXCLUSIVE)sys/netproto/802_11/ieee80211_mesh.h:436βrt_lifetimeis the correct read-only fieldsys/config/X86_64_GENERIC:54,256βoptions DDB+options IEEE80211_SUPPORT_MESH(default-kernel reachability)
Caveats / what to try next
- Live DDB exercise (
db> show mesh <mesh-state-addr>) on a box with a real MBSS mesh vap would let an operator witness the deadlock/double-fault directly. On this VM the disassembly + addr2line trace is conclusive. - Defense-in-depth items mentioned in the finding (clamp
keylenin_db_show_key; addnt_keyixmap != NULLguard in_db_show_node_table) are out of scope for this verification (they are unrelated to the lockmgr-from-DDB defect).
Fix verification
fixedVALIDATED the fix. Built the single-fix kernel via cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel (rc=0; full log in fix_build.log), booted it (kern.version bumped #0 -> #1), and re-ran verify.sh. Baseline #0 (unpatched): 1 call to ieee80211_mesh_rt_update inside db_show_mesh at ieee80211_ddb.c:907 (BUG PRESENT). Patched #1: 0 calls inside _db_show_mesh; disassembly at 0xffffffff8075d058 now reads mov 0x70(%r12),%esi (rt_lifetime directly) followed by callq db_printf, with no callq ieee80211_mesh_rt_update anywhere in the loop body. The 23 legitimate runtime call sites (hwmp*, mesh_recv_mgmt, mesh_ioctl_get80211, etc.) are unaffected. Fix closes the bug.
baseline #0: 'summary: 1 call(s) inside _db_show_mesh, 23 legit call(s) elsewhere' -> 'PASS: baseline BUG PRESENT: _db_show_mesh calls ieee80211_mesh_rt_update (lockmgr from DDB)'. patched #1: 'summary: 0 call(s) inside _db_show_mesh, 23 legit call(s) elsewhere' -> 'PASS: patched BUG GONE: _db_show_mesh no longer calls ieee80211_mesh_rt_update'. Patched disasm: 'ffffffff8075d058: mov 0x70(%r12),%esi # rt_lifetime' + 'ffffffff8075d069: callq <db_printf>' (no callq ieee80211_mesh_rt_update).
Confirmed kernel references
- sys/netproto/802_11/wlan/ieee80211_ddb.c:907
- sys/netproto/802_11/wlan/ieee80211_ddb.c:908
- sys/netproto/802_11/wlan/ieee80211_ddb.c:202
- sys/netproto/802_11/wlan/ieee80211_ddb.c:497
- sys/netproto/802_11/wlan/ieee80211_mesh.c:266
- sys/netproto/802_11/wlan/ieee80211_mesh.c:275
- sys/netproto/802_11/ieee80211_dragonfly.h:607
- sys/netproto/802_11/ieee80211_mesh.h:436
- sys/config/X86_64_GENERIC:54
- sys/config/X86_64_GENERIC:256
Detail
Exploit chain
none (non-corruption: DDB-context privileged debugger defect, not a userspace memory-corruption primitive). No uid=0 escalation chain exists or applies -- the trigger is an operator-typed db> show mesh <addr> at the ddb prompt; the bug is reachable only with console/kdb access or after a prerequisite panic. Realistic impact ceiling: DDB deadlock / forced reboot / destruction of in-panic forensic state + silent mesh routing table corruption.
Evidence (decisive lines)
baseline (#0): objdump+addr2line -> callq ieee80211_mesh_rt_update at 0xffffffff8075d060 inside _db_show_mesh at ieee80211_ddb.c:907; verify.sh baseline -> 'summary: 1 call(s) inside _db_show_mesh, 23 legit call(s) elsewhere' / 'PASS: baseline BUG PRESENT'. patched (#1, single-fix): verify.sh patched -> 'summary: 0 call(s) inside _db_show_mesh, 23 legit call(s) elsewhere' / 'PASS: patched BUG GONE'; disassembly of the same loop body now reads the field directly: 'mov 0x70(%r12),%esi # rt->rt_lifetime' instead of callq ieee80211_mesh_rt_update.
PoC changes
Added verify.sh (disassembly-level verifier: enumerates every callq ieee80211_mesh_rt_update via objdump, resolves each to source via addr2line -f, asserts count inside _db_show_mesh is >=1 baseline / ==0 patched -- runs on any DFly kernel with DDB+IEEE80211_SUPPORT_MESH, which is the default GENERIC). Added repro.sh (theoretical live-DDB procedure for a box with a real mesh vap; not runnable on this VM). Added fix.diff (git-apply-able; matches finding proposal verbatim). Added build.sh/run.sh wrappers, VERDICT.md, manifest.json, and saved baseline_verify.log/patched_verify.log/patched_disasm.txt/fix_build.log/env.txt.
Verified recommended fix
In sys/netproto/802_11/wlan/ieee80211_ddb.c:908 replace the call ieee80211_mesh_rt_update(rt, 0) with a direct read of rt->rt_lifetime. This makes the DDB display routine lock-free and non-mutating, matching how every other field in the routine is already read; the lockmgr-acquiring, state-mutating helper is no longer invoked from debugger context. Matches finding proposal verbatim (no supersede). Full diff in findings/poc/DF-0606/fix.diff.
Verdict
REPRODUCED (disassembly-confirmed). db_show_mesh (the DDB command registered as show mesh <addr> at ieee80211_ddb.c:202-213, and reached recursively from show vap <addr> m at ieee80211_ddb.c:497-499) calls ieee80211_mesh_rt_update(rt, 0) at ieee80211_ddb.c:908 purely to obtain a value for printing. That helper acquires the sleepable lockmgr MESH_RT_ENTRY_LOCK (ieee80211_mesh.c:275 -> ieee80211_dragonfly.h:607 lockmgr(&rt->rt_lock, LK_EXCLUSIVE)) and MUTATES rt_updtime/rt_flags/rt_lifetime (ieee80211_mesh.c:274,284,290,291). Disassembly of the running #0 kernel proves the offending call: objdump shows callq ieee80211_mesh_rt_update at 0xffffffff8075d060 and addr2line resolves it to _db_show_mesh / ieee80211_ddb.c:907. verify.sh baseline enumerates 1 call inside _db_show_mesh (the bug) plus 23 legitimate runtime calls in hwmp/mesh_recv_/etc. Live ddb exercise is impossible on this VM (no WiFi hardware -- only vtnet0/lo0 -- so no MBSS mesh vap with routes can be constructed); the disassembly+addr2line trace is the authoritative reproduction and is conclusive. Impact ceiling: DDB deadlock (lockmgr blocks against a stopped CPU) or double-fault/forced reboot (recursive lock panic re-enters the trap from inside ddb), destroying in-panic forensic state, plus silent corruption of the mesh routing table on every dump. Privileged-context-only trigger (console/kdb or panic-to-ddb with debug.debugger_on_panic=1) -- Medium severity confirmed.
No comments yet.