# DF-5760 VERDICT

**Status: reproduced** (rig, production code verbatim; deterministic; guest up)
**Impact: dos** — remote-peer-triggered permanent lockout of local RFCOMM
outbound connections + rogue DLC/socket allocation in the wrong DLCI space.

## What was run

KLD rig `rfcomm_pn_harness` (see README): production `rfcomm_session.c`,
`rfcomm_dlc.c`, `rfcomm_upper.c` copied byte-identical from
`/usr/src/sys/netbt` (md5s in setup.log), compiled against a stub L2CAP
layer.  Frames from the "remote peer" are injected through the production
entry point `rfcomm_session_input()` — the same callback the real L2CAP
layer invokes.  Upper-layer callbacks stand in for the socket layer
(`rfcomm_sock_newconn()` = sonewconn + return so->so_pcb).

## Root cause (path:line)

* `sys/netbt/rfcomm_session.c:1322-1331` — `rfcomm_session_recv_mcc_pn()`
  PN-command path calls `rfcomm_dlc_newconn(rs, pn.dlci)` with no
  direction validation (contrast `rfcomm_session_recv_sabm()`
  at rfcomm_session.c:738-742, which *does* check).
* `sys/netbt/rfcomm_dlc.c:88` — `chan = RFCOMM_CHANNEL(dlci)` and the
  listener matching at rfcomm_dlc.c:92-111 consider only the channel,
  never the direction bit.
* `sys/netbt/rfcomm_dlc.c:128-140` — the fabricated DLC is stamped with
  the peer-chosen (wrong-direction) `rd_dlci` and inserted on the session.

## Reproduced behaviour (run.log, deterministic, first shot)

1. Local service listening on channels 1..30; responder session opened
   by the peer (SABM(0)); baseline local `rfcomm_connect(ch 4)` accepted
   (DLCI 9 — confirming local DLCIs are the odd/responder direction).
2. One PN command with DLCI 7 (channel 3, responder direction = OUR
   direction) → production `rfcomm_dlc_newconn()` matched the channel-3
   listener, called its newconn (socket child created in production),
   stamped `rd_dlci = 7`, inserted it: `POISON-CONFIRMED`.
3. Legit SABM(6) still opens DLCI 6 → the same channel 3 now hosts TWO
   DLCs, one of them bogus (`DUP-CHANNEL`).
4. Every subsequent local `rfcomm_connect(ch 3)` hits
   `rfcomm_upper.c:181-182` (`rfcomm_dlc_lookup(rs, 7) != NULL`) →
   **EBUSY forever** (`LOCAL-CONNECT ... -> 16`).
5. Sweeping the poison over all 30 channels: 28/29 channels refuse local
   connect with EBUSY (`POISON-SWEEP`) — the peer can lock the local
   RFCOMM client out of every server channel.  (The 29th, ch16, is
   blocked by the *separate* channel-mask bug DF-5762: `16 & 0x2f = 0`
   matches no listener.  Every other channel ≥ 17 was still poisoned —
   via the wrong listener, see DF-5762.)

Each planted DLC allocates a socket (sonewconn) + DLC in production;
they linger in `RFCOMM_DLC_WAIT_CONNECT` until the session dies
(`recv_sabm` rejects the wrong-direction SABM that would complete them).

## Threat

The remote peer of any established RFCOMM session (Bluetooth adjacency,
L2CAP PSM 3): permanent DoS of *local outbound* RFCOMM connections to
that peer, plus unkillable rogue half-open DLC/socket pairs.  No memory
corruption (the planted DLCs are correctly refcounted by the socket
layer); severity Low/Medium boundary — filed Low→Medium given the
whole-channel lockout.  Precondition: netbt loaded + RFCOMM session
between attacker and victim service.

## Fix validation

`fix.diff` adds the missing direction check to `recv_mcc_pn` (identical
in form to `recv_sabm`'s).  Applied inside the guest's `/usr/src`
copy; rig rebuilt from the patched verbatim sources; same harness run:

* PN(DLCI 7) now answered with DM, no DLC created
  (`DUP-CHANNEL ... planted DLCI 7 (state -1)`, `newconn calls: 2`).
* `LOCAL-CONNECT: rfcomm_connect(ch 3) -> 0` — **accepted**.
* `POISON-SWEEP: 1/29` (the single EBUSY is ch4's own legitimate
  baseline DLC from step 1).
* Guest alive (`uptime` in transcript).

## Rig notes / deviations

* The first rig iteration panicked in harness code: the DF-5733 stub's
  `l2cap_sockaddr()` returned a fixed bdaddr so each of the 30 listeners
  created its own listening session and exhausted the stub's channel
  table (`rfcomm_session_alloc()` → NULL, compiler-hoisted
  `rs->rs_l2cap` load faulted).  Fixed by making the stub report
  BDADDR_ANY (the natural "bind any" setup) so listeners share one
  session, exactly as production does when binding ANY.
* Two rig runs were lost to a stale module load (KLD unload is EBUSY);
  reboot between runs is required — noted in run.sh.

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