# DF-3985 — sbp(4): `sbp_free_target` early-returns on `luns == NULL` — target slot permanently leaked with a dangling `fwdev` pointer (device-churn DoS), and freed-`fw_device` dereferences in the fw_attach_dev window

## Where
`sys/dev/disk/sbp/sbp.c:1993-2014`:

```c
static void
sbp_free_target(struct sbp_target *target)
{
	...
	if (target->luns == NULL)
		return;                    /* <-- fwdev never cleared, callouts not stopped */
	...
	target->fwdev = NULL;          /* only reached when luns != NULL */
}
```

Two consequences:

**(1) Slot leak / permanent dangling pointer (DoS).**
`sbp_alloc_target` (sbp.c:544-589) sets `target->fwdev = fwdev` and
`target->luns = NULL` (sbp.c:586-587) *before* any LUN exists. If the device
then reports no usable LUN set (`sbp_alloc_lun` leaves `num_lun == 0` /
`luns == NULL`), every later free attempt — `sbp_post_explore` GC
(sbp.c:776-786), the `num_lun == 0` path (sbp.c:816-817), `sbp_detach`
(sbp.c:2040-2041) — hits the early return and **never clears
`target->fwdev`**. `sbp_new_target` only reuses slots with
`fwdev == NULL` (sbp.c:393-397), so the slot is dead forever while holding
a dangling `fwdev` (the `fw_device` is kfree'd by the firewire layer after
`hold_count` missed resets, firewire.c:1600-1611). A hostile device that
presents a CROM_MGM key but zero/absent CROM_LUN entries, repeats this via
bus resets: after 8 rounds (SBP_NUM_TARGETS) the initiator can never attach
any SBP device again until reboot — "increase SBP_NUM_TARGETS!" forever.

**(2) Transient freed-fwdev dereferences.**
`fw_attach_dev` kfrees an absent device's `fw_device` and *afterwards*
calls the children's `post_explore` (firewire.c:1588-1622) — the GC that
would clear `target->fwdev` runs inside that same call. In the window
(and permanently for slots stuck per (1)), code paths that dereference
`target->fwdev` run against freed memory:
- armed `mgm_ocb_timeout` (5 s, sbp.c:1351) fires after the device died:
  `sbp_mgm_timeout` → `sbp_reset_start` → `sbp_write_cmd` reads
  `sdev->target->fwdev->speed` / `->dst` (sbp.c:1256, 1273);
- `sbp_probe_target` reads `target->fwdev->csrrom/status` (sbp.c:663)
  whenever reached with a stale pointer;
- a hostile bus peer can inject a status write for the dead target's FIFO
  address at any time (physical-request writes default-on, DF-1009),
  driving `sbp_recv1` → `sbp_agent_reset` → `sbp_write_cmd` into the same
  freed-fwdev reads.

Reads of a freed M_FW slab (`speed`, `dst`) feed packet headers —
freed-memory content disclosure onto the bus and garbage `spd` in xfer
setup; the slot leak is a deterministic attach-DoS.

## Severity
Low/Medium (device-gated DoS + narrow UAF-read). Source-certain;
hardware-gated runtime (no FireWire on the audit guest —
`../DF-3981/env.txt`) → inconclusive end-to-end.

## Fix
`fix.diff` (same restructure as DF-3981's second hunk): stop both callouts
and clear `fwdev` on **every** exit path of `sbp_free_target`; wrap the
luns-dependent body in `if (target->luns != NULL)`.
