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

Broken attach-unwind contract: mps_attach leaks sc->facts on early failures, and mps_pci_free destroys the parent DMA tag over live child tags when mps_free early-returns

Summary

mps_attach returns error without unwind at two sites (facts kmalloc leak), and whenever mps_free early-returns (exactly when the IOC is wedged) mps_pci_free destroys mps_parent_dmat over six live child tags - bus_dma_tag_destroy has no parent-child refcount, so dmat->parent dangles and all queue DMA memory leaks. HW-gated (no SAS2008 on guest); unconditional control flow, source-proven. Free+NULL fix + NULL guards riding with DF-2044.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5014 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
fix.diff β€” 839 B view raw
manifest.json β€” 1018 B view raw
verdict.json β€” 3.0 KB view raw

DF-5014 β€” mps attach unwind: sc->facts leak and parent DMA tag destroyed over live children

What

The unwind contract between mps_pci_attach() (sys/dev/raid/mps/mps_pci.c:234-237) and mps_attach() (sys/dev/raid/mps/mps.c:1024-1215) is broken on two families of early-failure paths β€” beyond the known DF-2044 (never-allocated IRQ release, not re-reported here):

  1. sc->facts leaked β€” mps_attach() returns error without calling mps_free() at: - mps.c:1043-1044 (mps_get_iocfacts fails: IOC wedged / not answering), - mps.c:1072-1073 (mps_transition_ready fails after the diag reset forced by missing EVENT_REPLAY capability). The kmalloc(sizeof(MPI2_IOC_FACTS_REPLY)) from mps.c:1041-1042 is never freed; mps_pci_free() does not know about it.

  2. parent DMA tag destroyed over live child tags β€” whenever mps_free() bails out early (mps.c:1323-1325 mps_detach_sas/mps_detach_log error, mps.c:1331-1334 mps_transition_ready error β€” realistic exactly when the IOC is wedged, e.g. after mps.c:1128-1133 transition-operational failure), the queue/request/reply/sense/chain/buffer DMA tags and their memory stay allocated while control returns to mps_pci_attach(), whose mps_pci_free() then destroys mps_parent_dmat (mps_pci.c:289-291). bus_dma_tag_destroy() (sys/platform/pc64/x86_64/busdma_machdep.c:334-346) keeps no parent↔child reference count: the parent tag memory is freed silently, leaving every child tag with a dangling dmat->parent pointer (latent UAF of the tag object) and leaking all queue DMA memory (map_count on the parent is 0, so the EBUSY guard never fires).

Both fire only at boot/hotplug on failing hardware (wedged or absent IOC mid-attach) β€” resource-leak / latent-dangling-pointer class, no attacker- controlled input. Same family context: DF-2044 (known), DF-4868/4869, DF-4363.

Reproduction status

HW-gated: no LSI SAS2008-class controller on the audit guest, mps cannot attach; both defect paths are unconditional control flow (cited above), no runtime PoC possible on this guest.

Fix

fix.diff: free sc->facts (and NULL it) on the two early-error returns in mps_attach(). DF-2044's NULL-guard for the IRQ teardown in mps_pci_free() should ride along in the same commit (not duplicated here to keep this diff scoped to the new finding).

VERDICT.md
↓ download raw

DF-5014 β€” VERDICT

Status: NOT REPRODUCED at runtime β€” hardware-gated (no LSI SAS2008/ MPT2-family controller on the audit guest; mps cannot be attached). Both defect paths are unconditional control flow, line-traced below against the audited tree. House standard for HW-gated driver findings (DF-2066, DF-3898): source-proven, runtime-gated.

Impact ceiling: resource leak (small one-shot sc->facts leak; unbounded queue-DMA leak + leaked child DMA tags on the mps_free-early-return family) plus a latent dangling dmat->parent in every surviving child tag. No attacker-controlled input on the path; trigger is a wedged IOC at boot or hotplug. Low severity, leak class.

Mechanism (source-proven)

  1. mps_pci_attach() (mps_pci.c:182-238) allocates the register resource and the parent DMA tag, then calls mps_attach(); every non-zero return funnels into mps_pci_free() (mps_pci.c:234-237).
  2. mps_attach() returns error without unwinding at exactly two sites: - mps.c:1043-1044: mps_get_iocfacts() failure β€” sc->facts was kmalloc'd at mps.c:1041-1042 one statement earlier; nothing frees it. mps_pci_free() releases only dmat/irq/regs (mps_pci.c:286-305). - mps.c:1072-1073: mps_transition_ready() failure after the EVENT_REPLAY-forced mps_diag_reset() β€” same leak.
  3. mps_free() early-returns (leaving all queue DMA tags + memory allocated): - mps.c:1323-1325 (mps_detach_log/mps_detach_sas failure), - mps.c:1331-1334 (mps_transition_ready failure β€” precisely the state reached when the IOC is wedged, e.g. after the transition-operational failure at mps.c:1128-1133). Control then returns to mps_pci_attach() β†’ mps_pci_free() β†’ bus_dma_tag_destroy(sc->mps_parent_dmat) (mps_pci.c:289-291).
  4. bus_dma_tag_destroy() (busdma_machdep.c:334-346) has no parent-child refcount β€” it checks only map_count (which counts maps created from this tag; the child tags' maps count against the children). The parent tag memory is kfree'd while queues_dmat/req_dmat/reply_dmat/ sense_dmat/chain_dmat/buffer_dmat still reference it as dmat->parent β†’ dangling pointer in live kernel objects (latent UAF of tag memory; benign only because nothing subsequently allocates maps from those tags on a failed attach).

Exploit chain

None (leak class; no attacker-controlled data on the path).

Fix

fix.diff β€” free sc->facts at both early-error returns in mps_attach(). git apply --check clean. Single-fix kernel build not meaningful on this guest (entry hardware-gated); fix validated by inspection.

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 (git apply --check clean) frees sc->facts at both early-error sites. Single-fix kernel build not meaningful: entry is hardware-gated (no SAS2008 on guest); fix validated by inspection.

fix.diff; VERDICT.md 'Fix' section
↓ fix.diffper-fix-DF-5014

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md mechanism section with the four cited sites; the leak paths need no concurrency - only a failing IOC at attach

PoC changes

authored from source analysis; no runnable PoC possible (no mps hardware on guest)

Verified recommended fix

kfree sc->facts (and NULL it) on the two early-error returns in mps_attach() - see fix.diff; ride along with DF-2044's NULL guards in mps_pci_free.

Verdict

Hardware-gated: no LSI SAS2008/MPT2 controller on the audit guest, so mps_attach cannot run; both defect paths are unconditional control flow in the source. (1) mps_attach() returns error without any unwind at mps.c:1043-1044 and mps.c:1072-1073, leaking the sc->facts allocation made at mps.c:1041-1042 (mps_pci_free does not free it). (2) Whenever mps_free() early-returns (mps.c:1323-1325, 1331-1334 - realistic exactly when the IOC is wedged, e.g. after the transition-operational failure at mps.c:1128-1133), all queue DMA tags and memory remain allocated while mps_pci_attach's unwind calls mps_pci_free() -> bus_dma_tag_destroy(mps_parent_dmat) (mps_pci.c:289-291); bus_dma_tag_destroy (busdma_machdep.c:334-346) has no parent-child refcount, so the parent tag is silently freed over live child tags (dangling dmat->parent in six live kernel objects) and all queue DMA memory leaks. Boot/hotplug-time resource-leak + latent dangling-pointer class; no attacker-controlled input.