# DF-3480 — ng_btsocket_l2cap_data_input appends to so_rcv without the sockbuf token: races soreceive's unlink surgery (lost wakeups / torn record list)

## What

`ng_btsocket_l2cap_data_input()` delivers received L2CAP packets to the
socket with:

```c
	sbappendrecord(&pcb->so->so_rcv.sb, m);      /* :1455 (normal path)  */
	sbappendrecord(&pcb->so->so_rcv.sb, copy);   /* :1529 (broadcast)    */
	sorwakeup(pcb->so);
```

holding only `pcb->pcb_lock` (+ `ng_btsocket_l2cap_sockets_lock`).
`sbappendrecord()` (sys/kern/uipc_sockbuf.c:176) is **caller-serialized**:
it reads `sb->sb_mb`, then writes through `sb->sb_lastrecord->m_nextpkt`,
then updates `sb_lastrecord/sb_lastmbuf` — with no internal lock and no
token taken by this caller.

The consumer — `soreceive()` on the user thread — performs the record
unlink surgery under `so->so_rcv.ssb_token`
(sys/kern/uipc_socket.c:1683/1831, `sbunlinkmbuf()`), which the SWI-task
appender does not hold. The two contexts share **no common lock**:

* append side: taskqueue_swi thread (`ng_btsocket_l2cap_input` ←
  `taskqueue_run` ← `ithread_handler` — the panic.txt stacks from DF-3479
  prove this context).
* consume side: user threads blocked in read(2).

Interleavings where the appender dereferences a `sb_lastrecord` that
soreceive has just unlinked (or where a wakeup is issued between the
reader's queue check and its sleep) produce:

* lost wakeups — readers sleep forever while records are queued;
* torn record chains — records linked to a detached tail, `sb_cc` diverging
  from `sb_mb` (silent data loss / permanent socket wedge);
* in the worst case a write through a freed mbuf when the unlinked
  last-record is freed before the append's `m_nextpkt` store.

This is a FreeBSD-port assumption: FreeBSD's sockbuf operations are
internally locked (SOCKBUF_LOCK inside sb*), so the ng_btsocket code never
learned to take the lock; DragonFly's sockbuf API requires the caller to
serialize, and this port does not.

## Runtime evidence (see run.log)

With the DF-3478 (listen recursion) and DF-3479 (missing soreference) fixes
applied so the inbound path works at all, the btsbx.ko harness drove an
inbound connection to OPEN and flooded it while 8 user threads read:

* 16.8M packets accepted in 240 s, zero netgraph-level drops;
* the 8 readers performed **nine** reads total, then slept the rest of the
  run;
* afterwards `FIONREAD` reported **4352 bytes queued** and a fresh
  `select()+read()` returned another record — readers asleep while the
  kernel counts 25+ queued records is impossible with a correctly
  serialized sockbuf.

No kernel memory corruption was captured in ~10 minutes of racing, so the
memory-safety ceiling (freed-mbuf write) is **not** claimed as reproduced;
the reproduced manifestation is the lost-wakeup/wedge anomaly above.

## Fix direction

Take `so->so_rcv.ssb_token` (lwkt token) around the append + wakeup in
`ng_btsocket_l2cap_data_input()` (both the normal and broadcast paths), i.e.
the same token soreceive holds during its surgery:

```diff
+		lwkt_gettoken(&pcb->so->so_rcv.ssb_token);
 		sbappendrecord(&pcb->so->so_rcv.sb, m);
+		lwkt_reltoken(&pcb->so->so_rcv.ssb_token);
 		sorwakeup(pcb->so);
```

(the send path's so_snd manipulation under pcb_lock only has the analogous
exposure vs. any non-protocol-thread consumer of so_snd.)

## Harness

`btsbx.c` (kernel module: fake ng_l2cap lower layer — connects to
`btsock_l2c`, registers a source bdaddr via NGM_L2CAP_NODE_HOOK_INFO,
drives CON_IND/CON_RSP/CFG_IND/CFG/CFG_RSP to OPEN, then floods L2CAP data
from a kthread; sysctl debug.btsbx.cmd 1=setup 2=con_ind 3=cfg_ind 4=cfg
5=flood-on 6=flood-off), `df3478_drive.c` (userland: listener/accept +
8 reader threads + FIONREAD/drain probes). Makefile for the module.
Requires the DF-3478 and DF-3479 fixes applied first.
