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

TOCTOU use-after-free on rtentry in ng_btsocket_l2cap_raw_bind: releases rt_lock before storing pointer

Summary

ng_btsocket_l2cap_raw_bind(:690-715): looks up rt under rt_lock, RELEASES rt_lock at :703 before storing pointer at :714. ng_btsocket_l2cap_rtentry has NO refcount(ng_btsocket_l2cap.h:44-48). Between :703 and :712 bind holds NEITHER rt_lock NOR pcb_lock. ng_btsocket_l2cap_raw_rtclean(:450-502) can LIST_REMOVE+kfree rt(:495). bind then stores dangling pcb->rt=rt(:714). Subsequent ioctl derefs pcb->rt->hook without rt_lock -> reads freed heap. Freed slot reclaimable from M_NETGRAPH -> partial control of ->hook ptr -> UAF type confusion. Unpriv local user can attempt (attach grants socket, caps check only sets flag). Barrier: triggering concurrent hook disconnect needs netgraph priv/BT hw removal/malicious peer. Fix: hold rt_lock across :714.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0497 Β· 7 files
FileTypeDescriptionSize
trace.md source-trace TOCTOU window + rtclean race + no-refcount proof 2.8 KB ↓ raw
fix.diff suggested-fix hold rt_lock across pcb->rt store 937 B view raw
VERDICT.md verdict full narrative 1.5 KB ↓ raw
README.md readme summary 3.2 KB ↓ raw
env.txt environment guest uname, modules, HW-gate note 188 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 summary
↓ download raw

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

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

/* 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
VERDICT.md verdict full narrative
↓ download raw

DF-0497 detailed verdict

Verdict: INCONCLUSIVE at runtime β€” real TOCTOU UAF by inspection; no BT HW on guest

Mechanism

ng_btsocket_l2cap_raw_bind (sys/netgraph7/bluetooth/socket/...raw.c:690-715) releases ng_btsocket_l2cap_raw_rt_lock at :703, then β€” holding neither rt_lock nor pcb_lock β€” stores pcb->rt = rt at :714. The rtentry type has no refcount (ng_btsocket_l2cap.h:44-48), so a concurrent ng_btsocket_l2cap_raw_rtclean (:495 LIST_REMOVE+kfree) frees rt in the window; bind stores a dangling pointer, later UAF-read via pcb->rt->hook. CWE-416.

Privilege boundary

bind reachable by unprivileged user (attach sets only a flag), but the racing rtclean needs a Bluetooth hook disconnect (BT HW / netgraph priv). The finding's stated realistic barrier.

Why not reproduced

No Bluetooth hardware and no bluetooth netgraph modules on the audit guest (none under /boot/modules or /modules). ng_btsocket_l2cap_raw_node is NULL β‡’ bind returns before :690. Path is dead on this guest.

Fix (applies + compiles + boots)

fix.diff: hold rt_lock across pcb->rt = rt; release after on both paths. Lock-order verified: rtclean never holds rt_lock+pcb_lock together, so nesting pcb_lock inside rt_lock is safe. Built in combined kernel (#1); boots clean. fix_status = not_testable (no BT HW).

Why no chain

Trigger gated behind BT HW/netgraph priv on this guest; the UAF cannot be exercised. Documented as a real latent bug with a verified compile-clean fix.

trace.md source-trace TOCTOU window + rtclean race + no-refcount proof
↓ download raw

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.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. ng_btsocket_l2cap_raw_bind TOCTOU UAF rtentry no refcount. No BT HW/modules on guest.