hci_mtap() taps into receive buffers with no flow control: sbappendaddr without any ssb_space/hiwat check β unprivileged unbounded consumer of the global mbuf pool (kernel-wide allocation failure, hard hang)
Summary
Every matching packet is copied into each tap socket's so_rcv with no limit: a socket that never reads accumulates mbufs without bound from the GLOBAL pool. Demonstrated twice: 'objcache(mbuf pkthdr) exhausted' on all CPUs then unrecoverable hard hang. Unprivileged alone (HCI sockets unprivileged, filters settable by owner, the attacker's own command sends generate the tapped traffic). ssb_space-gate fix proposed (mechanism-confirmed).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5742 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.4 KB | β raw | |
| VERDICT.md | file | 1.4 KB | β raw | |
| exhaustion.txt | file | 1.3 KB | view raw | |
| fix.diff | file | 836 B | view raw | |
| race4.log | file | 566 B | view raw | |
| race5.log | file | 1.6 KB | view raw | |
| verdict.json | file | 2.8 KB | view raw |
DF-5742 β hci_mtap() taps into receive buffers with no flow control:
unbounded mbuf accumulation -> global mbuf-pool exhaustion (kernel-wide allocation failure / hard hang)
Production code: sys/netbt/hci_socket.c:1003-1010
m0 = m_copym(m, 0, M_COPYALL, M_NOWAIT);
if (m0 && sbappendaddr(&pcb->hp_socket->so_rcv.sb, &sa, m0, ctlmsg))
sorwakeup(pcb->hp_socket);
sbappendaddr() checks no high-water mark; ssb_cc/ssb_mbcnt grow
without bound for a socket that never reads. Every HCI packet (event,
command, ACL, SCO) that passes a tap socket's filters is copied into
its receive queue. A raw HCI socket that stops reading (or a handful
of them) turns the Bluetooth stack into an unbounded consumer of the
GLOBAL mbuf pool: every kernel allocation needing an mbuf pkthdr fails
system-wide (network, VM paging paths, ...), observed live as
"Warning: objcache(mbuf pkthdr) exhausted" on ALL CPUs followed by an
unrecoverable hard hang of the whole guest (exhaustion.txt,
race4.log, race5.log).
Reachability: raw HCI sockets are unprivileged; on this kernel every socket is even HCI_PRIVILEGED (DF-5741), so an unprivileged user can (1) open N promiscuous sockets with full packet+event filters via setsockopt and simply never read, and (2) generate the traffic himself (sending commands produces Command Complete/Status events that are tapped). No controller traffic from other users needed; no race; no timing.
The evidence in this pack was recorded during the DF-5740 race rig iterations whose taps were not draining (the first crash investigation, before the taps were made to drain, was in fact CAUSED by this bug, not by the DF-5740 race - see DF-5740/panic.txt's confound note; both point at the same run family).
Reproduce
Load the DF-5740 rig, sysctl hw.hsh_setup=1, then run its trigger
with taps that never read, e.g. ./trigger 100 48 40 (taps are only
drained at the end) while sysctl hw.hsh_fire=99999 floods
completions. Expected: exhaustion warnings on all CPUs within tens of
seconds, then system-wide allocation failure/hang.
Fix (proposed, NOT runtime-validated separately)
fix.diff: skip sockets whose receive buffer has no space
(ssb_space() check before sbappendaddr), mirroring what every other
tap/protocol does. Authored after the mechanism was confirmed; not
separately rebuilt on the guest (the class is resource exhaustion, not
memory corruption), so mark untested.
DF-5742 β VERDICT
Reproduced: yes (as part of the DF-5740 rig runs; the exhaustion is attributable to this bug, not to the race): with non-draining promiscuous tap sockets and controller-traffic injection through production hci_input_event/hci_output_cmd paths, the guest printed "Warning: objcache(mbuf pkthdr) exhausted" on cpu0..cpu3 and then hung hard (no trap, no dump, no console output, ssh dead) β twice (race4, race5; exhaustion.txt). Each copy is m_copym + sbappendaddr with zero bound (hci_socket.c:1003-1005); ssb_cc grows forever; the mbuf pool is global.
Impact: local DoS, kernel-wide (all mbuf-pkthdr allocations fail; hard hang observed). Unprivileged (HCI sockets are unprivileged; DF-5741 makes the full tap reachable by any user trivially; even without it, default filters pass Command Complete/Status events that the attacker's own sends generate).
Fix: proposed in fix.diff (ssb_space gate before sbappendaddr); mechanism-confirmed but not separately rebuilt/validated on the guest (resource-exhaustion class; the fix is the standard tap pattern).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablebaseline exhaustion/hang reproduced twice; fix.diff authored (standard tap pattern) but not rebuilt on the guest in this run - marked not_tested honestly
exhaustion.txt; fix.diff
Confirmed kernel references
Detail
Exploit chain
unpriv user: open N raw HCI sockets, bind ANY (promiscuous), setsockopt full SO_HCI_PKT_FILTER/SO_HCI_EVT_FILTER, never read; send allowed (or with DF-5741 any) commands to generate Command Complete/Status traffic -> every packet copied N times unboundedly -> global mbuf pool exhaustion -> kernel-wide allocation failure -> hard hang (observed).
Evidence (decisive lines)
exhaustion.txt (all-cpu warnings + hang, two runs); race4.log/race5.log (trigger-side); fix.diff (ssb_space gate, proposed)
PoC changes
no separate PoC build; evidence captured with the DF-5740 rig (same pack) with non-draining taps - which is precisely the bug's trigger
Verified recommended fix
gate the tap on receive-buffer space: if (ssb_space(&so->so_rcv) < pktlen) { free copy; continue; } before sbappendaddr (fix.diff).
Verdict
hci_mtap (hci_socket.c:1003-1010) copies every matching HCI packet into each tap socket's so_rcv via sbappendaddr with no ssb_space/hiwat check - a non-reading raw HCI socket accumulates mbufs without bound from the GLOBAL mbuf pool. Demonstrated on the guest: with non-draining promiscuous taps and controller traffic injected through production paths, 'Warning: objcache(mbuf pkthdr) exhausted' fired on cpu0..cpu3 and both guests then hard-hung unrecoverably (exhaustion.txt, race4/5 logs recorded during the DF-5740 rig sessions whose taps were not draining - the exhaustion is this bug's, independent of the close-race also running). Unprivileged: HCI sockets are unprivileged, and with DF-5741 any user can open full-filter promiscuous taps and generate the tapped traffic themselves.
No comments yet.