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

Unprivileged PSM bind: ng_btsocket_l2cap_bind() has no privilege check β€” any local user can occupy reserved Bluetooth service PSMs (SDP=1, RFCOMM=3) and hijack inbound connections

Summary

ng_btsocket_l2cap_bind() performs no priv_check and no PSM range restriction. VERIFIED on guest: uid=1001 successfully binds PSMs 1 and 3; a second bind of PSM 1 returns EADDRINUSE, demonstrating occupation semantics. Because pcb_by_addr() dispatches inbound connections to the matching bound socket, an unprivileged user can squat or impersonate any local Bluetooth service on systems with the ng_btsocket stack loaded. Local unpriv on a BT-equipped system; service interception for new inbound connections. Fix: priv_check for assigned-range PSMs (<0x1001).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3477 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
df3477_bind.c β€” 2.1 KB view raw
run.log β€” 243 B view raw
verdict.json β€” 2.1 KB view raw
manifest.json β€” 674 B view raw

DF-3477 β€” Unprivileged L2CAP PSM bind (no privilege check in ng_btsocket_l2cap_bind)

What

ng_btsocket_l2cap_bind() (sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c:2037-2096) contains no priv_check()/capability check and no PSM range restriction. Any local user can bind() a PF_BLUETOOTH / SOCK_SEQPACKET / BLUETOOTH_PROTO_L2CAP socket to any L2CAP PSM β€” including the assigned/reserved service PSMs (1 = SDP service discovery, 3 = RFCOMM) β€” and then listen() on it.

Because inbound L2CAP connections are dispatched by ng_btsocket_l2cap_pcb_by_addr() (:2679-2699, exact bdaddr match first, then the ANY-bound fallback), the first socket bound to a PSM receives all inbound connections for that PSM. An unprivileged user can therefore squat or hijack local Bluetooth services on any system where the ng_btsocket stack is loaded and a Bluetooth adapter is present: the service's own listener either fails to bind (EADDRINUSE) or, if it bound later/earlier without the exact address, connections are delivered to the attacker's socket.

Related sibling (filed separately for the raw node): DF-0499 (read-only node ioctls). This finding is the SEQPACKET data-path equivalent with a stronger impact (service impersonation rather than info exposure).

Reproduce

Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC, INVARIANTS), netgraph7 module stack loaded:

kldload netgraph.ko ng_bluetooth.ko ng_btsocket.ko

Build and run as an unprivileged user (uid 1001):

cc -o df3477_bind df3477_bind.c
./df3477_bind

Expected (vulnerable):

DF-3477: unprivileged L2CAP PSM bind test (uid=1001)
uid=1001 bind(psm=1) -> SUCCESS
uid=1001 bind(psm=3) -> SUCCESS
RESULT: VULNERABLE β€” unprivileged user occupied a reserved L2CAP service PSM
uid=1001 bind(psm=1) -> Address already in use

The second bind(psm=1) returning EADDRINUSE proves the occupation semantics that make service hijacking possible.

Fix direction

Add a privilege check for non-ephemeral PSMs (or at minimum PSMs in the assigned/reserved range < 0x1001), e.g.:

--- a/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c
+++ b/sys/netgraph7/bluetooth/socket/ng_btsocket_l2cap.c
@@ -2071,6 +2071,12 @@ ng_btsocket_l2cap_bind(netmsg_t msg)
    psm = le16toh(sa->l2cap_psm);
+
+   /* Assigned/reserved PSMs require privilege */
+   if (psm != 0 && psm < 0x1001) {
+       struct thread *td = msg->bind.nm_td;
+       if (td && priv_check(td, PRIV_NETBOUND) != 0)
+           ERROUT(EACCES);
+   }

(matching whatever privilege primitive the project prefers for socket binds).

Fix verification

not_testable
per-fix-DF-3477

Confirmed kernel references

Detail

Exploit chain

socket(PF_BLUETOOTH, SOCK_SEQPACKET, BLUETOOTH_PROTO_L2CAP) -> bind(PSM 1) as unprivileged user -> listen() [with DF-3478 fixed] -> ng_btsocket_l2cap_pcb_by_addr() delivers every inbound connection for that PSM to the attacker's listener instead of the system service

Evidence (decisive lines)

['README.md', 'df3477_bind.c', 'run.log (uid=1001 bind(psm=1) SUCCESS / bind(psm=3) SUCCESS / second bind(psm=1) EADDRINUSE)']

PoC changes

none β€” seed was accurate

Verified recommended fix

priv_check() privileged/binding user for assigned PSMs (< 0x1001) in ng_btsocket_l2cap_bind()

Verdict

Reproduced on the guest: uid=1001 successfully bind(2)s a PF_BLUETOOTH SOCK_SEQPACKET L2CAP socket to reserved service PSMs 1 (SDP) and 3 (RFCOMM) β€” ng_btsocket_l2cap_bind() contains no priv_check and no PSM range restriction β€” and a second bind of the same PSM returns EADDRINUSE, demonstrating occupation semantics. Impact ceiling is local Bluetooth service impersonation/squatting on systems with the ng_btsocket stack loaded and a BT adapter (no kernel memory-safety impact; the impact enum has no 'service-hijack' value, hence 'none').