# 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:

```c
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`:
```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)

- `sys/netproto/802_11/wlan/ieee80211_ddb.c:907-908` — buggy call site
- `sys/netproto/802_11/wlan/ieee80211_ddb.c:202-213` — `DB_SHOW_ALL_COMMAND(mesh, db_show_mesh)` registers `show mesh <addr>`
- `sys/netproto/802_11/wlan/ieee80211_ddb.c:497-499` — `_db_show_vap` recurses into `_db_show_mesh` for a mesh vap
- `sys/netproto/802_11/wlan/ieee80211_mesh.c:266-303` — the mutating helper
- `sys/netproto/802_11/wlan/ieee80211_mesh.c:275` — `MESH_RT_ENTRY_LOCK(rt)` = sleepable lockmgr
- `sys/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_lifetime` is the correct read-only field
- `sys/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 `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).
