# DF-5758 — rfcomm_ctloutput PRCO_GETOPT success-path mbuf leak

## Verdict: REPRODUCED + FIX VALIDATED (`fixed`)

The leak is **exactly 1 mbuf per successful
`getsockopt(BTPROTO_RFCOMM, SO_RFCOMM_MTU)`**, deterministic,
unprivileged, no Bluetooth hardware required once netbt.ko is loaded.
The authored `fix.diff` (shared hunk with the DF-5759 fix in the same
file) closes it completely at module level.

## Mechanism (trigger → primitive → effect)

`rfcomm_ctloutput()` is the RFCOMM socket control-output handler
(`sys/netbt/rfcomm_socket.c:92-140`). `PRCO_GETOPT` case, lines 116-127:

```c
case PRCO_GETOPT:
    m = m_get(M_WAITOK, MT_DATA);                        /* :117 alloc */
    crit_enter();
    m->m_len = rfcomm_getopt(pcb, sopt->sopt_name, mtod(m, void *)); /* :119 */
    crit_exit();
    if (m->m_len == 0) {                                 /* :121 err path */
        m_freem(m);                                      /* :122 only free */
        m = NULL;
        error = ENOPROTOOPT;
    }
    soopt_from_kbuf(sopt, mtod(m, void *), m->m_len);    /* :126 copyout */
    break;                                               /* :127 m leaked */
```

For `SO_RFCOMM_MTU`, `rfcomm_getopt()` (`sys/netbt/rfcomm_upper.c:541-543`)
writes `dlc->rd_mtu` and returns `sizeof(uint16_t)` = 2 (non-zero), so the
error block is skipped on every call; `soopt_from_kbuf()` (which does not
take ownership) copies the value out and the mbuf is orphaned — a
permanent 1-mbuf leak per successful getsockopt.

Effect and reachability are identical to DF-5757/DF-0709: the global mbuf
objcache drains monotonically; at pool exhaustion (146632 mbufs max on
this guest) every kernel network allocation fails permanently until
reboot.

## Reachability / threat model

* Precondition: admin `kldload netbt.ko` (optional-bluetooth module).
* Any local user can then `socket(AF_BLUETOOTH, SOCK_STREAM,
  BTPROTO_RFCOMM)`; `rfcomm_ctloutput` only needs `so->so_pcb != NULL`
  (:105), set by `rfcomm_sattach` at socket creation
  (`sys/netbt/rfcomm_socket.c:301-331`). No controller needed.
* Verified as unprivileged uid 1001 (maxx, not in wheel).
* Impact ceiling: permanent local kernel-mbuf-exhaustion DoS. No
  corruption primitive → no escalation chain.

## Reproduction (unpatched netbt.ko)

Two guest boots; linear across 10k / 20k batches (fresh-guest run, after
the l2cap test had already left 10007 in use):

```
=== DF-5758 rfcomm ===
rfcomm: iterations=10000 ok=10000 fail=0 last_val=127 optlen=2
AFTER 10k rfcomm: 20007/146632 mbufs in use (current/max):
rfcomm: iterations=20000 ok=20000 fail=0 last_val=127 optlen=2
AFTER 20k more rfcomm: 40007/146632 mbufs in use (current/max):
```
`last_val=127` = RFCOMM_MTU_DEFAULT, returned correctly. Deltas are
exactly +10000 and +20000. (First-guest capture in run.2.log: 10007 ->
20007 -> 40007.)

## Fix validation (fix.diff applied to /usr/src/sys/netbt, netbt.ko rebuilt)

```
=== PATCHED MODULE: DF-5758 rfcomm ===
rfcomm: iterations=10000 ok=10000 fail=0 last_val=127 optlen=2
AFTER 10k rfcomm: 7/146632 mbufs in use (current/max):
rfcomm: iterations=50000 ok=50000 fail=0 last_val=127 optlen=2
AFTER 50k more rfcomm: 7/146632 mbufs in use (current/max):
```
Flat at 7 across 60k calls; correct value still returned. Module build
rc=0 under `-Werror` (fix_build.log); patched netbt.ko sha256
f7d2c5ad3f5a90eaea99df15faf8a58c92b886ddc0622e4c7b71abe7fd0e1d6e
(shared with DF-5757 validation — both GETOPT hunks in the same build;
the DF-5759 ssend hunk in rfcomm_socket.c was also included, validated
separately in its own pack).

The same fix hunk also removes the DF-0680 error-path NULL-deref panic
(known; not re-verified here).

## Rig notes

Same `bt_getopt_leak.c` binary as DF-5757 with argument `1` (rfcomm).
No changes needed; built first try. attempts=2 (two guest boots).

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