# DF-0682 — L2CAP `l2cap_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/l2cap_socket.c:118-132` `l2cap_ctloutput` `case PRCO_GETOPT`:

```c
m = m_get(M_NOWAIT, MT_DATA);
if (m == NULL) { error = ENOMEM; break; }
m->m_len = l2cap_getopt(pcb, sopt->sopt_name, mtod(m, void *)); // :125
if (m->m_len == 0) {                          // unknown opt -> 0
    m_freem(m); m = NULL; error = ENOPROTOOPT; // :126-129
}                                              // *** MISSING break ***
sopt_from_kbuf(sopt, mtod(m, void *), m->m_len); // :131  mtod(NULL) deref
```

Mechanically identical to **DF-0680** (RFCOMM) and **DF-0687** (SCO).
`l2cap_getopt` (`sys/netbt/l2cap_upper.c:521-555`) returns 0 for any option name
other than `SO_L2CAP_IMTU=1..SO_L2CAP_LM=6`. The missing `break` after
`m_freem(m); m=NULL; error=ENOPROTOOPT;` lets execution fall through into
`sopt_from_kbuf(sopt, mtod(NULL, void *), m->m_len)` → page fault at
`offsetof(struct mbuf, m_data) == 0x18` → **panic**.

`l2cap_sattach` performs **no privilege check**, so any unprivileged local user
can create the socket. Precondition: `netbt.ko` loaded.

## Trigger

```
socket(AF_BLUETOOTH=33, SOCK_SEQPACKET, BTPROTO_L2CAP=2)
getsockopt(fd, BTPROTO_L2CAP, 99 /* unknown */, &buf, &len)
```
→ 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      l2cap_ctloutput.cold.5+0x8:     movl    0x18,%eax
db>
```

`0x18` = `offsetof(struct mbuf, m_data)` — the NULL+offset deref from
`mtod(NULL)`.

## Realistic impact ceiling

Deterministic local kernel panic / DoS. NULL-page **read** fault (no write), so
no memory-corruption primitive and no escalation chain. Once `netbt.ko` is
loaded, any local user can panic the machine instantly and repeatedly.

## Fix

`sys/netbt/l2cap_socket.c`: add `break;` after the `ENOPROTOOPT` block, and
`m_freem(m)` the buffer on the success path (the success path also leaked `m` —
finding's bonus point).

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

## Fix validation (Phase 8)

- **Baseline (`with-src` #0):** `./trigger` ⇒ `Fatal trap 12` at
  `l2cap_ctloutput.cold.5+0x8: movl 0x18,%eax` ⇒ guest DOWN (DDB).
- **Patched (`#1`):** `./trigger` ⇒ `getsockopt returned -1 (errno=42
  Protocol not available)` (ENOPROTOOPT from the fixed path), guest UP.

Clean before/after. Fix closes the bug.
