# DF-5761 VERDICT

**Status: reproduced** (rig, production code verbatim; deterministic panic)
**Impact: panic** (use-after-free read/walk + indirect-call primitive in
netisr context; realistic ceiling today: local/remote-adjacent kernel
panic DoS — see primitive characterization below).

## Root cause (path:line)

* `sys/netbt/hci_link.c:782-806` — `hci_sco_newconn()` (called from
  `hci_event_con_req()` in **netisr/event context** when an incoming SCO
  Connection Request arrives) walks the **global** `sco_pcb` list:
  `LIST_FOREACH(pcb, &sco_pcb, sp_next)` and, for each listening pcb,
  calls `new = (*pcb->sp_proto->newconn)(pcb->sp_upper, &laddr, &raddr)`
  — holding the raw `pcb` pointer across a callback that allocates a
  socket (`sco_newconn()` → `sonewconn()`).
* `sys/netbt/sco_upper.c:245-246` — `sco_detach()` (the **syscall
  context**, `close(2)` of a listening SCO socket via `sco_sdetach`)
  does `LIST_REMOVE(pcb, sp_next); kfree(pcb, M_BLUETOOTH);` with **no
  lock and no reference** anywhere in netbt (same design gap as the
  DF-5753/5749 family, but a *different list and a different sink*: the
  `sco_pcb` walk of the newconn matching, not the scodone dispatch, and
  not covered by DF-5753's authored fix, which locks the scodone
  dispatch and `sco_disconnect` teardown only).

A walker that has already loaded `pcb` (or is inside the callback)
continues through `LIST_NEXT(pcb, sp_next)` / `pcb->sp_flags` /
`(*pcb->sp_proto->newconn)` on freed — and immediately reusable —
memory.

## Reproduced behaviour (run.log, panic.txt — deterministic)

Rig: production sources compiled verbatim (md5s in setup.log);
`hw.scow_fire=1`:

1. Four listening pcbs attached via production `sco_attach()` +
   `sco_listen()` (exactly what bind()+listen() SCO sockets produce).
2. WALK thread enters `hci_sco_newconn()`, snapshot reaches the first
   listener, calls newconn — the rig callback sleeps 300 ms (sonewconn
   window).
3. CLOSE thread `sco_detach()`s all four (LIST_REMOVE + kfree), then
   re-allocates same-size chunks filled 0xA5 (objcache LIFO reuse —
   models any same-zone M_BLUETOOTH allocation).
4. The walk resumes and dereferences the freed pcb:
   `Fatal trap 12 ... current process = Idle
    Stopped at hci_sco_newconn+0x99: movq 0x18(%rax),%rax`
   — fault virtual address 0x18 = `sp_next` read on a clobbered
   chunk.  First shot, no retries needed.

## Primitive characterization

* UAF **read** through `sp_next` (walk continuation) and `sp_flags`.
* UAF **indirect call** through `sp_proto->newconn` if the freed chunk
  is reused with attacker-shaped content (`sp_proto` is a kernel pointer
  in a same-zone reallocation — reachable in principle by slab grooming
  with M_BLUETOOTH-sized objects; not developed to uid0 here because
  triggering the race requires Bluetooth adjacency/controller events
  (netbt), which constrains the attacker model to local-with-bt-hardware
  or a paired peer).
* The windows recur on *every* incoming SCO connection request racing
  socket close; the callback window (socket allocation) is wide.

## Fix validation (3 iterations — the shipped fix.diff is the final one)

`fix.diff`: `sp_refcnt` (atomic ops) + `sco_pcblock` spinlock;
`sco_attach` inserts under the lock; `sco_detach` removes under the lock
and releases the list reference; `hci_sco_newconn` snapshots+pins
candidates under the lock and runs the callback unpinned-lock.

Iterations (all rig-visible, exactly what fix validation is for):

1. v1: plain `--sp_refcnt` (non-atomic) + missing attach lock →
   deterministic round survived, but the stress phase double-faulted
   (lost decrements → premature kfree → UAF on two cpus).
2. v2: atomic refcounts — but `new` uninitialized when the snapshot
   found no listeners → success path wrote through a garbage pointer
   (`hci_sco_newconn+0x228`, the classic empty-walk path my
   restructure introduced).
3. v3 (shipped): atomic + `new = NULL` init →
   `SURVIVED: 519149322 walks, 12274611 stress rounds, no hit`,
   guest up (fix_run.log).

## Threat

Unprivileged local user who owns a listening SCO socket racing `close(2)`
against an incoming SCO connection request (needs a controller driver on
netbt, as with the whole netbt family), or the remote peer timing
connection requests against the local close.  Ceiling: kernel panic
(reproduced); latent UAF-call primitive.

## Rig notes

The baseline run.log/panic.txt were captured with the pre-marker
harness; the build in the pack carries two extra progress markers in the
CLOSE thread (harmless; used during fix debugging).

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