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

## Recommended fix

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