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

sco_input uses while-loop (not if) for receive buffer check β€” oversized SCO packet hangs protocol thread in infinite loop

Summary

sco_socket.c:221-222: while(m->m_pkthdr.len > sbspace(&so->so_rcv)) sbdroprecord(&so->so_rcv.sb). If inbound SCO packet exceeds sco_recvspace (4096 :81) and buffer is empty/under-full, sbdroprecord on empty buffer is no-op, sbspace never grows, loop never exits. L2CAP l2cap_socket.c:231 and RFCOMM rfcomm_socket.c:241 correctly use if (drop packet). Only SCO has broken while. Triggered by malicious paired BT peer sending oversized SCO data or malicious USB BT dongle injecting via hci_sco_recv->sco_input. hci_sco_recv (hci_link.c:827) strips 3-byte header and passes to sco_input with NO upper-bound check against sco_recvspace. Hangs BT protocol thread indefinitely. Requires root HCI socket or malicious BT hardware/peer, hence Medium not High.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0710 Β· 20 files
FileTypeDescriptionSize
README.md readme build/run/expected + reachability notes 2.2 KB ↓ raw
VERDICT.md verdict full narrative: source trace, harness, machine-code proof, fix, threat model 9.9 KB ↓ raw
sco_input_logic.c trigger-source deterministic code-level harness replicating exact kernel primitives (sbspace/sbdroprecord/sco_recvspace); buggy-while vs fixed-if 7.4 KB view raw
sco_input_hang.c trigger-source original live PoC (raw HCI socket); documents live unreachability on guest 2.0 KB view raw
build.sh build-script builds both the logic harness and the live PoC 508 B view raw
run.sh run-script runs the logic harness + live-PoC-unreachable demonstration 840 B view raw
build.log build-log harness compile output 13 B view raw
run.log run-log decisive logic-harness run (full output) 1.5 KB view raw
run.2.log run-log stress-test run 2 (identical) 255 B view raw
run.3.log run-log stress-test run 3 (identical) 255 B view raw
live_poc_unreachable.log run-log live PoC: 'socket: Protocol not supported' (no BT) 47 B view raw
env.txt environment uname, gcc, BT reachability facts 410 B view raw
sco_input_baseline.disasm disassembly baseline netbt.ko sco_input: backward branch 5b2: jg 581 = the infinite loop 3.4 KB ↓ download
sco_input_patched.disasm disassembly patched netbt.ko sco_input: forward branch 635: jg 660 only, NO backward branch 5.5 KB ↓ download
netbt_baseline_disasm.txt disassembly full baseline netbt.ko disassembly 650.4 KB ↓ download
netbt_patched_disasm.txt disassembly full patched netbt.ko disassembly 649.6 KB ↓ download
fix_build.log build-log netbt.ko built before/after fix (full, both rc=0 with -Werror, with sco_input disasm) 59.1 KB view raw
fix.diff suggested-fix git-apply-able: while -> if { m_freem; return } matching l2cap/rfcomm 781 B 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 build/run/expected + reachability notes
↓ download raw

DF-0710 PoC: sco_input infinite loop (while vs if)

Bug

sys/netbt/sco_socket.c:221-222 uses a while loop where the sibling BT socket implementations use a one-shot if { drop; return; } (sys/netbt/l2cap_socket.c:231, sys/netbt/rfcomm_socket.c:241):

while (m->m_pkthdr.len > sbspace(&so->so_rcv))
    sbdroprecord(&so->so_rcv.sb);

If an inbound SCO packet exceeds sco_recvspace (4096, sco_socket.c:81) and the receive buffer cannot free enough room (including the empty-buffer case), sbdroprecord on an empty buffer is a no-op (uipc_sockbuf.c:524, if (m)), sbspace never grows, and the loop never exits. The Bluetooth protocol thread spins at 100% CPU forever. Pure DoS β€” nothing is corrupted.

Reachability on the audit guest β€” UNREACHABLE LIVE

sco_input is driven only by hci_sco_recv (sys/netbt/hci_link.c:867), which fires from a real Bluetooth controller's RX path. The guest has: - no options BLUETOOTH in X86_64_GENERIC (so sco_input is not in the running kernel), - netbt.ko present but not loaded, - no Bluetooth controller (no PCI/USB BT device).

The original live PoC sco_input_hang therefore fails with socket: Protocol not supported. The bug is reproduced instead by the deterministic code-level harness sco_input_logic.c, which models the exact kernel primitives (sbspace macro, sbdroprecord no-op-on-empty, sco_recvspace=4096). See VERDICT.md for the full source trace and the machine-code (netbt.ko before/after) confirmation.

