sbp_free_target early-returns on luns==NULL without clearing target->fwdev or stopping scan_callout β target slots permanently leaked holding dangling fw_device pointers (8-round hostile device-churn attach DoS), plus freed-fw_device derefs via armed mgm/ccb timeouts
Summary
A device whose ROM has a management agent but no CROM_LUN keys leaves the slot with luns==NULL, and every later sbp_free_target hits the early return so fwdev is never cleared. sbp_new_target only reuses fwdev==NULL slots -> 8 hostile churn rounds exhaust SBP_NUM_TARGETS permanently. The dangling fw_device is kfree'd later; in the window, armed mgm_ocb_timeout -> sbp_reset_start -> sbp_write_cmd reads fwdev->speed/dst from freed memory. fix.diff restructures sbp_free_target so callout stops and fwdev=NULL run on every exit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3985 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| fix.diff | β | 1.4 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 2.9 KB | view raw |
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:
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).
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 setstarget->fwdev = fwdev(sbp.c:563),target->luns = NULL; target->num_lun = 0(sbp.c:586-587) and returns the target tosbp_post_explore, which immediately callssbp_probe_target(sbp.c:815).sbp_probe_targetβsbp_alloc_lun(sbp.c:403-542): if the device's Config ROM contains noCROM_LUNkeys,maxlunstays 0;maxlun(0) == num_lun(0)solunsremains NULL andnum_lunremains 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->fwdevnot cleared. - Every subsequent GC round (sbp.c:776-786) and
sbp_detach(sbp.c:2040-2041) hits the same early return.sbp_new_targetonly allocates slots withtargets[i].fwdev == NULL(sbp.c:393-394), so the slot is permanently unusable and holds a pointer to memory the firewire layer frees afterhold_countmissed 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
fwdevpointer indefinitely.
Freed-fwdev dereference sites reachable with a stale target->fwdev
sbp_write_cmd:min(sdev->target->fwdev->speed, max_speed)(sbp.c:1256) andFWLOCALBUS | sdev->target->fwdev->dst(sbp.c:1273) β reached fromsbp_reset_startviasbp_mgm_timeout(armed 5 s per mgm ORB, sbp.c:1351; timeout body sbp.c:2126-2146 β 2144), and fromsbp_agent_resetdriven 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)derefsfwdev->statusandfwdev->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_devkfrees absent fwdevs and only then runs the children'spost_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.
Fix verification
not_testablefix.diff applies cleanly; runtime validation needs FireWire hardware absent from the guest.
fix.diff in pack
Confirmed kernel references
Detail
Exploit chain
crafted FireWire device churn x8 -> all 8 target slots leaked with dangling fwdev -> no SBP storage attachable until reboot (DoS); transient freed-slab reads leak bus-relevant fields into packet setup.
Evidence (decisive lines)
VERDICT.md (control-flow proof of the leak + deref windows); fix.diff (restructure sbp_free_target: stop both callouts, clear fwdev on all exits - patch --dry-run clean)
PoC changes
No runnable PoC (no FireWire hardware); leak proven by pure control-flow trace in VERDICT.md.
Verified recommended fix
Restructure sbp_free_target so callout_stop(scan+mgm) and fwdev=NULL run on every exit path; wrap luns-dependent teardown in if (luns != NULL) (fix.diff).
Verdict
sbp_free_target() (sbp.c:1993-2014) early-returns when target->luns == NULL without clearing target->fwdev or stopping target->scan_callout. sbp_alloc_target (sbp.c:544-589) installs fwdev with luns==NULL before any LUN exists, so a device whose Config ROM has a management agent but no CROM_LUN keys leaves the slot in that state; every later free (post_explore GC sbp.c:776-786, num_lun==0 sbp.c:816-817, detach sbp.c:2040-2041) hits the early return. sbp_new_target only reuses fwdev==NULL slots (sbp.c:393-394), so 8 rounds of hostile device churn (new EUI-64 each) exhaust SBP_NUM_TARGETS -> permanent SBP attach DoS until reboot, each dead slot holding a dangling fw_device that firewire.c kfrees after hold_count missed resets (firewire.c:1600-1611). Additionally, in the window before GC (and indefinitely for stuck slots), armed mgm_ocb_timeout (sbp.c:1351, body sbp.c:2126-2146 -> sbp_reset_start -> sbp_write_cmd) and injected status packets for the dead target's FIFO address dereference the freed fw_device (fwdev->speed sbp.c:1256, fwdev->dst sbp.c:1273). End-to-end inconclusive: guest has no FireWire controller (../DF-3981/env.txt); the leak control-flow is source-certain.
No comments yet.