# DF-0497 — TOCTOU use-after-free on rtentry in ng_btsocket_l2cap_raw_bind

## Verdict: INCONCLUSIVE at runtime — bug real by inspection; no Bluetooth hardware/netgraph on guest

## The bug (real, by source inspection)
`sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap_raw.c`
`ng_btsocket_l2cap_raw_bind()` (lines 690-715):

```c
690:  if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, ...) != 0) {
692:      lockmgr(&ng_btsocket_l2cap_raw_rt_lock, LK_EXCLUSIVE);
694:      LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) { ... }
703:      lockmgr(&ng_btsocket_l2cap_raw_rt_lock, LK_RELEASE);   /* RELEASE */
705:      if (rt == NULL) { error = ENETDOWN; goto out; }
710:  } else rt = NULL;
712:  lockmgr(&pcb->pcb_lock, LK_EXCLUSIVE);
713:  bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src));
714:  pcb->rt = rt;                                              /* STORE */
715:  lockmgr(&pcb->pcb_lock, LK_RELEASE);
```

Between the `rt_lock` release (:703) and the `pcb->rt = rt` store (:714), bind
holds **neither** `rt_lock` nor `pcb_lock`. `ng_btsocket_l2cap_rtentry` has
**no refcount** (`ng_btsocket_l2cap.h:44-48`), so nothing keeps `rt` alive.
A concurrent `ng_btsocket_l2cap_raw_rtclean()` (:450-502) — which takes
`rt_lock` and `LIST_REMOVE`+`kfree(rt)` (:495) — can free `rt` in that window.
`bind` then stores a dangling `pcb->rt`; subsequent ioctls dereference
`pcb->rt->hook` without `rt_lock` ⇒ read of freed `M_NETGRAPH` heap ⇒ UAF /
type confusion. CWE-416.

## Privilege boundary
`socket(PF_BLUETOOTH, ...)` attach only sets a capability flag (no hard root
gate on the socket itself), so an unprivileged local user can reach `bind`.
But triggering the racing `rtclean`/hook-disconnect needs Bluetooth hardware or
netgraph privilege (loading the bluetooth netgraph subsystem, root-only on this
guest). So the *UAF trigger* is gated behind BT HW / netgraph priv, while the
*bind* itself is reachable by an unprivileged user — the finding's stated
realistic barrier.

## Why not reproduced on this guest
No Bluetooth hardware and no bluetooth netgraph modules on the audit guest
(`find /boot/modules /modules -iname '*bluetooth*' -o -iname '*hci*' -o -iname
'*l2cap*'` ⇒ none). `ng_btsocket_l2cap_raw_node` is NULL without the subsystem
loaded, so `bind` returns before reaching :690. The UAF cannot be exercised.

## Fix (applies + compiles + boots)
`fix.diff` keeps `rt_lock` held across the `pcb->rt = rt` store (and releases
it only after, on both the success and `rt==NULL` paths):

```c
/* keep rt_lock held; ng_btsocket_l2cap_rtentry has no refcount */
if (rt == NULL) { lockmgr(&rt_lock, LK_RELEASE); error = ENETDOWN; goto out; }
...
pcb->rt = rt;
lockmgr(&pcb->pcb_lock, LK_RELEASE);
if (rt != NULL) lockmgr(&rt_lock, LK_RELEASE);
```
Lock-order check: `rtclean` takes `rt_lock` alone for the rt-free section and
`sockets_lock`→`pcb_lock` separately; it never holds `rt_lock`+`pcb_lock`
together, so nesting `pcb_lock` inside `rt_lock` in `bind` introduces no
inversion. Built in the combined single-fix kernel (#1); boots clean.
`fix_status = not_testable` (no BT HW on guest).

## Files
- `trace.md` — line-by-line TOCTOU trace + rtclean race window
- `fix.diff` — hold rt_lock across pcb->rt store
- `README.md`, `VERDICT.md`, `manifest.json`
