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

Inquiry/RSSI result handlers allocate hci_memo records without bound (lookup-lazy reclamation): remotely-seedable kernel memory exhaustion

Summary

hci_memo_new allocates ~48B per unique bdaddr - a value controlled by the reporting remote - with no cap and lazy-only expiry (reclaimed inside find-lookups after 600s, no sweep): guest-verified monotonic growth 0->1436->2848->3184 records across 3 floods of 2048 unique addresses. RF-side spoofed bdaddrs or transport injection; the only limiter is input backpressure (~70-90% still accepted). sysctl-tunable cap fix validated (growth stops at exactly 1024).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5736 Β· 15 files
FileTypeDescriptionSize
harness.c β€” 15.4 KB view raw
hci_stub.c β€” 2.5 KB view raw
Makefile β€” 535 B ↓ download
build.sh β€” 83 B view raw
run.sh β€” 175 B view raw
build.log β€” 463 B view raw
run.log β€” 900 B view raw
fix.diff β€” 2.7 KB view raw
fix_run.log β€” 482 B view raw
fix_build.log β€” 7.3 KB view raw
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
verdict.json β€” 2.6 KB view raw
manifest.json β€” 808 B view raw
env.txt β€” 667 B view raw

DF-5736 β€” Inquiry/RSSI result events allocate hci_memo records without bound

File: sys/netbt/hci_event.c (pass 2) + sys/netbt/hci_misc.c Severity: Medium β€” CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L (CWE-770) Verdict: REPRODUCED (monotonic unbounded kernel-memory growth from events).

Root cause

hci_event_inquiry_result() (sys/netbt/hci_event.c:440-470) and hci_event_rssi_result() (sys/netbt/hci_event.c:477-507) call hci_memo_new() for every response carried in the event (sys/netbt/hci_event.c:463, :500), keyed by the response's bdaddr β€” a value fully controlled by the reporting remote device / controller. hci_memo_new() (sys/netbt/hci_misc.c:145-173) allocates a new struct hci_memo (~48 bytes) per unique address with no cap, and expired memos are reaped only inside hci_memo_find() lookups after hci_memo_expiry (600 s) β€” there is no background sweep. An inquiring host therefore accumulates kernel memory linearly with the number of (spoofable) unique responder addresses, for at least 10 minutes each, unbounded.

A hostile remote Bluetooth device can manufacture inquiry responses with random spoofed bdaddrs (inquiry response spoofing over RF); a hostile controller/transport-injector can do the same at HCI-USB speed (bounded only by the 20-event input queue backpressure).

Build & run (inside the guest, as root)

cd /root/hcih && sh build.sh
kldload ./hci_event_harness.ko
sysctl hw.hcih_setup=1
sysctl hw.hcih_fire_memo=1     # 128 events x 16 unique bdaddrs (2048 responses)

Expected result (baseline)

[harness] memos before: 0
[harness] memos after: 1436 (allocated 1436 for 2048 responses, ...)
[harness] memos before: 1436
[harness] memos after: 2848 ...
[harness] memos after: 3184 ...      <- monotonic growth, nothing reclaimed

(vmstat -m: Bluetooth 150K after three bursts; ~612 responses were dropped by the production hci_eventq_max=20 backpressure β€” the growth rate is limited only by that drain race.)

Fix validation

fix.diff adds a per-unit cap (hci_memo_max, default 1024, sysctl-tunable) counted in hci_memo_new(). On the patched build: three consecutive floods β†’ memos after: 1024, then 1024 (allocated 0 ...) twice β€” growth stops at the cap; guest alive.

VERDICT.md
↓ download raw

DF-5736 VERDICT β€” REPRODUCED (unbounded kernel memory growth from inquiry/RSSI events)

Bottom line

Reproduced, root-caused, fix validated. The inquiry-result handlers in sys/netbt/hci_event.c (hci_event_inquiry_result:440-470, hci_event_rssi_result:477-507) create one hci_memo per response via hci_memo_new() (sys/netbt/hci_event.c:463, :500). hci_memo_new() (sys/netbt/hci_misc.c:145-173) has no allocation cap, and reclamation of expired entries happens only inside hci_memo_find() lookups (sys/netbt/hci_misc.c:124-128) β€” no background sweep exists. Guest measurement: 384 injected events (2048 unique bdaddrs, some dropped by the production hci_eventq_max=20 queue backpressure) grew the per-unit memo list monotonically 0 β†’ 1436 β†’ 2848 β†’ 3184 records (~48 B each), nothing reclaimed (vmstat -m: Bluetooth 150 K).

Threat model

  • Remote (RF): any Bluetooth device in range can answer inquiries with spoofed source bdaddrs; each unique address mints a memo. Rate is bounded by inquiry response timing, but sustained inquiry sessions accumulate for β‰₯600 s per record with no ceiling.
  • Controller/transport injection:HCI-USB speed, bounded only by the 20-event input queue (which the guest run shows passes ~70-90% under a moderate flood).

Impact: kernel memory exhaustion (DoS) β€” slow but unbounded and remotely seedable; distinct from DF-0559 (which concerns the KKASSERT length guards on the parse of these same events, not the allocation policy on well-formed packets).

Fix validation

fix.diff caps the per-unit memo cache at hci_memo_max (default 1024, sysctl-tunable) counted in hci_memo_new() before allocating. On the patched build, the same three-burst flood produced: memos after: 1024, then 1024 (allocated 0), 1024 (allocated 0) β€” growth stopped exactly at the cap, guest alive, subsequent triggers (DF-5734, DF-5735 fixed runs) still functional on the same boot.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

With the cap hunk applied, the same three-burst flood stops at exactly 1024 memos ('allocated 0' on subsequent floods); guest alive and other triggers functional on the same boot.

fix_run.log (F3 FIXED RUN block: memos after: 1024 / 1024 / 1024, allocated 0 on repeats)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'memos before/after' sequence 0->1436->2848->3184 across three fires + vmstat -m Bluetooth 150K; fix_run.log: capped at 1024 with 'allocated 0' on repeat floods

PoC changes

none beyond the shared-rig fixes (copyback error checks)

Verified recommended fix

cap the per-unit memo cache (hci_memo_max sysctl, default 1024) in hci_memo_new()

Verdict

hci_event_inquiry_result/hci_event_rssi_result (sys/netbt/hci_event.c:463,500) call hci_memo_new() for every response with no allocation cap; hci_memo_new (sys/netbt/hci_misc.c:145) allocates ~48B per unique bdaddr and reclamation is lookup-lazy (hci_misc.c:124, 600s expiry, no sweep). Guest: 3 bursts of 2048 unique-address responses grew the list monotonically 0->1436->2848->3184 memos (vmstat -m Bluetooth 150K), ~70-90% of responses accepted past the hci_eventq_max=20 backpressure. Remotely seedable via inquiry-response spoofing; controller injection runs at USB speed. Fix (per-unit cap hci_memo_max=1024) validated: growth stops at exactly 1024, 0 allocated on subsequent floods.