# DF-0497 source trace — TOCTOU UAF on ng_btsocket_l2cap rtentry

## The window
`sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap_raw.c`
`ng_btsocket_l2cap_raw_bind()`:

```
690: if (bcmp(&sa->l2cap_bdaddr, NG_HCI_BDADDR_ANY, sizeof(...)) != 0) {
692:     lockmgr(&ng_btsocket_l2cap_raw_rt_lock, LK_EXCLUSIVE);   <-- TAKE rt_lock
694:     LIST_FOREACH(rt, &ng_btsocket_l2cap_raw_rt, next) {
695:         if (rt->hook == NULL || NG_HOOK_NOT_VALID(rt->hook)) continue;
698:         if (bcmp(&sa->l2cap_bdaddr, &rt->src, sizeof(rt->src)) == 0) break;
700:     }
703:     lockmgr(&ng_btsocket_l2cap_raw_rt_lock, LK_RELEASE);     <-- RELEASE rt_lock  *** WINDOW OPENS ***
705:     if (rt == NULL) { error = ENETDOWN; goto out; }
710: } else rt = NULL;
       /* between here and :714 bind holds NEITHER rt_lock NOR pcb_lock */
712: lockmgr(&pcb->pcb_lock, LK_EXCLUSIVE);                       <-- take pcb_lock
713: bcopy(&sa->l2cap_bdaddr, &pcb->src, sizeof(pcb->src));
714: pcb->rt = rt;                                                <-- STORE (possibly dangling)
715: lockmgr(&pcb->pcb_lock, LK_RELEASE);                         *** WINDOW CLOSES ***
```

## The free (racing, in rtclean)
`ng_btsocket_l2cap_raw_rtclean()` (:450-502):
```
: (takes ng_btsocket_l2cap_raw_rt_lock)
:     LIST_FOREACH_SAFE(rt, &ng_btsocket_l2cap_raw_rt, next, tmp) {
:         if (<rt still valid>) continue;
495:        LIST_REMOVE(rt, next);
:         kfree(rt, M_NETGRAPH_BTSOCKET_L2CAP_RAW);              <-- FREE
:     }
: (releases rt_lock)
```
`rtclean` runs when a Bluetooth hook disconnects (HCI device removal / peer
disconnect / netgraph reconfig). If it runs in the bind window (:703–:714), the
`rt` that `LIST_FOREACH` handed to `bind` is `kfree`'d. `bind` then stores the
freed pointer in `pcb->rt` (:714).

## Why it's a UAF (no refcount)
`ng_btsocket_l2cap_rtentry` (`ng_btsocket_l2cap.h:44-48`) has no reference
count. Nothing in `bind` takes a reference on `rt` after the lookup; releasing
`rt_lock` therefore drops the only protection. Subsequent operations on the pcb
dereference `pcb->rt->hook` (e.g. in connect/ioctl paths) without `rt_lock` ⇒
read of freed `M_NETGRAPH` heap. The freed slot is reclaimable (slab reuse) ⇒
partial attacker influence over the `->hook` value read ⇒ potential type
confusion.

## Trigger barrier (the finding's stated caveat)
- `bind` itself is reachable by an unprivileged user (attach only sets a flag).
- But the racing `rtclean` needs a Bluetooth hook disconnect, which on a real
  system means BT hardware present + a disconnecting peer / device removal, or
  netgraph privilege to tear down the lower link. On the audit guest there is
  no BT hardware and no bluetooth netgraph module, so the path is dead.

## Fix
Hold `rt_lock` across the `pcb->rt = rt` store (release after, on both paths).
See `fix.diff`. Lock-order verified safe vs `rtclean`.
