hci_event_num_compl_pkts: unbounded variable-length loop NO per-iteration bounds check -> remote kernel panic
Summary
hci_event_num_compl_pkts(:376-405): trusts attacker ep.num_con_handles(uint8 0-255). Only up-front check KKASSERT(pkthdr.len>=sizeof(ep)=1)(:376). while(ep.num_con_handles--) loop(:380-405) m_copydata(handle)+m_copydata(num)+m_adj per iter NO check chain still contains 4*count bytes. Controller sends num_con_handles>N actual pairs -> m_copydata walks off chain -> KASSERT(m!=NULL) panic INVARIANTS / NULL-deref panic non-INVARIANTS. hci_event_hdr_t.length parsed at hci_event():168 NEVER consulted. INVARIANTS on in X86_64_GENERIC(:56) so panic is production default. Netbt twin of DF-0543(ng7). Remote unauth BT DoS. Fix: validate num_con_handles*4<=pkthdr.len upfront.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0558 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | upfront num_con_handles*4 <= pkthdr.len bound + early return | 609 B | view raw |
| build.sh | build-script | build netbt.ko to validate the fix compiles | 439 B | view raw |
| run.sh | run-script | documents HW-gated trigger (no BT radio on guest) | 673 B | view raw |
| VERDICT.md | verdict | full source trace + why-not-reproducible + fix | 3.3 KB | β raw |
| env.txt | environment | uname, config, netbt module/HW reachability | 1.8 KB | view raw |
| fix_build.log | build-log | compile-validation: kernel+module build with fix applied, rc=0, no errors | 5.3 KB | 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 |
DF-0558 β hci_event_num_compl_pkts unbounded loop
Verdict
NOT TESTABLE at runtime (no Bluetooth hardware) β bug CONFIRMED at source
(certain). Fix authored in fix.diff; compiled cleanly as part of a combined
nativekernel build (netbt.ko) carrying all four verified findings' fixes.
Mechanism (source trace, every hop cited)
hci_event() (sys/netbt/hci_event.c:160-230) is the single HCI event
dispatcher. It reads the 4-byte hci_event_hdr_t, m_adjs it off, then
switches on hdr.event. For HCI_EVENT_NUM_COMPL_PKTS it calls
hci_event_num_compl_pkts(unit, m) at hci_event.c:186.
hci_event_num_compl_pkts() (sys/netbt/hci_event.c:368-432):
- KKASSERT(m->m_pkthdr.len >= sizeof(ep)) (:376) β only verifies the 1-byte
hci_num_compl_pkts_ep header is present. Reads ep.num_con_handles (a
uint8_t, 0-255, attacker-controlled) and m_adjs it off (:377-378).
- while (ep.num_con_handles--) (:380) β the unbounded loop. Each
iteration consumes sizeof(handle)+sizeof(num) = 4 bytes via
m_copydata+m_adj (:381-386) with no check that the mbuf chain still
contains 4 bytes.
- ep.num_con_handles is taken straight from the controller's event and is
never validated against m->m_pkthdr.len (the remaining bytes), nor is
hci_event_hdr_t.length (:168) consulted anywhere on this path.
If a controller (or a malicious Bluetooth adapter / injected HCI event) sends
num_con_handles = N but fewer than 4*N follow-on bytes, the loop runs off
the end of the mbuf chain:
- m_copydata on an empty/exhausted chain returns with m == NULL and
KASSERT(m != NULL) trips (INVARIANTS ON in X86_64_GENERIC, :56) β
panic; or
- on a non-INVARIANTS kernel a NULL-deref in m_copydata/m_adj β panic.
INVARIANTS is ON in the default X86_64_GENERIC, so the panic is the
production-default behavior β a remote unauthenticated Bluetooth DoS (the
event can arrive from any paired/scanning controller with no application-level
auth).
Why not reproduced live (the realistic constraint)
The QEMU/KVM audit guest has no Bluetooth controller hardware. netbt is
shipped only as the netbt.ko module (NOT compiled into the kernel; nm on
/boot/kernel/kernel shows no hci_event_* symbols), and is not loaded.
Crucially, even loading netbt.ko does not create a struct hci_unit β
that only happens when a real controller driver (ubt/bt3c/bcsp/β¦) attaches.
With no unit, hci_input() is never called and hci_event() is unreachable.
So no harness on this guest can deliver a crafted HCI event; the bug is a
genuine latent/net path that is only live on a machine with a Bluetooth radio.
The netbt-twin finding DF-0543 (netgraph7 BT) is the same defect in the older
stack.
Fix (fix.diff)
Add an explicit upfront bounds check after reading ep, before the loop:
bound ep.num_con_handles * (sizeof(handle)+sizeof(num)) against the
remaining m->m_pkthdr.len, log + return on a malformed event. This is the
minimal targeted fix: it makes the loop provably unable to run off the chain
and supersedes reliance on the INVARIANTS-only KKASSERT.
PoC changes
findings/poc/DF-0558/ was empty on arrival (no reviewer PoC). This runner
authored: fix.diff, build.sh, run.sh, README.md, VERDICT.md,
manifest.json, env.txt.
Fix verification
not_testablecompile validated rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed real, no BT HW. hci_event_num_compl_pkts unbounded loop past mbuf.
No comments yet.