Reproduce

./build.sh && ./run.sh

Expected

  • sco_input_logic: 4 of 6 oversize cases print BUGGY(while): iters=100000 *** WOULD LOOP FOREVER *** while FIXED (if): iters=1 (dropped packet); the 2 control cases (small / exactly-fitting packet) terminate on both. Exit 0 = bug demonstrated.
  • sco_input_hang (live): socket: Protocol not supported (documents live unreachability).

Fix

fix.diff: change while to if { m_freem(m); return; } (matches l2cap/rfcomm). Validated by building netbt.ko before/after: baseline sco_input has a backward branch 5b2: jg 581 (the loop); patched has only a forward branch 635: jg 660 (one-shot drop) and no backward branch. Both compile clean with -Werror.

VERDICT.md verdict full narrative: source trace, harness, machine-code proof, fix, threat model
↓ download raw

DF-0710 β€” VERDICT

Verdict: REPRODUCED (code-level; live path unreachable on guest) Impact: dos (kernel thread infinite-loop / hang in sco_input) Fix: VALIDATED at compile + machine-code + logic level (not_testable live)

1. The bug (confirmed in source)

sys/netbt/sco_socket.c:210-228 β€” sco_input(arg, m):

while (m->m_pkthdr.len > sbspace(&so->so_rcv))      /* line 221 */
    sbdroprecord(&so->so_rcv.sb);                   /* line 222 */
...
sbappendrecord(&so->so_rcv.sb, m);
sorwakeup(so);

A while is used where the two sibling Bluetooth socket implementations use a one-shot if { drop; return; }:

Only SCO has the broken while. The consequence, proven by tracing each kernel primitive:

  • sbspace (sys/netbt/bluetooth.h:150-152) = (long) imin((int)(ssb_hiwat - ssb_cc), (int)(ssb_mbmax - ssb_mbcnt)). For an empty SCO receive buffer this equals sco_recvspace = 4096 (sys/netbt/sco_socket.c:81, set via soreserve in sco_sattach:282).
  • sbdroprecord (sys/kern/uipc_sockbuf.c:517-535) is a no-op when the buffer is empty β€” it is guarded by if (m) at line 524 (m = sb->sb_mb; if (m) {...}). On an empty buffer it frees nothing, so ssb_cc/ssb_mbcnt and therefore sbspace do not change.

Therefore: an inbound SCO packet with m_pkthdr.len > 4096 arriving at a socket whose receive buffer cannot free enough room (including the trivially empty buffer) makes the loop condition permanently true: sbdroprecord no-ops, sbspace never grows, and the loop never exits. The Bluetooth protocol thread spins at 100% CPU forever. This is a deterministic, unbounded kernel hang.

The packet does not need to be enormous β€” a single byte over the hi-water mark (4097) is sufficient (harness case 2). The bug is a pure control-flow defect; nothing is corrupted (no write primitive), so there is no escalation chain.

2. Reachability on this guest β€” UNREACHABLE LIVE

sco_input is reachable only via the BT controller input path:

BT controller RX β†’ hci_sco_recv(m, unit) (sys/netbt/hci_link.c:828) strips the 3-byte SCO header (line 839) and calls (*link->hl_sco->sp_proto->input)(...) (sys/netbt/hci_link.c:867), which is the sco_input callback registered in sco_proto (sco_socket.c:70-78, .input = sco_input). hci_sco_recv performs no upper-bound check against sco_recvspace (only DIAGNOSTIC-gated self-consistency checks at lines 841-854), so an oversized-but-self-consistent packet is forwarded unchanged.

