β¬’ DragonFlyBSD Kernel Audit
← triage Β· 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).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0281 Β· 18 files
FileTypeDescriptionSize
divzero_proof.c trigger-source userspace replication of the exact kernel arithmetic on the confirmed vulnerable path; every arithmetic/control op annotated with its kernel source line 8.5 KB view raw
build.sh build-script builds divzero_proof (vulnerable) and divzero_proof_fixed (-DFIX_MTU) 502 B view raw
run.sh run-script runs vulnerable path (expect SIGFPE) then guarded path (expect NO FAULT) 454 B view raw
build.log build-log full userspace build output, both variants 133 B view raw
run.log run-log decisive vulnerable-path run, full output incl SIGFPE 821 B view raw
run.fixed.log run-log guarded-path run (-DFIX_MTU), full output incl NO FAULT 685 B view raw
run.stress.log stress-log 3x determinism check β€” SIGFPE every run 1002 B view raw
module_build.log build-log FULL output of building the ACTUAL ng_btsocket.ko from /usr/src against the audit kernel (unpatched baseline); rc=0, then objdump idiv count=1 1.1 KB view raw
fix_build.log build-log FULL output of rebuilding ng_btsocket.ko with fix.diff applied (Phase 8); rc=0, idiv count=1 but now preceded by test/je guard 1.5 KB view raw
objdump_unpatched.dis objdump full objdump -d of the unpatched ng_btsocket.ko; idiv %r8 at offset 0xb418 with NO preceding zero-check on the divisor 446.4 KB ↓ download
objdump_patched.dis objdump full objdump -d of the patched ng_btsocket.ko; idiv %rdi at offset 0xb4b2 preceded by 'test %cx,%cx; je b7d3' guard 446.5 KB ↓ download
idiv_comparison.txt evidence focused before/after object-code comparison: unguarded idiv (unpatched) vs guarded idiv (patched), with annotations 3.6 KB view raw
env.txt environment uname, kern.version, cc version, kldstat, GENERIC bluetooth check, module build result 772 B view raw
fix.diff suggested-fix git-apply-able unified diff: (1) set_pn clamps mtu!=0 to RFCOMM_DEFAULT_MTU, (2) send_credits returns EINVAL if pcb->mtu==0 before the divide 1.1 KB view raw
README.md readme human-facing summary: what the pack contains, build/run, expected output, reachability caveat 4.2 KB ↓ raw
VERDICT.md verdict full narrative: reproduced at code/object-code level; line-by-line trace; objdump proof; why no runtime trigger; fix validation 11.9 KB ↓ 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 human-facing summary: what the pack contains, build/run, expected output, 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 (:3019); ng_btsocket_rfcomm_send_credits later divides ssb_space / pcb->mtu unconditionally (:3283). A peer PN with mtu=0 plus one UIH data frame β†’ divide-by-zero #DE β†’ kernel panic. CWE-369 (divide-by-zero), severity High.

Verdict: REPRODUCED (code/object-code level, certain)

The bug is confirmed three independent ways:

  1. Static line-by-line trace β€” every hop from peer PN frame to the unguarded divisor is cited path:line in VERDICT.md.
  2. Actual kernel module build + objdump β€” ng_btsocket.ko builds cleanly from /usr/src/sys/netgraph7/bluetooth/socket/ against the audit kernel; objdump confirms the unguarded idiv %r8 (divisor = pcb->mtu) at module offset 0xb418 with no preceding zero-check. (objdump_unpatched.dis, idiv_comparison.txt)
  3. Userspace arithmetic replication β€” divzero_proof.c reproduces the exact kernel integer arithmetic; with pcb->mtu = 0 it raises SIGFPE 3/3 runs.

No runtime trigger on this KVM guest: ng_btsocket is not in X86_64_GENERIC, the freshly-built .ko won't kldload (netgraph ABI mismatch), and reaching the live path needs real bluetooth HW anyway. The object-code-level proof is the accepted reproduction (precedent: DF-0594 / DF-0616).

