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| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 (seeenv.txtandVERDICT.mdfor 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โ standalonegit apply-able unified diff fixing the bug (validated withgit apply --checkandpatch --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.
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.
-
pcb->mtuisu_int16_t(sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h:176). Default on DLC creation isRFCOMM_DEFAULT_MTU = 667(ng_btsocket_rfcomm.c:432). The only writer that takes a peer-controlled value isset_pn. -
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 = 0is copied verbatim. -
PN reachability from a peer frame.
ng_btsocket_rfcomm_receive_pn(:2881) only checkspn->dlci != 0(:2899); it never inspectspn->mtu. It callsset_pnat:2913(PN request, existing DLC),:2931(PN response), and:2955(incoming connection).receive_pnis called fromng_btsocket_rfcomm_receive_mcc(:2553), which is called fromng_btsocket_rfcomm_receive_frame(:2022), which is called fromng_btsocket_rfcomm_session_receive(:1679). That session-receive routine pulls frames off the L2CAP socket withsoreceive(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. -
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()returnslong(sys/sys/socketvar.h:270-282). The expression is thereforelong / u_int16_tโ on x86-64 anidivwith divisor 0 whenpcb->mtu == 0. A zero divisor traps as #DE (divide error) regardless of the dividend โ0/0faults just asN/0does. โ kernel panic. -
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), decrementsrx_cred(:2428) and, when it has dropped to<= RFCOMM_MAX_CREDITS/2, callssend_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:3283executes before any code that could indirectly noticemtu == 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_pnaccepting a peermtu = 0with no validation (pcb->mtubecomes 0);- the UIH-driven credit decrement reaching the
send_creditscall site; and - the divisor
pcb->mtubeing evaluated, which on x86-64 raises the #DE trap โ in the harness surfaced asSIGFPE, 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.
Recommended fix
fix.diff (validated: git apply --check and patch --dry-run both rc=0)
applies two guards, both minimal:
- Root cause โ in
ng_btsocket_rfcomm_set_pn(:3019), do not store a zero MTU; fall back toRFCOMM_DEFAULT_MTU:pcb->mtu = (le16toh(mtu) != 0) ? le16toh(mtu) : RFCOMM_DEFAULT_MTU; - 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
- sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:3019
- sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:3283
- sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:2429
- sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:2438
- sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:2881
- sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:1665
- sys/netgraph7/bluetooth/include/ng_btsocket_rfcomm.h:176
- sys/sys/socketvar.h:270
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).