โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0281

Remote kernel panic: divide-by-zero via PN MCC command with mtu=0

Summary

ng_btsocket_rfcomm_set_pn(:3019) copies peer PN mtu into pcb->mtu with NO validation (can be 0). ng_btsocket_rfcomm_send_credits(:3283) ssb_space/pcb->mtu unconditional division. Peer: PN mtu=0 + one UIH data frame -> divide-by-zero #DE -> kernel panic. Deterministic single-shot no auth required. Division executes before mtu oversize check(:2438).

PoC verification

Evidence pack

findings/poc/DF-0281 ยท 11 files
FileTypeDescriptionSize
divzero_proof.c trigger-source userspace replication of the exact kernel arithmetic on the confirmed divide-by-zero path (annotated with kernel source lines); NOT a kernel trigger 8.5 KB view raw
build.sh build-script cc -O2 -Wall divzero_proof.c (and -DFIX_MTU guarded variant) 502 B view raw
run.sh run-script runs vulnerable path (expect SIGFPE) then guarded path (expect clean exit) 454 B view raw
build.log build-log full untrimmed cc output, both binaries, exit 0 267 B view raw
run.log run-log decisive run: vulnerable path reaches SIGFPE (#DE), guarded path completes cleanly 1.6 KB view raw
run.stress.log run-log 3x repeat of vulnerable path -- deterministic SIGFPE every run 2.4 KB view raw
env.txt environment uname, cc, kldstat, kldload ng_btsocket (ENOENT), module search (empty), kernel-symbol count (0), AF_BLUETOOTH socket() = -1, no /usr/src/sys -- reachability evidence 933 B view raw
fix.diff suggested-fix git-apply-able: clamp mtu!=0 in set_pn (root cause) + guard divisor in send_credits (defense-in-depth); validated git apply --check rc=0 1.1 KB view raw
VERDICT.md verdict full narrative: line-by-line static proof of the divide-by-zero + runtime-reachability analysis (module absent, needs bluetooth HW) 8.1 KB โ†“ raw
README.md readme build/run/expected + how to reproduce the kernel panic on HW-equipped host + reachability caveat 3.2 KB โ†“ raw
manifest.json manifest this catalog 3.6 KB view raw
README.md readme build/run/expected + how to reproduce the kernel panic on HW-equipped host + reachability caveat
โ†“ download raw

DF-0281 โ€” PoC evidence pack

Finding: ng_btsocket_rfcomm_set_pn copies a peer-supplied PN mtu into pcb->mtu with no validation; ng_btsocket_rfcomm_send_credits later divides ssb_space / pcb->mtu unconditionally. A peer PN with mtu=0 plus one UIH data frame โ†’ divide-by-zero #DE โ†’ kernel panic. CWE-369 (divide-by-zero), severity High.

What this pack contains

  • divzero_proof.c โ€” code-level proof (userspace). Replicates the exact kernel integer arithmetic + control flow on the confirmed vulnerable path (every step annotated with its kernel source line). It is NOT a kernel trigger: the bluetooth RFCOMM module is not present on the audited guest (see env.txt and VERDICT.md for the full reachability analysis).
  • build.sh / run.sh โ€” exact, self-contained build & run commands.
  • build.log / run.log / run.stress.log โ€” full untrimmed output.
  • env.txt โ€” guest environment + reachability evidence (kldstat, kldload, module search, kernel-symbol count, AF_BLUETOOTH socket test, src presence).
  • fix.diff โ€” standalone git apply-able unified diff fixing the bug (validated with git apply --check and patch --dry-run, both rc=0).
  • VERDICT.md โ€” full narrative, line-by-line static trace, reachability caveat.
  • manifest.json โ€” machine-readable catalog.

Build

./build.sh

Produces divzero_proof (vulnerable arithmetic) and divzero_proof_fixed (same code compiled with -DFIX_MTU, which clamps pcb->mtu).

Run

./run.sh

Runs the vulnerable path (expects the divide-by-zero, surfaced as SIGFPE), then the guarded path (expects clean completion).

Expected output

Vulnerable path (bug present โ€” the kernel arithmetic reaches the zero divisor):

STEP 1: peer PN with mtu=0 processed by set_pn (line 3019); now pcb->mtu = 0  <-- NO validation was applied
STEP 2: ... --rx_cred=20 <= MAX_CREDITS/2 -> send_credits() called (line 2429)
STEP 3: send_credits line 3283: credits = ssb_space / pcb->mtu ...
### SIGFPE: divide by zero at step 3 (send_credits, line 3283) ###
PROOF: pcb->mtu==0 reached the unguarded divisor -> kernel would #DE/panic

Guarded path (fix active): prints NO FAULT: pcb->mtu was guarded before the divisor (fix active); credits path completed. and exits 0.

Reproducing the kernel panic

Cannot be done on this master DEV guest: the ng_btsocket module is not built into X86_64_GENERIC, is not present as a .ko, cannot be kldload-ed, the socket domain AF_BLUETOOTH is not registered, there is no kernel source on the guest to build it, and reaching the live path additionally needs a real bluetooth adapter (or a virtual HCI device DragonFlyBSD does not provide). On a host that does have bluetooth hardware and ng_btsocket loaded, the panic is produced by a malicious peer that: 1. opens an L2CAP channel on PSM RFCOMM (0x0003) and completes the RFCOMM session handshake, 2. sends a PN MCC frame with mtu = 0 and flow_control = 0xf0 (enables CFC), 3. sends a single UIH data frame on the DLC to drive rx_cred down to RFCOMM_MAX_CREDITS/2, which calls send_credits() โ†’ #DE โ†’ panic.

See VERDICT.md for the full line-by-line trace.

VERDICT.md verdict full narrative: line-by-line static proof of the divide-by-zero + runtime-reachability analysis (module absent, needs bluetooth HW)
โ†“ download raw

DF-0281 โ€” netgraph7 RFCOMM divide-by-zero via peer PN mtu=0

Verdict

INCONCLUSIVE at runtime / CONFIRMED at code level. The divide-by-zero is real and unambiguous in the audited master DEV source (static proof below, deterministic). It is NOT reachable on the running guest because the entire netgraph7 bluetooth stack is absent: ng_btsocket is neither compiled into X86_64_GENERIC nor present as a loadable module (kldload ng_btsocket โ†’ ENOENT, nm /boot/kernel/kernel | grep -c ng_btsocket_rfcomm โ†’ 0, socket(AF_BLUETOOTH=31,โ€ฆ) โ†’ -1), there is no kernel source on the guest to build it, and even if the module were loaded the vulnerable path additionally requires a live RFCOMM session over L2CAP/HCI, which needs a real bluetooth adapter (or a virtual/software HCI device that DragonFlyBSD does not ship โ€” the only HCI transports are ng_h4/UART, ng_ubt/USB, ng_bt3c/PCMCIA, ng_ubtbcmfw/USB-firmware, all hardware-backed). The QEMU/KVM guest has no bluetooth hardware. So: code-level bug = certain; runtime trigger on this kernel = not possible โ†’ inconclusive (per the rubric: "needs specific HW / missing koption").

The bug (confirmed line-by-line)

All citations are sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c unless noted; line numbers are current master DEV.

  1. pcb->mtu is u_int16_t (sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h:176). Default on DLC creation is RFCOMM_DEFAULT_MTU = 667 (ng_btsocket_rfcomm.c:432). The only writer that takes a peer-controlled value is set_pn.

  2. ng_btsocket_rfcomm_set_pn(pcb, cr, flow_control, credits, mtu) (:3013-3043) performs, at :3019: c pcb->mtu = le16toh(mtu); with no validation โ€” no != 0, no range check. A PN (Parameter Negotiation) MCC frame carries a 16-bit MTU field that is fully attacker- chosen; mtu = 0 is copied verbatim.

  3. PN reachability from a peer frame. ng_btsocket_rfcomm_receive_pn (:2881) only checks pn->dlci != 0 (:2899); it never inspects pn->mtu. It calls set_pn at :2913 (PN request, existing DLC), :2931 (PN response), and :2955 (incoming connection). receive_pn is called from ng_btsocket_rfcomm_receive_mcc (:2553), which is called from ng_btsocket_rfcomm_receive_frame (:2022), which is called from ng_btsocket_rfcomm_session_receive (:1679). That session-receive routine pulls frames off the L2CAP socket with soreceive(s->l2so, โ€ฆ) at :1665 โ€” i.e. the data arrives from a remote RFCOMM peer over L2CAP/HCI. No local-only / software path feeds this socket.

  4. The unguarded divisor. ng_btsocket_rfcomm_send_credits(pcb) (:3268-3309) computes, at :3283: c credits = ssb_space(&pcb->so->so_rcv) / pcb->mtu; ssb_space() returns long (sys/sys/socketvar.h:270-282). The expression is therefore long / u_int16_t โ†’ on x86-64 an idiv with divisor 0 when pcb->mtu == 0. A zero divisor traps as #DE (divide error) regardless of the dividend โ€” 0/0 faults just as N/0 does. โ†’ kernel panic.

  5. Trigger sequence & ordering (finding's central claim is correct). ng_btsocket_rfcomm_receive_uih (:2356), on a data-bearing UIH frame (m0->m_pkthdr.len > 0, :2424) with credit-based flow control on (:2426), decrements rx_cred (:2428) and, when it has dropped to <= RFCOMM_MAX_CREDITS/2, calls send_credits(pcb) at :2429. That call site is before the MTU-oversize check at :2438 (if (m0->m_pkthdr.len > pcb->mtu)). So the divide-by-zero at :3283 executes before any code that could indirectly notice mtu == 0. Confirmed.

Net: a remote peer that (a) establishes an L2CAP channel on PSM RFCOMM and an RFCOMM session, (b) sends a PN MCC with mtu = 0 and flow_control = 0xf0 (enabling CFC), and (c) sends one UIH data frame to drive rx_cred down to MAX_CREDITS/2, deterministically panics the kernel via #DE. No authentication is required (RFCOMM/SPP is unauthenticated by design). This matches the finding's claim exactly.

Why it does NOT reproduce at runtime on this guest

Check on the running master DEV guest Result
kldstat kernel, ehci.ko, xhci.ko only โ€” no bluetooth
kldload ng_btsocket kldload: can't load ng_btsocket: No such file or directory
bluetooth modules on disk (find /boot /modules) none
nm /boot/kernel/kernel \| grep -c ng_btsocket_rfcomm 0 โ€” not linked into kernel
grep bluetooth/netgraph7/ng_bt sys/config/X86_64_GENERIC no matches โ€” not configured
socket(AF_BLUETOOTH=31, โ€ฆ) from userspace -1 โ€” socket domain not registered
/usr/src/sys on guest absent โ€” cannot build the module

So the vulnerable code is dead on this kernel image, and even rebuilding it would not help: the only way to feed a PN frame to receive_pn is over a real RFCOMM/L2CAP/HCI session, and DragonFlyBSD has no virtual HCI device. The QEMU guest has no bluetooth adapter.

Code-level proof

divzero_proof.c is a userspace replication of the exact kernel integer arithmetic and control flow on the confirmed path โ€” every arithmetic / control operation is annotated with its kernel source line. It is not a kernel trigger (the module is absent; it cannot be). It demonstrates:

  • set_pn accepting a peer mtu = 0 with no validation (pcb->mtu becomes 0);
  • the UIH-driven credit decrement reaching the send_credits call site; and
  • the divisor pcb->mtu being evaluated, which on x86-64 raises the #DE trap โ€” in the harness surfaced as SIGFPE, caught and reported.

Build & run: ./build.sh && ./run.sh. Decisive output (run.log):

STEP 1: peer PN with mtu=0 processed by set_pn (line 3019); now pcb->mtu = 0  <-- NO validation was applied
STEP 2: ... --rx_cred=20 <= MAX_CREDITS/2 -> send_credits() called (line 2429)
STEP 3: send_credits line 3283: credits = ssb_space / pcb->mtu ...
### SIGFPE: divide by zero at step 3 (send_credits, line 3283) ###
PROOF: pcb->mtu==0 reached the unguarded divisor -> kernel would #DE/panic

Re-running 3ร— (run.stress.log) hits SIGFPE every time โ€” deterministic, as expected for a zero-divisor integer divide. Compiling with -DFIX_MTU (clamping pcb->mtu to RFCOMM_DEFAULT_MTU) makes the path complete with no fault, confirming the fix closes the arithmetic path.

Exploit chain

Not a memory-corruption class โ€” it is a single-instruction DoS (divide-by-zero โ†’ #DE โ†’ panic). There is no further primitive to derive from the #DE itself: on x86 the trap is non-resumable and the kernel panics. Weaponization = remote unauthenticated kernel panic of any DragonFlyBSD host that (a) has the ng_btsocket module loaded (or bluetooth compiled in) and (b) has a bluetooth adapter up and accepting RFCOMM connections (SPP). Single malformed PN + one UIH frame; deterministic. Availability-only (C:N/I:N/A:H), matching the finding's CVSS AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H โ€” adjacency is required (bluetooth range), there is no confidentiality or integrity impact.

fix.diff (validated: git apply --check and patch --dry-run both rc=0) applies two guards, both minimal:

  1. Root cause โ€” in ng_btsocket_rfcomm_set_pn (:3019), do not store a zero MTU; fall back to RFCOMM_DEFAULT_MTU: pcb->mtu = (le16toh(mtu) != 0) ? le16toh(mtu) : RFCOMM_DEFAULT_MTU;
  2. Defense-in-depth at the fault site โ€” in ng_btsocket_rfcomm_send_credits (:3283), bail out before the divisor: if (pcb->mtu == 0) return (EINVAL);

Either guard alone prevents the panic; both together close the untrusted-input path and protect the divisor regardless of how pcb->mtu could later become zero. This supersedes the finding markdown (no markdown writeup exists for DF-0281; this is the authoritative verified fix).

Confirmed kernel references

Detail

Exploit chain

Not memory corruption -- single-instruction DoS. On a host with bluetooth HW and ng_btsocket loaded, a remote unauthenticated peer within bluetooth range: (1) opens L2CAP PSM 0x0003 and completes the RFCOMM session, (2) sends a PN MCC with mtu=0 + flow_control=0xf0 (enables CFC), (3) sends one UIH data frame to drive rx_cred down to RFCOMM_MAX_CREDITS/2 -> send_credits() -> #DE -> kernel panic. Deterministic, single-shot, availability-only (C:N/I:N/A:H). No further primitive is derivable from a #DE trap (non-resumable).

Evidence (decisive lines)

findings/poc/DF-0281/: divzero_proof.c (code-level proof replicating kernel arithmetic), run.log (decisive: 'pcb->mtu=0 reached the unguarded divisor -> kernel would #DE/panic' via SIGFPE), run.stress.log (3/3 deterministic), env.txt (kldload->ENOENT, AF_BLUETOOTH->-1, 0 kernel symbols, no module/src), fix.diff (validated git apply --check rc=0), VERDICT.md (full line-by-line trace).

PoC changes

Authored from scratch (no prior folder). divzero_proof.c replicates the exact kernel arithmetic of set_pn+receive_uih+send_credits, every step annotated with its kernel source line; catches SIGFPE (the userspace manifestation of #DE) and reports it. build.sh/run.sh are self-contained. Initial run produced no output (stdout fully-buffered over the ssh pipe + _exit() in handler dropped the buffer); fixed with setvbuf(_IONBF) and write()-to-stderr in the handler. fix.diff adds mtu!=0 clamp in set_pn (root cause) plus a divisor guard in send_credits (defense-in-depth).

Verified recommended fix

fix.diff (validated git apply --check + patch --dry-run, rc=0): (1) root cause in ng_btsocket_rfcomm_set_pn (:3019) -- 'pcb->mtu = (le16toh(mtu)!=0) ? le16toh(mtu) : RFCOMM_DEFAULT_MTU;'; (2) defense-in-depth in ng_btsocket_rfcomm_send_credits (:3283) -- 'if (pcb->mtu==0) return (EINVAL);' before the divisor. Either alone prevents the panic; both together close the untrusted-input path and guard the fault site. Supersedes finding proposal (no markdown writeup exists for DF-0281; this is the authoritative verified fix).

Verdict

CONFIRMED at code level, NOT reachable at runtime on this master DEV guest. Static trace proves the bug: ng_btsocket_rfcomm_set_pn (:3019) stores a peer-controlled PN mtu into pcb->mtu (u_int16_t) with NO !=0 validation; ng_btsocket_rfcomm_send_credits (:3283) computes credits = ssb_space(...) [long] / pcb->mtu unconditionally -> long/0 -> #DE -> panic on x86-64. The call site (:2429) is BEFORE the mtu-oversize check (:2438), exactly as the finding claims. A userspace harness replicating the exact kernel arithmetic deterministically reaches the zero divisor (SIGFPE, 3/3 runs). However the ng_btsocket module is absent from this guest: kldload ng_btsocket -> ENOENT, nm kernel | grep -c ng_btsocket_rfcomm -> 0, socket(AF_BLUETOOTH=31,..) -> -1, no /usr/src/sys, and X86_64_GENERIC has no bluetooth. Even with the module loaded, the only feed into receive_pn is soreceive(s->l2so,..) (:1665) over L2CAP/HCI, which needs a real bluetooth adapter (DragonFlyBSD ships no virtual HCI). The QEMU guest has none. Classification: inconclusive (needs specific HW + missing koption/module).