# DF-0680 — RFCOMM `rfcomm_ctloutput` PRCO_GETOPT missing-break NULL-deref panic

## Verdict
**REPRODUCED** on baseline `6.5-DEVELOPMENT #0` (build `Thu Jul  2 06:02:54 UTC 2026`).
**FIXED** on single-fix-combined kernel `6.5-DEVELOPMENT #1` (build `Sun Jul 19 06:56:50 UTC 2026`, sha256 `32765f70…`).

## Mechanism (trigger → primitive → effect)

`sys/netbt/rfcomm_socket.c:115-127` `rfcomm_ctloutput` `case PRCO_GETOPT`:

```c
m = m_get(M_WAITOK, MT_DATA);                 // :117  m != NULL
crit_enter();
m->m_len = rfcomm_getopt(pcb, sopt->sopt_name, mtod(m, void *)); // :119
crit_exit();
if (m->m_len == 0) {                          // :121  unknown opt -> 0
    m_freem(m);                                // :122
    m = NULL;                                  // :123
    error = ENOPROTOOPT;                       // :124
}                                              // *** MISSING break ***
sopt_from_kbuf(sopt, mtod(m, void *), m->m_len); // :126  mtod(NULL) deref
```

`rfcomm_getopt` (`sys/netbt/rfcomm_upper.c:540-566`) returns 0 for any option name
other than `SO_RFCOMM_MTU=1` / `SO_RFCOMM_FC_INFO=2` / `SO_RFCOMM_LM=3`. With
`m_len == 0`, the code frees `m`, sets `m = NULL`, sets `error = ENOPROTOOPT`, and
**falls through** to `sopt_from_kbuf(sopt, mtod(m, void *), m->m_len)`. `mtod(m,
void *)` is `((struct mbuf *)m)->m_data`; with `m == NULL` this reads at offset
`offsetof(struct mbuf, m_data) == 0x18` of the NULL page → kernel page fault →
**panic**.

`rfcomm_sattach` (`sys/netbt/rfcomm_socket.c:302-329`) performs **no privilege
check** (`priv_check`/`suser`), so any unprivileged local user can create the
socket. The only precondition is `netbt.ko` loaded (realistic on Bluetooth-
enabled systems).

## Trigger

```
socket(AF_BLUETOOTH=33, SOCK_STREAM, BTPROTO_RFCOMM=3)
getsockopt(fd, BTPROTO_RFCOMM, 99 /* unknown */, &buf, &len)
                            ^^^^^^^^^^^^^^^^^^ any value other than 1/2/3
```
→ deterministic kernel panic, 100 % reliable, no Bluetooth hardware needed.

## Panic signature (baseline)

```
Fatal trap 12: page fault while in kernel mode
fault virtual address    = 0x18
fault code               = supervisor read data, page not present
Stopped at      rfcomm_ctloutput.cold.9+0x8:    movl    0x18,%eax
db>
```

`0x18` is exactly `offsetof(struct mbuf, m_data)` — the NULL+offset dereference
from `mtod(NULL)`.

## Realistic impact ceiling

This is a **deterministic local kernel panic / DoS**. The fault is a *read* of
the NULL page (not a write), so there is no memory-corruption primitive — no
escalation chain is possible. Once `netbt.ko` is loaded, any local user can
panic the machine instantly and repeatedly.

Precondition: `netbt.ko` loaded (root action; realistic on Bluetooth-enabled
systems). No `kldload` by the attacker is required.

## Fix

`sys/netbt/rfcomm_socket.c`: add `break;` after the `ENOPROTOOPT` block so the
function returns the error instead of falling through into the
`sopt_from_kbuf(mtod(NULL))` deref. Also `m_freem(m)` the buffer on the success
path (the original code transferred bytes into `sopt` but never freed `m`,
leaking it — a real but minor leak).

See `fix.diff` (git-apply-able).

## Fix validation (Phase 8)

- **Baseline (`with-src` #0, unpatched):** `./trigger` ⇒ `Fatal trap 12` at
  `rfcomm_ctloutput.cold.9+0x8: movl 0x18,%eax` ⇒ guest DOWN (DDB).
- **Patched (`#1`, all 5 fixes incl. this one):** `./trigger` ⇒
  `getsockopt returned -1 (errno=42 Protocol not available)` (i.e.
  `ENOPROTOOPT`, exactly what the fixed path returns), guest STAYS UP.

Clean before/after. Fix closes the bug.
