β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-0606

_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 with debugger_on_panic=1.
  • Privileges gained or impact:
  • (a) A:H β€” the ddb session hangs (lockmgr deadlock 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_lifetime across 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_MESH compiled in (part of the standard wlan_mesh module) and an MBSS vap with peers (ms_routes non-empty). Requires DDB in the kernel config (standard on DragonFly).
  • Reachability: show vap <mesh-vap-addr> m or show com <mesh-ic-addr> a at the db> prompt, where <mesh-vap-addr> is an ieee80211vap whose iv_opmode == IEEE80211_M_MBSS and iv_mesh != NULL. The loop body executes once per entry in ms->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_mesh is invoked on a mesh vap with at least one route.

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

Timeline

  • 2026-07-02 Discovered during automated DragonFlyBSD kernel security audit.
  • 2026-07-02 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0606 Β· 15 files
FileTypeDescriptionSize
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
README.md readme original PoC README + verifier notes
↓ download 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, run show 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_mesh module loaded and an MBSS vap with at least one route in ms_routes. Verify with ifconfig wlan0 list mesh before 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 (read rt->rt_lifetime directly instead of calling ieee80211_mesh_rt_update); after the fix, show vap <addr> m should return cleanly without mutating the table or deadlocking.
VERDICT.md verdict full narrative: bug, disassembly proof, fix validation
↓ download raw

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 CPUs stop_cpus'd, scheduler frozen) is forbidden: if the lock is already held by the stopped current thread, lockmgr recurses into its own lk_shared owner check and panics ("locking against itself"); if held by another (frozen) CPU, lockmgr attempts 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 mutates rt_updtime, clears IEEE80211_MESHRT_FLAGS_VALID from rt_flags, and zeros rt_lifetime on 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 every callq ieee80211_mesh_rt_update in /boot/kernel/kernel.debug, resolves each to its source function/line via addr2line -f, and asserts the count inside _db_show_mesh is >=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)

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 keylen in _db_show_key; add nt_keyixmap != NULL guard in _db_show_node_table) are out of scope for this verification (they are unrelated to the lockmgr-from-DDB defect).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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).
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Wed Jul 8 19:45:58 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (sha256 kernel=90c7e3cd1b0fca6849ac8778a42adc1d90700b889c13220dd7fc6ca4442cf3f9)

Confirmed kernel references

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.