# DF-3981 VERDICT — sbp(4) login_callout use-after-free on sdev free paths

**Status: inconclusive end-to-end (hardware-gated: guest has no FireWire
OHCI controller — see env.txt), mechanism PROVEN on the guest kernel
(callout fires on kfree'd container, two runs, two boots). Source-level
certainty: certain.**

## Root cause (path:line, sys/dev/disk/sbp/sbp.c)

`sdev->login_callout` (sbp.c:183) is armed by `sbp_login()`:

```c
649:	callout_reset(&sdev->login_callout, ticks,
650:			sbp_login_callout, (void *)(sdev));
```
with `ticks` up to `login_delay` (default 1000 ms, sysctl-writable), for
every newly seen/RESET LUN (`sbp_probe_target`, sbp.c:689-694, `auto_login`
default 1; also `sbp_reset_start_callback` sbp.c:864-868, RCN failure
sbp.c:1727).

The callback dereferences the argument immediately:

```c
625: static void sbp_login_callout(void *arg)
626: {
627: 	struct sbp_dev *sdev = (struct sbp_dev *)arg;
628: 	sbp_mgm_orb(sdev, ORB_FUN_LGI, NULL);
629: }
```

Three sdev-free paths never stop the callout:

1. **sbp_alloc_lun stale-LUN invalidation** (sbp.c:436-447): when the
   device's Config ROM (attacker-controlled content) reports a smaller LUN
   set after a bus reset, LUNs ≥ maxlun are freed:
   `sbp_cam_detach_sdev(sdev); sbp_free_sdev(sdev);` (sbp.c:444-445).
2. **sbp_alloc_lun trailing !VALID_LUN loop** (sbp.c:534-541): any LUN whose
   CROM_LUN entry disappeared from the ROM is freed even when the array is
   not shrunk.
3. **sbp_post_explore garbage collection** (sbp.c:776-786, 816-817):
   `sbp_free_target()` (sbp.c:1993-2014) → `sbp_free_sdev()` (sbp.c:1978-1990)
   → `kfree(sdev, M_SBP)` (sbp.c:1989) — the only callout it stops is
   `target->mgm_ocb_timeout` (sbp.c:2000); `login_callout` (per-sdev) is
   not touched anywhere in these functions (rg confirms the only
   `callout_stop(&sdev->login_callout)` in the driver is `sbp_logout_all`
   sbp.c:1959, reached only from `sbp_shutdown`/`sbp_detach`).

After the kfree, the armed callout fires `sbp_login_callout(freed_sdev)`:
`sdev->target` is read from freed slab memory; `sbp_get_ocb(sdev)` walks the
freed `free_ocbs` STAILQ head (freed-slab read → attacker-influenceable
pointer if the slab is recycled); the resulting "ocb" is written through
(`ocb->flags = OCB_ACT_MGM`, `ocb->sdev = sdev`, `bzero(ocb->orb)`,
`ocb->orb[0..7] = ...` — sbp.c:1306-1311,1319-1325) into the
`fwdma_free`'d ORB page pool; `callout_reset(&target->mgm_ocb_timeout, 5*hz,
..., ocb)` re-arms with the dangling ocb (sbp.c:1351). This is a
use-after-free read + write of kernel heap, plus later FireWire writes of
ORB contents into freed pages (`sbp_write_cmd` → `fw_asyreq`).

The window is deterministic for the attacker: `login_delay` default 1000 ms
between arming and firing; the hostile device triggers the bus reset /
ROM change inside that window.

## Runtime verification performed (guest, DF 6.5-DEVELOPMENT)

Mechanism replica (`sbp_replica.c`, mechanism A): allocate an object
containing an armed 2 s callout, `kfree()` the object with no stop, observe
the callback execute with the freed pointer:

```
sbpprep[A]: armed login_callout in 0xfffff80117817880, tick=3940
sbpprep[A]: kfree(0xfffff80117817880) DONE - callout still armed, no callout_stop() (sbp.c:1978 has none)
sbpprep[A]: login_callout FIRED on FREED object 0xfffff80117817880 (arg == freed pointer: YES)
```
(run.log; identical result on the pre-reset boot with pointer 0xfffff8008bb64460.)

The end-to-end sbp path additionally requires a FireWire OHCI controller
plus an SBP-2 device; `pciconf -lv` on the guest shows none (env.txt), so
the full UAF cannot be triggered on this guest — hence inconclusive rather
than reproduced. Nothing in the path is guest-specific: the callout
subsystem behavior is the shipped kernel's (kern_timeout.c of this tree).

## Not-a-duplicate check
Known sbp.c findings are DF-1317 (INQUIRY bcopy), DF-1318 (NULL cam_path),
DF-2514 (CALC_GEOMETRY div0), DF-2515 (16-byte CDB orb overflow), DF-2516
(payload-length). None covers callout lifetime. The firewire.c-side findings
(fwdev/tlabel/ioctl classes) do not cover per-sdev callouts in sbp.c.

## Fix
`fix.diff`: stop `login_callout` in `sbp_free_sdev` (covers all three free
paths) and also stop `scan_callout` in `sbp_free_target` (currently only
stopped via `sbp_cam_detach_target`). Cannot be validated on this guest
(no FireWire hardware; sbp is compiled per sys/conf/files and module build
of the driver alone would not exercise the free path without hardware).

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