# DF-3985 VERDICT — sbp_free_target early return: slot leak + dangling fw_device

**Status: inconclusive (hardware-gated; no FireWire controller on the audit
guest). Source-level certainty: certain for the leak/dangling-pointer
logic; the transient freed-fwdev dereferences are certain as code paths,
their firing windows are timing-dependent.**

## Proof of the leak (pure control flow)

- `sbp_alloc_target` (sbp.c:544-589): on success sets `target->fwdev =
  fwdev` (sbp.c:563), `target->luns = NULL; target->num_lun = 0`
  (sbp.c:586-587) and returns the target to `sbp_post_explore`, which
  immediately calls `sbp_probe_target` (sbp.c:815).
- `sbp_probe_target` → `sbp_alloc_lun` (sbp.c:403-542): if the device's
  Config ROM contains no `CROM_LUN` keys, `maxlun` stays 0; `maxlun(0) ==
  num_lun(0)` so `luns` remains NULL and `num_lun` remains 0.
- Back in `sbp_post_explore`: `if (target->num_lun == 0)
  sbp_free_target(target);` (sbp.c:816-817) → **early return at
  sbp.c:1998-1999** (`luns == NULL`) → `target->fwdev` not cleared.
- Every subsequent GC round (sbp.c:776-786) and `sbp_detach`
  (sbp.c:2040-2041) hits the same early return. `sbp_new_target` only
  allocates slots with `targets[i].fwdev == NULL` (sbp.c:393-394), so the
  slot is permanently unusable and holds a pointer to memory the firewire
  layer frees after `hold_count` missed resets (firewire.c:1600-1611,
  `kfree(fwdev, M_FW)` at 1610).
- Hostile-device recipe: present CROM with a valid CROM_MGM management
  agent (sbp.c:568-573 requires it) but no CROM_LUN entries; vanish; repeat
  with a new EUI-64 identity per round. 8 rounds (SBP_NUM_TARGETS) exhaust
  all slots → "increase SBP_NUM_TARGETS!" (sbp.c:557) forever → SBP
  attach DoS until reboot. Each dead slot also retains a dangling `fwdev`
  pointer indefinitely.

## Freed-fwdev dereference sites reachable with a stale target->fwdev

- `sbp_write_cmd`: `min(sdev->target->fwdev->speed, max_speed)`
  (sbp.c:1256) and `FWLOCALBUS | sdev->target->fwdev->dst` (sbp.c:1273) —
  reached from `sbp_reset_start` via `sbp_mgm_timeout` (armed 5 s per mgm
  ORB, sbp.c:1351; timeout body sbp.c:2126-2146 → 2144), and from
  `sbp_agent_reset` driven by any received status for the dead target's
  FIFO address (sbp_recv1 → done: reset_agent → sbp_agent_reset,
  sbp.c:1784-1785, 1060-1081). Reads freed M_FW slab.
- `sbp_probe_target`: `SBP_FWDEV_ALIVE(target->fwdev)` derefs
  `fwdev->status` and `fwdev->csrrom` (sbp.c:653-654, 663) — reachable
  when post_explore matches a live fwdev to a *different* slot while this
  slot's stale pointer is compared (comparison-only in that loop, but the
  probe path is entered with the live one; the stale deref needs the
  mgm-timeout/injection arms above).
- Window: `fw_attach_dev` kfrees absent fwdevs and only then runs the
  children's `post_explore` (firewire.c:1588-1622), so any armed mgm/ccb
  timeout or injected status firing inside that window (or later, for
  slots stuck per the leak) dereferences freed memory.

Impact: deterministic attach-DoS (certain) + freed-slab reads feeding
FireWire packet fields `spd`/`dst` (bus-visible behavior of freed memory;
narrow). Low/Medium.

## Fix validation
Not runnable on this guest (no FireWire hardware). fix.diff restructures
`sbp_free_target` so every exit clears `fwdev` and stops both callouts
(scan_callout was also never stopped on this path) — identical to the
second hunk of DF-3981's fix.diff, kept as a standalone diff here.

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