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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0558 Β· 8 files
FileTypeDescriptionSize
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
VERDICT.md verdict full source trace + why-not-reproducible + fix
↓ download 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_testable

compile 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.