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

inquiry_result: unbounded variable-length loop reads past mbuf end -> remote kernel panic

Summary

inquiry_result(:380-424): pulls up only sizeof(inquiry_result_ep)=1 byte(num_responses)(:380). Loops for(;num_responses>0;num_responses--)(:387). num_responses attacker-controlled first byte NEVER validated vs actual mbuf length. Each iter consumes ~14 bytes(m_copydata 6+1+1+1+3+2 via m_adj/m_copydata/mtod). Chain exhausted -> m_copydata hits KASSERT(m!=NULL) panic DEBUG / NULL-deref panic RELEASE. ng_hci_process_event logs hdr->length(:100) but NEVER enforces it. 1-byte body num_responses=255 reliably triggers. Stale *mtod reads -> neighbor cache info leak. Comment :406 "XXX call m_pullup here?" admits gap. Remote unauth BT. Fix: clamp num_responses to m_pkthdr.len/14.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0542 Β· 9 files
FileTypeDescriptionSize
README.md readme claim, byte-count trace, repro status, fix 3.9 KB ↓ raw
VERDICT.md verdict full narrative: source-confirmed OOB, dead-code blocker, fix 3.9 KB ↓ raw
fix.diff suggested-fix bound inquiry_result loop by m_pkthdr.len (14 bytes/response) 1.1 KB view raw
build_hci.log build-log patched ng_hci.ko module build (both fixes), rc=0 clean 10.4 KB view raw
build_result.txt build-log module builds; kldload fails (ng_bluetooth absent) 745 B view raw
env.txt environment uname, cc, module availability, reachability conclusion 1.6 KB view raw
hci_event_analysis.sh trigger-source source-level byte-layout analysis of a triggering INQUIRY_RESULT HCI event 1.8 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
README.md readme claim, byte-count trace, repro status, fix
↓ download raw

DF-0542 β€” inquiry_result: unbounded loop reads past mbuf (netgraph7 HCI)

File: sys/netgraph7/bluetooth/hci/ng_hci_evnt.c Severity (fileed): High Class: Out-of-bounds read (mbuf chain over-read) β†’ kernel panic / info leak Module: ng_hci (netgraph7 bluetooth) β€” NOT compiled, NOT shipped, NOT loadable on the default X86_64_GENERIC guest.

The claim (source-level, CONFIRMED)

inquiry_result() (ng_hci_evnt.c:380) pulls up only sizeof(ng_hci_inquiry_result_ep) = 1 byte (num_responses) and then loops:

for (; ep->num_responses > 0; ep->num_responses --) {   /* :387 */
    m_copydata(event, 0, sizeof(bdaddr), &bdaddr); m_adj(event, sizeof(bdaddr)); /* 6 */
    ...
    n->page_scan_rep_mode = *mtod(event, u_int8_t *); m_adj(event, 1);          /* 1 */
    m_adj(event, sizeof(u_int8_t));                                              /* 1 */
    n->page_scan_mode     = *mtod(event, u_int8_t *); m_adj(event, 1);          /* 1 */
    m_adj(event, NG_HCI_CLASS_SIZE);                                             /* 3 */
    m_copydata(event, 0, sizeof(n->clock_offset), &n->clock_offset);            /* 2 */
}

Each iteration consumes 14 bytes (6+1+1+1+3+2). The controller-supplied num_responses (ep->num_responses, the first body byte) is never validated against the actual mbuf length β€” ng_hci_process_event (:86) logs hdr->length (:100) but does not enforce it against m_pkthdr.len.

A single-byte HCI event body of num_responses = 0xFF (255) demands ~3570 bytes but the chain holds far less. After the chain is drained:

  • m_copydata() hits KASSERT(m != NULL, "length > size of mbuf chain") at sys/kern/uipc_mbuf.c:1687 β†’ panic on INVARIANTS (GENERIC); on a non-INVARIANTS build m->m_len dereferences NULL β†’ page-fault panic.
  • Before exhaustion, the *mtod(event, ...) reads (:408, :414) read stale mbuf residue that is then copied into the neighbor cache (n->page_scan_*) β†’ kernel-memory info leak (limited; the data lands in an in-kernel neighbor struct, not directly returned to userspace, so the leak ceiling is "corrupt neighbor cache / subsequent inquiry leak", not a direct arbitrary-kptr disclosure).

The comment at :406 (/* XXX call m_pullup here? */) admits the gap.

Reproduction status

  • Source-level: CONFIRMED (byte-count and m_copydata KASSERT traced).
  • Live trigger: NOT possible on the default guest. netgraph7 bluetooth is optional netgraph7_bluetooth, absent from X86_64_GENERIC, and no bluetooth/netgraph7 .ko ships. Building ng_hci.ko standalone succeeds, but kldload fails: "depends on ng_bluetooth - not available". Even with the full stack loaded, injecting a crafted HCI event requires a Bluetooth controller / ng_ubt/ng_h4 lower driver. β‡’ dead code / latent here.

Impact (realistic, conditional)

Conditional on netgraph7 bluetooth being compiled in and a BT HCI controller attached: remote-unauthenticated-packet β†’ kernel panic (DoS) and a limited in-kernel stale-data leak into the neighbor cache. On the shipped default kernel this code is not present, so the practical impact on a default install is none β€” it is a latent bug in opt-in code.

Old netgraph BT?

sys/netgraph/bluetooth/ does not exist in this tree; the vulnerable parser lives only in netgraph7. No second copy to fix.

