# DF-5733 VERDICT — REPRODUCED (kernel panic, remote-peer-triggered DoS)

## Bottom line

**Reproduced, deterministic, root-caused, fix validated.** An unauthenticated
remote RFCOMM (Bluetooth) peer can set a DLC's negotiated MTU to zero via a
PN *response* frame, because the response path at `sys/netbt/rfcomm_session.c:1374`
checks only `pn.mtu > RFCOMM_MTU_MAX || pn.mtu > dlc->rd_mtu` — no lower
bound. The zero lands in `dlc->rd_mtu` (line 1385). The next time the DLC
starts output — app write (`rfcomm_send` → `rfcomm_dlc_start`) or app read
(`rfcomm_rcvd` → `rfcomm_dlc_start`) — `sys/netbt/rfcomm_dlc.c:341` executes
`dlc->rd_rxsize / dlc->rd_mtu` with divisor 0 → x86-64 `#DE` (trap 18) in
kernel mode → `trap_fatal()` (`sys/platform/pc64/x86_64/trap.c:787,1101`) →
**panic, all CPUs stopped, system halted in DDB**.

## Why the injected path is the production path

* The KLD compiles `rfcomm_session.c` / `rfcomm_dlc.c` / `rfcomm_upper.c`
  copied **unmodified** from `/usr/src/sys/netbt` — md5 identical to the audit
  tree and to the guest's `/usr/src` (`setup.log`, `env.txt`;
  `cbd20192a12ca5178e759d009d92fcb1a`). Same compiler (`cc 8.3
  [DragonFly]`), same KLD CFLAGS (`-O2 -Werror -mcmodel=kernel`).
* `l2cap_stub.c` implements only the 10 L2CAP upper-API functions the RFCOMM
  code calls; the harness drives the session **through the same
  `struct btproto` callback vector** (`connected`, `input`) that
  `sys/netbt/l2cap_upper.c` uses — no state-machine code is duplicated or
  modified.
* Injected frames carry valid TS 07.10 FCS (same reversed-CRC-8 table as the
  production file) and pass `rfcomm_session_input()`'s FCS gate
  (`rfcomm_session.c:664`) — nothing is bypassed.

## Trigger chain (as observed in run.log / run.2.log)

1. Local app connects DLCI 6 (channel 3); session initiator sends SABM(0).
2. Peer sends UA(0) → session OPEN → DLC sends PN command (mtu 127).
3. **Peer sends PN response, mtu=0, flow_control=0xe0** →
   `rfcomm_session_recv_mcc_pn` response path: `0 > 32767`? no; `0 > 127`?
   no → check passes → `dlc->rd_mtu = 0`; CFC enabled on session
   (`rfcomm_session.c:1392-1394`).
4. UA(6) → DLC OPEN (MSC exchanged).
5. App sends 1 byte → `rfcomm_dlc_start` → `credits = 4096 / 0` →
   `Fatal trap 18: integer divide fault while in kernel mode`,
   `Stopped at rfcomm_dlc_start+0x3b: divq %rcx,%eax`.

## Impact ceiling — why panic/DoS, not privesc

The primitive is a *denominator* zero, not a controlled memory write: the
CPU raises `#DE` at the `divq`; no memory is corrupted, no operand beyond
zero/nonzero is attacker-controlled, and the trap is fatal *before* any
result is used. There is no slab-groom/forge path from this bug. Impact is
availability: unauthenticated remote kernel panic (system halt) on any
kernel built with `device bluetooth` (netbt is `optional bluetooth`,
present in LINT64, **not** in GENERIC). Precondition: a local RFCOMM
connection to the hostile peer (normal usage — e.g. any serial-port
service); the panic then fires on first I/O. Both outgoing and incoming DLCs
are poisonable (incoming: PN command creates the DLC with clamped mtu
≥ 23 at `rfcomm_session.c:1334-1337`, but a *subsequent PN response* for
that same DLC still hits the unbounded path at 1374-1385).

## Reproduction & stress

* Baseline run 1 (fresh guest): panic (`run.log`, `panic.txt`).
* Baseline run 2 after `vm.sh reset with-src` (fully clean guest, rebuild):
  identical panic (`run.2.log`) — deterministic across resets, cpuid/IP
  differ only in the ASLR-ish module load address.
* No hardware needed: synthetic injection is the endorsed evidence class
  for this rig (no Bluetooth hardware).

## Fix validation (same verbatim code, one-hunk patch)

`fix.diff` adds the missing lower bound (mirrors NetBSD trunk,
`rfcomm_session.c` v1.30 "reject invalid or unacceptable MTU"):

```diff
-		if (pn.mtu > RFCOMM_MTU_MAX || pn.mtu > dlc->rd_mtu) {
+		if (pn.mtu < RFCOMM_MTU_MIN ||
+		    pn.mtu > RFCOMM_MTU_MAX || pn.mtu > dlc->rd_mtu) {
```

Applied with `patch -p3` to the module's verbatim copy of
`rfcomm_session.c`, module rebuilt (`build-fix.log` flow), guest reset, same
harness re-run with byte-identical injected frames:

* `after PN response: dlc->rd_mtu = 127` (rejected — the "(poisoned!)"
  suffix is static harness text; the value is what matters)
* `dlc state = 7` (RFCOMM_DLC_WAIT_DISCONNECT), DISC(6) sent, then UA(6)
  closes the DLC, session sends DISC(0) (`run.fix.log`)
* app send → returns without any trap; sysctl returns 0; **guest stays up**
  (`uptime`/`GUEST_ALIVE` in the ssh transcript).

Full-kernel rebuild validation was not possible/applicable: the shipped
X86_64_GENERIC does not compile netbt (`optional bluetooth`, absent from
GENERIC); the KLD path compiles the identical production `.c` files with
identical flags, which is the faithful validation for this configuration.

## Guest hygiene

Guest was reset to the `with-src` snapshot after the fix run and is `up` and
clean (`guest_dirty: 0`).

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