What this pack contains

  • divzero_proof.c β€” userspace replication of the exact kernel arithmetic (every step annotated with its kernel source line).
  • build.sh / run.sh β€” build & run the harness.
  • build.log / run.log / run.fixed.log / run.stress.log β€” full output.
  • module_build.log β€” building the actual ng_btsocket.ko from source (unpatched baseline).
  • fix_build.log β€” rebuilding ng_btsocket.ko with fix.diff applied.
  • objdump_unpatched.dis / objdump_patched.dis β€” full disassemblies.
  • idiv_comparison.txt β€” focused before/after object-code comparison.
  • fix.diff β€” git apply-able fix (validated both directions).
  • env.txt β€” guest environment + reachability evidence.
  • VERDICT.md β€” full narrative.
  • manifest.json β€” machine-readable catalog.

Build (userspace harness)

./build.sh

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

Run

./run.sh

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
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.

Object-code proof (the strong evidence)

Build the actual vulnerable module and disassemble:

ssh dfbsd 'cd /usr/src/sys/netgraph7/bluetooth/socket && make &&
           objdump -d ng_btsocket.ko | grep idiv'
# -> exactly one "idiv %r8" at offset 0xb418, divisor = pcb->mtu, NO guard

Apply the fix and rebuild:

ssh dfbsd 'cd /usr/src && patch -p1 < /root/fix.diff &&
           cd sys/netgraph7/bluetooth/socket && make &&
           objdump -d ng_btsocket.ko | grep -B3 idiv'
# -> "idiv %rdi" at 0xb4b2, preceded by "test %cx,%cx; je <return>" at 0xb470

See idiv_comparison.txt for the annotated before/after.

Reproducing the kernel panic

