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)
PoC verification
Evidence pack
findings/poc/DF-3477 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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_testableConfirmed 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').
No comments yet.