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)
PoC verification
Evidence pack
findings/poc/DF-5736 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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
fixedWith 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)
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.
No comments yet.