Cannot be done on this master DEV guest (no bluetooth HW, module won't load). 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: reproduced at code/object-code level; line-by-line trace; objdump proof; why no runtime trigger; fix validation
↓ download raw

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

Verdict

REPRODUCED at code/object-code level (certain). The divide-by-zero is real, deterministic, and unambiguous β€” confirmed three independent ways:

  1. Static line-by-line trace of the cited path in the audited master DEV source (every hop cited path:line below).
  2. Actual kernel module build: ng_btsocket.ko compiles cleanly from /usr/src/sys/netgraph7/bluetooth/socket/ against the audit-source kernel (DragonFly 6.5-DEVELOPMENT #0, Thu Jul 2 06:02:54 UTC 2026). The vulnerable function ng_btsocket_rfcomm_send_credits is inlined into ng_btsocket_rfcomm_sessions_task and objdump confirms the unguarded idiv %r8 instruction (divisor = pcb->mtu, offset 0x24 of the PCB) at module offset 0xb418. There is no zero-check between loading the divisor and the idiv. (Full disassembly: objdump_unpatched.dis; focused comparison: idiv_comparison.txt.)
  3. Userspace arithmetic replication (divzero_proof.c) reproduces the exact kernel integer arithmetic on the confirmed path; with pcb->mtu = 0 it raises SIGFPE (the userspace analogue of in-kernel #DE) 3/3 runs β€” deterministic, as expected for a zero-divisor integer divide.

It is NOT runtime-triggerable on this KVM guest because (a) the ng_btsocket module is neither compiled into X86_64_GENERIC nor shipped as a .ko, and (b) kldload of the freshly-built module fails with a netgraph ABI version mismatch (KLD ng_btsocket.ko: depends on netgraph - not available or version mismatch) β€” and even with the module loaded, reaching the live path requires a real RFCOMM session over L2CAP/HCI, i.e. real bluetooth hardware, which the QEMU guest lacks and DragonFlyBSD provides no virtual/software HCI device for. The object-code-level proof is the accepted reproduction (precedent: DF-0594 TKIP / DF-0616 netmap code-level harnesses; here it is stronger β€” the actual vulnerable .ko is built and disassembled, not just a userspace reimplementation).

This supersedes the prior inconclusive run (id 23): that run had only the userspace arithmetic replication; this run adds the real-module build + objdump proof + object-code-level fix validation.

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 (commit 6cc80ee9).

  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. (Confirmed by reading the source and by the objdump: set_pn is inlined and the le16toh(mtu) store reaches the same 0x24(%r13) slot that the idiv later reads.)

  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), from ng_btsocket_rfcomm_receive_frame (:2022), from ng_btsocket_rfcomm_session_receive (:1679). The 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 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 (0x0003) 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.

Object-code-level proof (the new evidence)

The vulnerable .ko was built from /usr/src (same audit commit) and disassembled. The inlined send_credits lives inside ng_btsocket_rfcomm_sessions_task. objdump shows exactly one idiv in the entire module, and it is the unguarded divide by pcb->mtu:

UNPATCHED ng_btsocket.ko β€” idiv at offset 0xb418 (NO guard before it):
    b3df:  movzwl 0x24(%r13),%edi      ; edi  = pcb->mtu (the divisor)
    ...
    b3fa:  movzwl %di,%r8d              ; r8   = pcb->mtu (divisor, zero-extended)
    ...
    b413:  mov    %rsi,%rax             ; rax  = ssb_space(...) (dividend)
    b416:  cqto                          ; sign-extend
    b418:  idiv   %r8                   ; *** DIVIDE BY pcb->mtu β€” NO GUARD ***
                                        ; *** if r8==0 (peer PN mtu=0) -> #DE -> panic ***

There is no test/je/jne on r8/edi between loading pcb->mtu (b3df/b3fa) and the idiv (b418). The divide is unconditional. This is the compiled form of the unguarded line :3283. (Full module disassembly in objdump_unpatched.dis.)

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
grep bluetooth netgraph7 ng_bt X86_64_GENERIC 0 matches β€” not configured
ng_btsocket.ko build from /usr/src succeeds (rc=0) β€” code is healthy
kldload ng_btsocket.ko (freshly built) fails: depends on netgraph - not available or version mismatch (ABI mismatch between the Jul-2 kernel build and the Jul-3 module build; netgraph.ko is busy/in-kernel and cannot be swapped)
socket(AF_BLUETOOTH=31, …) from userspace -1 β€” socket domain not registered
bluetooth HW on QEMU guest none (no -device bt*); DragonFlyBSD has no virtual HCI

So the vulnerable code is dead on the running kernel image (the module is not loaded), and even rebuilding+loading 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 object-code-level proof (module build + objdump + userspace arithmetic replication) is the accepted reproduction.

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: applies cleanly with patch -p1 both directions) adds 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 any pre-verification proposal (no finding markdown exists for DF-0281; this is the authoritative verified fix).

Object-code-level fix validation (Phase 8)

The fix was validated at the .ko object-code level (a full kernel rebuild + runtime trigger is impossible: no bluetooth HW + module won't load). The patched module was rebuilt from the same source with fix.diff applied, and objdump confirms the compiler now emits a guard immediately before the idiv:

PATCHED ng_btsocket.ko β€” idiv at offset 0xb4b2, GUARDED by test/je:
    b46b:  movzwl 0x24(%r13),%ecx       ; ecx = pcb->mtu (the divisor)
    b470:  test   %cx,%cx                ; *** THE GUARD: is pcb->mtu == 0? ***
    b473:  je     b7d3                   ; *** YES -> jump to clean return (b7d3),
                                        ;     SKIPPING the divide entirely ***
    ...
    b498:  movzwl %cx,%edi               ; edi = pcb->mtu (KNOWN NON-ZERO here)
    ...
    b4b0:  cqto
    b4b2:  idiv   %rdi                   ; *** DIVIDE β€” only reached if pcb->mtu != 0 ***

    b7d3:  mov    0xb8(%rbx),%eax       ; <-- je target: clean return path
    b7d9:  jmpq   ab8f                   ;     (function epilogue)

The test %cx,%cx; je b7d3 at b470/b473 is exactly the compiler's emission of if (pcb->mtu == 0) return (EINVAL);. When pcb->mtu == 0, execution jumps past the idiv to b7d3 (clean return). The divide-by-zero is now impossible. (Full module disassembly in objdump_patched.dis.)

The userspace arithmetic replication (divzero_proof_fixed, compiled with -DFIX_MTU) also completes with NO FAULT when pcb->mtu is clamped to RFCOMM_DEFAULT_MTU, confirming the arithmetic path is closed.

Before/after contrast: - Unpatched .ko: one idiv %r8, no preceding zero-check on the divisor β†’ #DE when pcb->mtu == 0 β†’ kernel panic. - Patched .ko: idiv %rdi preceded by test %cx,%cx; je <return> β†’ divide skipped when pcb->mtu == 0 β†’ no fault.

PoC changes (this run vs prior inconclusive run)

The PoC sources (divzero_proof.c, build.sh, run.sh, fix.diff) are unchanged β€” the prior run's harness and fix were already correct. What changed is the evidence: this run adds (a) the actual ng_btsocket.ko module build log proving the vulnerable code links cleanly against the audit kernel, (b) objdump_unpatched.dis / objdump_patched.dis proving the unguarded idiv exists in the compiled binary and is guarded after the fix, and (c) idiv_comparison.txt β€” the focused before/after object-code comparison. This elevates the verdict from inconclusive (userspace arithmetic only) to reproduced (object-code-level proof on the real built module).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED at object-code level: the unpatched ng_btsocket.ko (built from /usr/src) contains an unguarded 'idiv %r8' at offset 0xb418 with divisor=pcb->mtu and NO preceding zero-check. After applying fix.diff and rebuilding the module, objdump confirms the compiler now emits 'test %cx,%cx; je b7d3' (offset 0xb470) immediately before the 'idiv %rdi' (offset 0xb4b2) -- when pcb->mtu==0, execution jumps to b7d3 (clean return) and the divide is skipped entirely. The userspace arithmetic replication with -DFIX_MTU also completes with NO FAULT. Runtime trigger impossible on this guest (no bluetooth HW + module won't kldload), so object-code-level validation -- the strongest possible without HW -- is the Phase 8 proof.

=== BEFORE (unpatched module): unguarded idiv, NO zero-check ===
    b3df: movzwl 0x24(%r13),%edi      ; load pcb->mtu
    b3fa: movzwl %di,%r8d              ; r8 = pcb->mtu (divisor)
    b418: idiv   %r8                   ; *** DIVIDE -- #DE if mtu==0 ***
=== AFTER (patched module): idiv guarded by test/je ===
    b46b: movzwl 0x24(%r13),%ecx       ; load pcb->mtu
    b470: test   %cx,%cx                ; *** GUARD: is mtu==0? ***
    b473: je     b7d3                   ; *** YES -> skip divide (clean return) ***
    b498: movzwl %cx,%edi               ; edi = mtu (known non-zero here)
    b4b2: idiv   %rdi                   ; divide -- only if mtu!=0
=== userspace replication ===
unpatched: '### SIGFPE: divide by zero at step 3 ###' (3/3 runs)
fixed (-DFIX_MTU): 'NO FAULT: pcb->mtu was guarded before the divisor (fix active)'
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (module validated at .ko object-code level -- vulnerable module cannot be loaded at runtime on this guest: not in GENERIC + netgraph ABI mismatch prevents kldload + no bluetooth HW)

Confirmed kernel references

Detail

Exploit chain

none -- not a memory-corruption class. Single-instruction DoS: idiv-by-0 -> #DE -> non-resumable kernel trap -> panic. No further primitive derivable. Weaponization = remote unauthenticated kernel panic of any DragonFlyBSD host with ng_btsocket loaded + a bluetooth adapter accepting RFCOMM (SPP): one PN(mtu=0, flow_control=0xf0) + one UIH frame; deterministic. Availability-only (C:N/I:N:A:H), CVSS AV:A/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.

Evidence (decisive lines)

=== UNPATCHED ng_btsocket.ko objdump (idiv at 0xb418, NO guard) ===
    b3df: movzwl 0x24(%r13),%edi      ; edi = pcb->mtu (divisor)
    b3fa: movzwl %di,%r8d              ; r8  = pcb->mtu (divisor, zero-extended)
    b413: mov    %rsi,%rax             ; dividend = ssb_space(...)
    b416: cqto
    b418: idiv   %r8                   ; *** DIVIDE BY pcb->mtu -- NO GUARD -- #DE if 0 ***
=== userspace replication (run.log, vulnerable) ===
STEP 1: peer PN with mtu=0 processed by set_pn (line 3019); now pcb->mtu = 0
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
(stress: SIGFPE 3/3 runs -- deterministic)

PoC changes

PoC sources (divzero_proof.c, build.sh, run.sh, fix.diff) UNCHANGED -- the prior inconclusive run's harness and fix were already correct. New EVIDENCE this run: (a) module_build.log -- full output of building the ACTUAL ng_btsocket.ko from /usr/src (rc=0); (b) objdump_unpatched.dis / objdump_patched.dis -- full disassemblies; (c) idiv_comparison.txt -- focused before/after object-code comparison proving the unguarded 'idiv %r8' becomes a guarded 'idiv %rdi' preceded by 'test %cx,%cx; je'; (d) fix_build.log -- Phase 8 patched-module rebuild log; (e) rewritten VERDICT.md/README.md/manifest.json. Elevates verdict from inconclusive (userspace arithmetic only) to reproduced (real-module objdump proof).

Verified recommended fix

fix.diff applies two minimal guards (both in sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c): (1) root cause at :3019 -- clamp peer mtu to RFCOMM_DEFAULT_MTU when zero: 'pcb->mtu = (le16toh(mtu) != 0) ? le16toh(mtu) : RFCOMM_DEFAULT_MTU;'; (2) defense-in-depth at the fault site :3283 -- 'if (pcb->mtu == 0) return (EINVAL);' before the divide. Either alone prevents the panic; both close the untrusted-input path. Full git-apply-able diff in findings/poc/DF-0281/fix.diff.

Verdict

REPRODUCED at code/object-code level (certain). The bug is real and confirmed three independent ways. (1) Static trace: ng_btsocket_rfcomm_set_pn copies a peer-supplied PN mtu into pcb->mtu with NO validation at sys/netgraph7/bluetooth/socket/ng_btsocket_rfcomm.c:3019; ng_btsocket_rfcomm_send_credits divides ssb_space/pcb->mtu unconditionally at :3283; the send_credits call site at :2429 is BEFORE the mtu-oversize check at :2438, so a peer PN with mtu=0 + one UIH frame deterministically hits idiv-by-0 -> #DE -> kernel panic. (2) Built the ACTUAL ng_btsocket.ko from /usr/src against the audit kernel (rc=0); objdump confirms exactly one 'idiv %r8' at module offset 0xb418 with divisor=pcb->mtu (loaded from PCB offset 0x24) and NO preceding zero-check. (3) Userspace arithmetic replication raises SIGFPE 3/3 runs. Not runtime-triggerable on this guest (ng_btsocket not in GENERIC, freshly-built .ko won't kldload due to netgraph ABI mismatch, and the live path needs real bluetooth HW which QEMU lacks) -- the object-code proof is the accepted reproduction. This supersedes the prior inconclusive run which had only userspace arithmetic; this run adds the real-module build + objdump proof.