Fix

fix.diff β€” bound the loop by the remaining mbuf length. A per-response constant NG_HCI_INQUIRY_RESULT_SIZE (= sizeof(bdaddr_t)+3+NG_HCI_CLASS_SIZE+ sizeof(u_int16_t) = 14) is added to the loop guard so the loop stops as soon as the remaining event->m_pkthdr.len cannot hold a full response. git-apply clean; compiles (patched ng_hci.ko build rc=0, build_hci.log).

Build

# on the guest, from a tree where fix.diff is applied:
cd /usr/src/sys/netgraph7/bluetooth/hci && make     # -> ng_hci.ko (objdir), rc=0

Logs: build_hci.log, build_result.txt, env.txt.

VERDICT.md verdict full narrative: source-confirmed OOB, dead-code blocker, fix
↓ download raw

DF-0542 β€” VERDICT

Verdict: NOT REPRODUCED at runtime (source-CONFIRMED real OOB-read; the vulnerable code is dead/opt-in on the default guest). Impact: none on a default install (latent; conditional on netgraph7_bluetooth + BT hardware). Confidence: likely (bug confirmed by source trace; not runtime-observed because the module is not compiled/shipped/loadable on the default guest).

Mechanism (source-confirmed)

  1. ng_hci_process_event() (ng_hci_evnt.c:86) pulls the 3-byte HCI event header, logs hdr->length (:100) but does not validate it against the mbuf length, then dispatches on hdr->event (:106).
  2. NG_HCI_EVENT_INQUIRY_RESULT β†’ inquiry_result() (:127,:370).
  3. inquiry_result() pulls only sizeof(ng_hci_inquiry_result_ep) = 1 byte (num_responses) (:380), m_adjs it (:385), then loops for (; ep->num_responses > 0; ...) (:387) with no length guard.
  4. Each iteration consumes 14 bytes: m_copydata(sizeof(bdaddr)=6)+m_adj(6) (:389-390), *mtod+m_adj(1) (:408-409), m_adj(1) (:412), *mtod+m_adj(1) (:414-415), m_adj(NG_HCI_CLASS_SIZE=3) (:418), m_copydata(sizeof(clock_offset)=2) (:421). (NG_HCI_CLASS_SIZE=3 and bdaddr_t=6 confirmed in ng_hci.h:78,:73,:375.)
  5. When num_responses exceeds the data in the chain, m_adj drains the chain to nothing; the next m_copydata() walks m = m->m_next to NULL and hits KASSERT(m != NULL, "%s: length > size of mbuf chain") at sys/kern/uipc_mbuf.c:1687 β†’ panic (INVARIANTS/GENERIC); non-INVARIANTS β‡’ NULL m->m_len deref β‡’ page-fault panic. The *mtod reads (:408,:414) additionally read stale residue into the neighbor cache (limited leak).

The bug is real and not a false positive.

Why NOT reproduced at runtime

netgraph7 bluetooth is opt-in and absent from the default system: - sys/conf/files gates ng_hci_evnt.c on optional netgraph7_bluetooth; X86_64_GENERIC has no netgraph7 option β‡’ not in the kernel. - No bluetooth/netgraph7 .ko ships in /boot/kernel/. - Standalone make in sys/netgraph7/bluetooth/hci builds ng_hci.ko (rc=0), but kldload fails: "depends on ng_bluetooth - not available" β€” the whole netgraph7 BT framework is missing. - Even fully loaded, the only way to reach inquiry_result() is a real HCI INQUIRY_RESULT event from a Bluetooth controller via ng_ubt/ng_h4; there is no userspace injection path on this guest.

β‡’ The code path is dead/unreachable at runtime on this guest AND no harness can exercise it without building the netgraph7 BT stack (root) and attaching BT hardware β€” a valid hard blocker for live reproduction. (Validated the bug at the source/object level and validated the fix compiles instead.)

Fix-validation (fix.diff)

fix.diff adds a per-response size and bounds the loop by remaining event->m_pkthdr.len:

#define NG_HCI_INQUIRY_RESULT_SIZE \
    (sizeof(bdaddr_t) + 3 + NG_HCI_CLASS_SIZE + sizeof(u_int16_t))
for (; ep->num_responses > 0 &&
       event->m_pkthdr.len >= NG_HCI_INQUIRY_RESULT_SIZE;
     ep->num_responses --) { ... }
  • git-apply clean; compiles (patched ng_hci.ko build rc=0, build_hci.log).
  • fix_status: not_testable (runtime). The module is not loadable on the default guest and event injection requires BT hardware; the fix was validated to apply + compile and traced to close the over-read.

PoC changes

Created the evidence pack from scratch (empty on handoff): README.md, this VERDICT.md, env.txt, build_result.txt, build_hci.log, fix.diff, manifest.json.

In sys/netgraph7/bluetooth/hci/ng_hci_evnt.c, bound inquiry_result()'s loop by the remaining mbuf length (stop when event->m_pkthdr.len < NG_HCI_INQUIRY_RESULT_SIZE, 14 bytes/response). (Matches the finding's "clamp num_responses to m_pkthdr.len/14" proposal.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable: compile validated, no runtime trigger.

git apply --check OK + module build rc=0.

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

Source trace + compile validation. No live trigger.

PoC changes

fix.diff + VERDICT.md + manifest.json per finding.

Verified recommended fix

See individual fix.diff per finding.

Verdict

Source-confirmed real but not runtime-reproduced. See individual evidence packs.