# DF-0687 — SCO `sco_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/sco_socket.c:109-121` `sco_ctloutput` `case PRCO_GETOPT`:

```c
m = m_get(M_WAITOK, MT_DATA);                                       // :111
m->m_len = sco_getopt(pcb, sopt->sopt_name, mtod(m, uint8_t *));    // :112
if (m->m_len == 0) {                          // unknown opt OR unconnected
    m_freem(m); m = NULL; err = ENOPROTOOPT;  // :113-117
}                                              // *** MISSING break ***
/* *opt = m; */
/* XXX There are possible memory leaks (Griffin) */                   // :119
sopt_from_kbuf(sopt, mtod(m, void *), m->m_len);                     // :120 mtod(NULL) deref
```

Mechanically identical to **DF-0680** (RFCOMM) and **DF-0682** (L2CAP).
`sco_getopt` (`sys/netbt/sco_upper.c` / `sco_socket.c:354-357`) returns 0 in two
cases: (a) unknown option name (the `default:` case), and (b)
`SO_SCO_HANDLE=2` when the socket is unconnected (`sp_link == NULL`). In both
cases `m_len == 0`, the block runs `m_freem(m); m=NULL; err=ENOPROTOOPT;`, and
**falls through** to `sopt_from_kbuf(sopt, mtod(m, void *), m->m_len)` with
`m==NULL` → page fault at `offsetof(struct mbuf, m_data) == 0x18` → **panic**.

The maintainer flagged this code path years ago — the comment at line 119
`/* XXX There are possible memory leaks (Griffin) */` — but the more serious
bug (the NULL deref) was missed.

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

## Triggers

```
socket(AF_BLUETOOTH=33, SOCK_SEQPACKET, BTPROTO_SCO=4)
getsockopt(fd, BTPROTO_SCO, 0xFFFF /* unknown */, &buf, &len)
        OR
getsockopt(fd, BTPROTO_SCO, SO_SCO_HANDLE=2, &buf, &len)   /* unconnected */
```
→ 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      sco_ctloutput.cold.2+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.

## Fix

`sys/netbt/sco_socket.c`: add `break;` after the `ENOPROTOOPT` block, and
`m_freem(m)` the buffer on the success path. The maintainer's
`XXX … memory leaks (Griffin)` comment is resolved by the success-path free
(we removed the now-stale comment).

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

## Fix validation (Phase 8)

- **Baseline (`with-src` #0):** `./trigger` ⇒ `Fatal trap 12` at
  `sco_ctloutput.cold.2+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.