Confirmed on the audit guest (6.5-DEVELOPMENT #0):

Check Result
options BLUETOOTH in X86_64_GENERIC absent (count 0)
sco_input symbols in /boot/kernel/kernel 0 (not compiled into kernel)
netbt kernel module loaded (kldstat) none loaded
netbt.ko present in /boot/kernel/ yes (stock, not loaded)
Bluetooth controller (pciconf) no BT PCI/USB device

So the vulnerable routine is not present in the running kernel AND there is no hardware to drive hci_sco_recv even if netbt.ko were loaded. The live PoC sco_input_hang consequently fails with socket: Protocol not supported (captured in live_poc_unreachable.log).

Because the live path is unreachable, the bug is reproduced by a deterministic code-level harness that replicates the exact in-kernel primitives (sbspace macro, sbdroprecord no-op-on-empty semantics, sco_recvspace=4096). This is the accepted path for latent / hardware-gated findings.

3. Reproduction harness β€” sco_input_logic.c

sco_input_logic.c faithfully models the sockbuf as a record queue with ssb_cc/ssb_mbcnt accounting and implements both sco_input_buggy (the while) and sco_input_fixed (the if+drop), with a 100000-iteration cap to detect the infinite loop without actually hanging. Result (3 consistent runs):

[case 1] empty buf + oversize pkt (8192 > 4096)
    BUGGY(while): iters=100000 *** WOULD LOOP FOREVER *** (appended=0)
    FIXED (if)  : iters=1 (dropped packet)
[case 2] empty buf + pkt 4097 (> hiwat 4096 by 1)
    BUGGY(while): iters=100000 *** WOULD LOOP FOREVER *** (appended=0)
    FIXED (if)  : iters=1 (dropped packet)
[case 3] half-full buf (2048) + oversize pkt 8192
    BUGGY(while): iters=100000 *** WOULD LOOP FOREVER *** (appended=0)
    FIXED (if)  : iters=1 (dropped packet)
[case 4] 3 records (1024 each) + oversize pkt 8192
    BUGGY(while): iters=100000 *** WOULD LOOP FOREVER *** (appended=0)
    FIXED (if)  : iters=1 (dropped packet)
[case 5] empty buf + small pkt 100 (control)
    BUGGY(while): iters=0 (terminated) (appended=1)
    FIXED (if)  : iters=0 (appended)
[case 6] empty buf + pkt 4096 (== hiwat, fits)
    BUGGY(while): iters=0 (terminated) (appended=1)
    FIXED (if)  : iters=0 (appended)

SUMMARY: BUGGY(while) infinite-loops: 4 / 6 ; FIXED(if) <=1 iter on ALL: YES

Cases 5-6 are controls: normally-sized packets terminate on both paths, so the fix does not regress legitimate traffic. Cases 1-4 (any packet exceeding sco_recvspace, with empty through multi-record buffers) all infinite-loop in the buggy version and are cleanly dropped by the fix.

4. Machine-code confirmation (netbt.ko, built before/after fix)

Because sco_socket.c is optional bluetooth and options BLUETOOTH is absent from X86_64_GENERIC, the file is not compiled into the default kernel; the correct build target is the netbt.ko module (sys/netbt/Makefile, bsd.kmod.mk). Both builds use -Werror.

sco_input disassembly of the baseline netbt.ko (offset 0x540):

57f:  7e 33           jle    5b4 <sco_input+0x74>     ; if fits, go append
581:  ...             mov %rbx,%rdi
584:  e8 .. .. .. ..  callq  sbdroprecord             ; drop a record
...
5b2:  7f cd           jg     581 <sco_input+0x41>     ; *** BACKWARD BRANCH = loop ***
5b4:  ...             (append + sorwakeup)

The 7f cd jg 581 at 0x5b2 is the compiled while β€” a backward conditional branch wrapping the sbdroprecord call. When sbdroprecord is a no-op this branch is taken unconditionally and forever.

sco_input disassembly of the patched netbt.ko (offset 0x610):

635:  7f 29           jg     660 <sco_input+0x50>     ; one-shot if -> drop path
637:  ...             push/save
64b:  e8 .. .. .. ..  callq  sbappendrecord           ; normal append
65a:  e9 .. .. .. ..  jmpq   sorwakeup (tail)
660:  48 89 f7        mov    %rsi,%rdi                ; drop path: arg = m
663:  e9 .. .. .. ..  jmpq   m_freem (tail) + return

There is no backward branch in the patched sco_input. The loop is structurally impossible: a single forward jg 660 redirects to the m_freem drop path. Module size changes (105024 -> 104960 bytes) confirming the code differ. This is conclusive proof the fix eliminates the loop at the machine-code level.

5. The fix (fix.diff)

Minimal, root-cause fix matching the sibling implementations exactly:

-   while (m->m_pkthdr.len > sbspace(&so->so_rcv))
-       sbdroprecord(&so->so_rcv.sb);
+   if (m->m_pkthdr.len > sbspace(&so->so_rcv)) {
+       DPRINTF("%s: packet (%d bytes) dropped (socket buffer full)\n",
+           __func__, m->m_pkthdr.len);
+       m_freem(m);
+       return;
+   }

The comment above is updated to reflect that the packet is dropped (the old "dump data until the latest one will fit" wording described the buggy intent). git apply --check passes against the read-only sys/ tree.

6. Impact / threat model

  • Effect: unbounded kernel-thread CPU spin; the Bluetooth protocol thread is wedged and the BT subsystem becomes unresponsive. Pure DoS β€” no memory is corrupted (sbdroprecord is a no-op on an empty buffer), so no privilege escalation primitive exists. This is a logic/DoS bug, not memory corruption.
  • Trigger preconditions (realistic): (a) a malicious paired Bluetooth peer that sends an oversized SCO data packet, or (b) a malicious/compromised USB Bluetooth dongle injecting via hci_sco_recv, or (c) a root raw-HCI socket with an active SCO connection. No unprivileged-local-only path exists without BT hardware. Severity Medium is appropriate (local/peer DoS requiring BT hardware or root HCI; no privesc, no remote-without-BT).

7. Files in this evidence pack

File Purpose
sco_input_logic.c deterministic code-level harness (buggy vs fixed logic)
sco_input_hang.c original (live) PoC β€” proves live unreachability on guest
build.sh / run.sh exact reproducible build/run
build.log harness build output
run.log, run.2.log, run.3.log 3 stress-test runs (identical results)
live_poc_unreachable.log live PoC socket: Protocol not supported
env.txt guest uname, gcc, BT reachability facts
sco_input_baseline.disasm baseline sco_input: backward branch jg 581
sco_input_patched.disasm patched sco_input: forward branch jg 660, no loop
fix_build.log netbt.ko built before/after fix (full, with disasm)
fix.diff git-apply-able one-line fix (while -> if + drop)
manifest.json machine-readable catalog

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

FIXED at compile + machine-code + logic level; live-run not_testable. The task explicitly permits 'netbt module' / 'compile check' / 'harness before-after' for this finding given BT unreachability, and all three were performed: (1) git apply --check passes against the read-only sys/ tree; (2) netbt.ko (the only build unit that compiles sco_socket.c, since the default kernel has no options BLUETOOTH) builds clean with -Werror both UNPATCHED (rc=0) and PATCHED (rc=0); (3) objdump of the two modules proves the loop is structurally eliminated -- baseline sco_input has a backward branch 5b2: 7f cd jg 581 wrapping callq sbdroprecord (the compiled while-loop), patched sco_input has ONLY a forward 635: 7f 29 jg 660 to the m_freem drop path with NO backward branch (module size 105024->104960); (4) the logic harness shows before=100000-iter-loop on 4/6 oversize cases vs after=1-iter-drop on all. fix_status is not_testable rather than fixed ONLY because the live PoC cannot run on this guest (no BT hardware + sco_input not in default kernel); the bad behavior (infinite loop) is otherwise proven gone at the machine-code level. Recommend the maintainer also boot a BLUETOOTH-enabled kernel on real BT hardware to confirm no behavioral regression on legitimate in-bound SCO traffic, but the one-line while->if change is identical to two already-shipping sibling implementations, so regression risk is minimal.

BASELINE netbt.ko sco_input @0x540: `57f: jle 5b4; 584: callq sbdroprecord; 5b2: 7f cd jg 581 <sco_input+0x41>` (BACKWARD branch = infinite loop). PATCHED netbt.ko sco_input @0x610: `635: 7f 29 jg 660 <sco_input+0x50>` (FORWARD, one-shot if -> drop); 660: mov %rsi,%rdi; jmpq m_freem. NO backward branch in patched sco_input. Both builds rc=0 -Werror. Harness: buggy iters=100000 (LOOP FOREVER) on 4/6 cases; fixed iters=1 (dropped) on all; controls (small/exactly-fitting pkt) iters=0 appended on both. Live PoC: 'socket: Protocol not supported' (unreachable on guest).
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 (netbt.ko module validated against this kernel's headers/obj; not booted as a single-fix kernel because X86_64_GENERIC has no options BLUETOOTH, so sco_input.c is not part of the default kernel build -- loading netbt.ko would still not make sco_input reachable without a BT controller)

Confirmed kernel references

Detail

Exploit chain

none (non-corruption DoS). The defect is a pure control-flow/logic bug: sbdroprecord on an empty buffer is a true no-op (it frees nothing and changes no accounting), so the only effect is an unbounded kernel-thread CPU spin. There is NO write primitive, NO memory corruption, NO UAF/double-free/type-confusion, and therefore NO escalation path to uid=0. Realistic impact ceiling: permanent denial of service of the Bluetooth subsystem (protocol thread wedged). Trigger requires either a malicious paired BT peer sending an oversized SCO packet, a malicious/compromised USB BT dongle injecting via hci_sco_recv (hci_link.c:867, which has no upper-bound check against sco_recvspace), or a root raw-HCI socket with an active SCO connection -- no unprivileged-local-only path exists without BT hardware. Severity Medium is appropriate.

Evidence (decisive lines)

Harness (3 identical runs): [case 1] empty buf + oversize pkt 8192: BUGGY(while) iters=100000 *** WOULD LOOP FOREVER *** ; FIXED(if) iters=1 (dropped packet). [case 2] pkt 4097 (one byte over hiwat): BUGGY iters=100000 LOOP FOREVER ; FIXED drops. [case 4] 3 records + oversize pkt: BUGGY loops, FIXED drops. SUMMARY: BUGGY(while) infinite-loops 4/6 cases; FIXED(if) <=1 iter on ALL. Live PoC: 'socket: Protocol not supported'. Machine-code: baseline sco_input @0x540 `5b2: 7f cd jg 581 <sco_input+0x41>` (backward branch = loop) wrapping `584: callq sbdroprecord`; patched sco_input @0x610 only `635: 7f 29 jg 660` (forward to m_freem drop), no backward branch. netbt.ko size 105024(baseline)->104960(patched); both build rc=0 with -Werror.

PoC changes

Added sco_input_logic.c -- a deterministic code-level harness (the live path needs a Bluetooth controller absent on the guest) that faithfully replicates the exact kernel primitives: the sbspace() macro from bluetooth.h:150-152, sbdroprecord() no-op-on-empty semantics from uipc_sockbuf.c:517-535, and sco_recvspace=4096 from sco_socket.c:81. It implements both sco_input_buggy (the while) and sco_input_fixed (the if+drop) with a 100000-iter loop-cap detector, across 6 cases (4 oversize, 2 controls). Also added fix.diff (while->if+drop matching l2cap/rfcomm), build.sh, run.sh, VERDICT.md, manifest.json, and captured full before/after netbt.ko build logs + sco_input disassemblies. The original sco_input_hang.c live PoC is retained to document live unreachability.

Verified recommended fix

In sys/netbt/sco_socket.c:221-222 change while (m->m_pkthdr.len > sbspace(&so->so_rcv)) sbdroprecord(&so->so_rcv.sb); to if (m->m_pkthdr.len > sbspace(&so->so_rcv)) { m_freem(m); return; } (optionally with a DPRINTF), matching l2cap_socket.c:231 and rfcomm_socket.c:241 exactly. This eliminates the infinite loop for oversized packets while preserving normal in-bound delivery. The full git-apply-able diff (also updates the stale comment) is in findings/poc/DF-0710/fix.diff; it supersedes/matches the finding markdown's recommended approach (the finding markdown proposed no diff but described the same while->if intent). Validated: git apply --check passes; netbt.ko compiles clean with -Werror before and after; baseline sco_input has backward branch 5b2: jg 581 (loop), patched has only forward 635: jg 660 (drop) with no backward branch.

Verdict

REPRODUCED at code level (live path unreachable on guest). sys/netbt/sco_socket.c:221 uses while (m->m_pkthdr.len > sbspace(&so->so_rcv)) sbdroprecord(...) where the sibling BT socket implementations (l2cap_socket.c:231, rfcomm_socket.c:241) use a one-shot if { m_freem; return; }. Because sbdroprecord is a NO-OP on an empty buffer (uipc_sockbuf.c:524, guarded by if (m)) and sbspace (bluetooth.h:150-152) equals sco_recvspace=4096 when empty, any SCO packet larger than 4096 bytes makes the condition permanently true and the loop never exits -> the Bluetooth protocol thread spins at 100% CPU forever. Confirmed by (a) line-by-line source trace of all three primitives, (b) a deterministic harness replicating the exact kernel logic (4/6 oversize cases hit the 100000-iter loop cap on the buggy path, 0 on the fixed path; 2 control cases pass on both, across 3 identical runs), and (c) machine-code: baseline netbt.ko sco_input has a backward branch 5b2: 7f cd jg 581 wrapping callq sbdroprecord = the compiled while-loop; patched netbt.ko has only a forward 635: 7f 29 jg 660 to the m_freem drop path with NO backward branch. The path is unreachable live on the audit guest (no options BLUETOOTH in X86_64_GENERIC, netbt.ko not loaded, no Bluetooth controller; the live PoC fails with 'socket: Protocol not supported'), so the harness is the accepted deterministic reproduction.