# DF-5753 — scodone completion dispatch races syscall-context SCO link teardown: NULL/UAF write in hci_sco_complete

**File:** `sys/netbt/hci_unit.c` (pass 2) — `hci_intr()` scodone branch
**Trigger surface:** `hci_complete_sco` + the `hci_scodone` membership walk

## Build (inside the guest, as root)

```
cd /root/hciuu
sh build.sh          # verbatim production netbt sources (md5s recorded)
```

## Run (inside the guest, as root)

```
kldload /root/hciuu/hci_unit_harness.ko
sysctl hw.hciu_setup=1          # attach fake unit (production hci_attach)
sysctl hw.hcius_fire=1500       # 1500 rounds of [200 SCO sends + sco_disconnect(pcb,0)]
```

## Expected

Within the first rounds (baseline observed the crash immediately after
round 0):

```
Fatal trap 12: page fault while in kernel mode
fault virtual address	= 0x1c
fault code		= supervisor write data, page not present
Stopped at      hci_sco_complete+0x7:   subl   $0x1,0x1c(%rax)
```

i.e. `link->hl_sco->sp_pending--` (hci_link.c:884-892) with
`hl_sco == NULL` on the netisr thread (`current process = Idle`).

## Mechanism

* `sco_send()` (sco_upper.c:310) tags each TX mbuf with
  `M_SETCTX(m, pcb->sp_link)`; the controller completes it via
  `hci_complete_sco()` (hci_unit.c:538-547) → `hci_scodone` + netisr.
* `hci_intr()`'s scodone branch (hci_unit.c:395-400) walks
  `unit->hci_links` and calls `hci_sco_complete(link, 1)` when the ctx
  matches — **after** releasing `hci_devlock` and with no reference on
  the link.
* The socket-close path `sco_sabort()` → `sco_disconnect(pcb, 0)`
  (sco_socket.c:253, sco_upper.c:213-217) runs on the *syscall* thread:
  it NULLs `link->hl_sco` **before** `hci_link_free()` removes the link
  from the list (hci_link.c:1019-1023). Any scodone mbuf dispatched in
  that window matches the still-listed link and dereferences the NULL'd
  `hl_sco` — a deterministic NULL write at `hl_sco + 0x1c` in netisr
  context. The same window extended past the `TAILQ_REMOVE`+`kfree` is a
  use-after-free write (`sp_pending--` on freed memory, or a wild
  `sp_proto->complete` call when the chunk is reused).

## Fix check

Apply `fix.diff` (scodone dispatch under `hci_devlock` in hci_intr;
`sco_disconnect` teardown under the same lock — unit pointer saved before
`hci_link_free`, which frees the link), rebuild, reboot, re-run
`hcius_fire=1500`: **all 1500 rounds complete, no panic** (fix_run.log;
~28k completions processed through the locked dispatch).
