# DF-5754 — hci_detach frees the unit with no lifetime protection for in-flight netisr processing: unit UAF

**File:** `sys/netbt/hci_unit.c` (pass 2) — `hci_attach`/`hci_detach`,
unit list, `hci_unit_lookup`
**Trigger surface:** driver detach (kldunload / USB hot-unplug) racing
rx traffic

## 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            # base unit (also registers netisr)
sysctl hw.hciud_fire=300          # 300 rounds of [attach + flood + detach]
                                   # (hciud_fire=2 uses the slow inquiry-16
                                   #  payload of the original baseline)
```

## Expected (baseline, unfixed)

The guest dies within the first rounds:

```
Fatal trap 12: page fault while in kernel mode
cpuid = 0
fault virtual address	= 0x90
fault code		= supervisor read data, page not present
current process		= Idle
Stopped at      lockmgr_exclusive+0x5d:   orl   (%r14),%esi
```

`hci_detach()` (hci_unit.c:120-130) runs `hci_disable()`, removes the
unit from `hci_unit_list` (under a `crit_enter()` that only defers
interrupts on the *current* CPU) and `kfree`s it — while `btintr()`
(bt_input.c:37) is walking the same list and `hci_intr()` is mid-drain on
the netisr thread (`goto another` re-locks `&unit->hci_devlock` on every
packet, hci_unit.c:335). The freed unit's `hci_devlock` is then locked
through freed (reused) memory. The driver-rx side
(`hci_input_event(unit)` in flight during detach) has the same lifetime
hole; `hci_unit_lookup()` (hci_unit.c:265-279) likewise returns raw,
unreferenced unit pointers that sockets and upper layers retain.

## Fix check

Apply `fix.diff` (spinlock-guarded list + `hci_refcnt` acquired by the
netisr walker via a snapshot, `hci_detach()` waiting the count out
before `kfree`; the driver remains responsible for quiescing its own rx
before detach, as documented in the diff) and re-run `hciud_fire=300`:
**all 300 rounds survive, guest up** (fix_run.log).
