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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5742 Β· 7 files
FileTypeDescriptionSize
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
README.md file
↓ download 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.

VERDICT.md file
↓ download raw

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_testable
baseline reproduced→ patch + rebuild →patched clean

baseline 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
↓ fix.diffper-fix-DF-5742

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.