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

hci_event_command_compl: reads status byte beyond asserted length -> short-event remote kernel panic

Summary

hci_event_command_compl(:299-317): KKASSERT(pkthdr.len>=sizeof(hci_command_compl_ep)=3)(:299) copies ep+m_adj. Then UNCONDITIONALLY m_copydata(m,0,sizeof(rp=1),&rp)(:313) to read status byte. Comment :308-312 admits not guaranteed command_complete will contain status. If controller sends CommandComplete param total=3 (no return params) -> m_copydata reads 1 byte never validated -> KASSERT(m!=NULL) panic INVARIANTS / NULL-deref non-INVARIANTS. Happens BEFORE per-opcode handler re-asserts. Remote unauth BT DoS. Fix: if(pkthdr.len>=sizeof(rp)) m_copydata.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0560 Β· 7 files
FileTypeDescriptionSize
bttest.c trigger-source conceptual PoC skeleton (cannot run without BT HW) 1.9 KB view raw
fix.diff suggested-fix guard m_copydata with `if (m_pkthdr.len >= sizeof(rp))` 883 B view raw
build.log build-log netbt.ko build with patched hci_event.c, full output 6.8 KB view raw
VERDICT.md verdict full narrative: source trace, threat model, fix validation 6.1 KB ↓ raw
env.txt environment guest uname, netbt.ko shipped but no BT controller 1.0 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 narrative: source trace, threat model, fix validation
↓ download raw

DF-0560 β€” hci_event_command_compl reads status byte beyond mbuf

Verdict

NOT REPRODUCED LIVE β€” but bug confirmed by source trace. The path requires a Bluetooth controller (real HW or virtual HCI device) to send a Command Complete event with a 3-byte payload to the kernel's BT stack. The audit guest has no BT HW and no virtual-HCI driver; the netbt.ko module IS shipped but unreachable without a controller.

Mechanism (cited path:line, confirmed by trace)

sys/netbt/hci_event.c line-by-line:

293: static void
294: hci_event_command_compl(struct hci_unit *unit, struct mbuf *m)
295: {
296:     hci_command_compl_ep ep;             // sizeof = 3 (1 + 2)
297:     hci_status_rp rp;                    // sizeof = 1
298:
299:     KKASSERT(m->m_pkthdr.len >= sizeof(ep));   // asserts >= 3 bytes
300:     m_copydata(m, 0, sizeof(ep), &ep);        // copies 3 bytes
301:     m_adj(m, sizeof(ep));                     // trims 3 bytes off front
302:
...
308:     /*
309:      * I am not sure if this is completely correct, it is not guaranteed
310:      * that a command_complete packet will contain the status though most
311:      * do seem to.
312:      */
313:     m_copydata(m, 0, sizeof(rp), &rp);        // <--- BUG: unconditional

After m_adj(m, sizeof(ep)), the mbuf has (pkthdr.len - 3) bytes remaining. The KKASSERT only verified the pre-m_adj length was >= 3. If the original event had exactly 3 bytes of payload (i.e. a Command Complete event with num_cmd_pkts + opcode and NO return parameters, which is legal per the BT spec for commands that have no return params), the trimmed mbuf has 0 bytes.

m_copydata(m, 0, sizeof(rp)=1, &rp) then tries to read 1 byte from the now-empty mbuf. In sys/kern/uipc_mbuf.c:1671-1696:

1686:    while (len > 0) {
1687:        KASSERT(m != NULL,("%s: length > size of mbuf chain", __func__));
1688:        count = min(m->m_len - off, len);          // 0 for empty mbuf
1689:        bcopy(mtod(m, caddr_t) + off, cp, count);   // copies 0 bytes
1690:        len -= count;                                // len unchanged
1691:        cp += count;
1692:        off = 0;
1693:        m = m->m_next;                               // NULL for single mbuf
1694:    }
  • On INVARIANTS kernels (default GENERIC): the next loop iteration hits KASSERT(m != NULL) at line 1687 and panics with m_copydata: length > size of mbuf chain. (KKASSERT expands to an inline panic, so nm /boot/kernel/kernel | grep KASSERT == 0 is a false-negative β€” the check IS compiled in.)
  • On non-INVARIANTS kernels: count = 0, bcopy copies nothing, but the next iteration dereferences m = NULL β†’ page fault at m->m_len (offset 0 from NULL) β†’ kernel panic.

Either way: remote unauthenticated DoS triggered by a single malformed Command Complete HCI event packet from a paired BT controller.

The bug is reachable BEFORE the per-opcode handler switch at line 324 (which is where per-opcode KKASSERTs would catch short events); the generic unconditional m_copydata(...sizeof(rp)...) at line 313 runs regardless of opcode.

Why not testable on this guest

hci_event_command_compl is invoked from hci_event_handler (sys/netbt/hci_event.c:160) when an HCI event packet arrives from a registered BT controller. The dispatch path is:

ubt(4) USB driver β†’ ng_hci / hci_recv β†’ hci_event_handler β†’ hci_event_command_compl

The audit guest has: - No USB Bluetooth dongle (QEMU has no BT controller attached). - No virtual-HCI driver (DragonFly does not ship a vhci equivalent of BlueZ's hci_vhci that would let userland inject HCI events).

netbt.ko can be kldloaded but it has no event source, so the hci_event_handler path is dead code on this guest.

fix.diff wraps the unconditional m_copydata in a length check, zero-initializing rp first so a missing status byte is treated as "success" (the existing code's comment already admits the status is optional):

memset(&rp, 0, sizeof(rp));
if (m->m_pkthdr.len >= sizeof(rp))
    m_copydata(m, 0, sizeof(rp), &rp);

This matches the finding proposal (if(pkthdr.len>=sizeof(rp)) m_copydata) and adds the zero-init as a defense-in-depth (avoids uninitialized rp.status if the length check fails). Validated by:

  1. patch -p3 < fix.diff β†’ Hunk #1 succeeded at 309.
  2. make in /usr/src/sys/netbt β†’ builds cleanly (/usr/obj/usr/src/sys/netbt/netbt.ko, 105024 bytes, all 18 .o units compiled, no warnings).

Cannot load+test on the audit guest (no event source) so fix_status: not_testable. The patch is a clear defensive improvement and the source-trace shows it closes the only path to the unconditional m_copydata.

Kernel references (verified by source trace)

Threat model

Remote unauthenticated BT DoS. Requires a paired BT controller (which is the attacker in the threat model β€” a malicious BT device posing as a controller). The HCI event path is parsed before any authentication state is established, so any BT peer that can deliver a Command Complete event triggers the panic. No local-priv-esc chain (no write primitive). Not reachable on the audit guest.

PoC

A PoC for this bug would require either: 1. A malicious USB BT dongle (HW-in-the-loop), or 2. A virtual-HCI driver that lets userland inject HCI events (not shipped on DragonFly).

Neither is available on the audit guest. The bttest.c skeleton in this folder shows the conceptual trigger (open AF_BLUETOOTH / PF_BLUETOOTH socket, send a Command Complete event) but cannot run to completion here because no BT controller is attached. The fix validation is therefore compile-only + source-trace.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. hci_event_command_compl m_copydata on empty mbuf after m_adj. netbt not in GENERIC, no BT HW.