β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3985

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3985 Β· 5 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff applies cleanly; runtime validation needs FireWire hardware absent from the guest.

fix.diff in pack
↓ fix.diffper-fix-DF-3985

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.