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

tws_attach error unwinds leak the MSI allocation and every post-probe resource (~200 KB heap + DMA maps/tags + scan_ccb per failed attach)

Summary

The attach_fail unwinds release only the cdev/IRQ/BARs - never pci_release_msi (detach does, proving the omission) and never reqs/sense_bufs/scan_ccb/aen_q/trace_q/per-req maps/cmd DMA/three tags. ~200KB + an MSI vector per failed attach; failure is most reachable with wedged firmware (the DF-1924/1925 state) where operators retry load/unload - unbounded aggregate leak. Release-everything-in-unwind fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4591 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.4 KB ↓ raw
trigger.sh β€” 2.0 KB view raw
run.sh β€” 250 B view raw
run.log β€” 425 B view raw
build.log β€” 212 B view raw
env.txt β€” 209 B view raw
guest_probe.log β€” 677 B view raw
fix.diff β€” 3.3 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 3.6 KB view raw

DF-4591 β€” tws(4) attach-failure unwinds leak MSI vectors and all post-probe allocations

Finding file: sys/dev/raid/tws/tws.c (pass 2) Lines: 309-341 (attach_fail_4/3/2/1) vs allocations at 202 (trace_q), 426 (MSI), 465-530 (bus_dma tags + dma_mem/cmd_map), 535-539 (reqs, sense_bufs, scan_ccb), 580-581 (aen_q), 610 (per-req dma maps).

What the bug is

tws_detach() carefully releases the MSI allocation (if (sc->intr_type == PCI_INTR_TYPE_MSI) pci_release_msi(...) β€” tws.c:381-382), but the attach-failure unwinds never do. MSI vectors are allocated as early as tws_setup_irq() β†’ pci_alloc_1intr() (tws.c:426), i.e. before every attach_fail_4 exit (tws_init / tws_init_ctlr / tws_cam_attach failure β€” realistic with wedged firmware per DF-1924/1925, or per DF-4402's cam attach bugs) and before the bus_setup_intr failure return at 432-436 (which unwinds at attach_fail_3).

The unwinds also free none of:

  • sc->reqs (kmalloc at 535) and sc->sense_bufs (537),
  • sc->scan_ccb (xpt_alloc_ccb at 539),
  • sc->aen_q.q (580) and sc->trace_q.q (592 β€” allocated at tws_attach:202, so even the earliest goto attach_fail_1 at 218-221 leaks it),
  • the per-request bus_dmamap_create maps (610),
  • the command DMA map/memory (499-509) and the three bus_dma tags (parent/cmd/data, 465-530).

Each failed attach leaks β‰ˆ200 KB of M_TWS kernel heap + DMA resources + one MSI vector. tws_init() returns FAILURE at 541-543 whenever the controller fails tws_ctlr_ready()/tws_ctlr_reset() β€” precisely the flaky-hardware situation in which an operator retries load/unload cycles, so the leak is repeatable and unbounded in aggregate. (tws_detach also never frees the maps/tags/dma_mem β€” same cleanup gap, included in fix.diff.)

Preconditions / reachability

Requires a 3ware 9700-series controller (13C1:1010) that fails to initialize + root privilege to (re)load the driver. Severity Low.

Reproduce (hardware required)

See trigger.sh. EXPECTED vulnerable: after each failed load, vmstat -m | grep twsbuf shows the ~200 KB still allocated; repeated cycles grow it linearly; MSI leak visible via pciconf/devinfo. Patched: allocation-neutral.

On the audit guest: no 3ware controller (run.log / env.txt) β†’ not_reproduced; defect certain from source (side-by-side with the symmetric, correct code in tws_detach:381-382 / 398-402).

Build / run on guest

./run.sh        # probes guest hardware; records absence in run.log

Fix

fix.diff: attach_fail_4 cancels callouts and calls pci_release_msi before destroy_dev; attach_fail_1 frees every guarded allocation (maps β†’ reqs β†’ sense β†’ scan_ccb β†’ aen_q β†’ trace_q β†’ dma map/mem β†’ data/cmd/parent tags); tws_detach gains the missing map/tag/dma_mem cleanup.

VERDICT.md
↓ download raw

DF-4591 VERDICT β€” not_reproduced (hardware-gated), defect certain from source

Finding

The attach-failure unwinds of tws_attach() (sys/dev/raid/tws/tws.c:309-341) release only the char device (315-316), the IRQ resource (318-322) and the memory BARs (326-335). They leak:

  1. MSI vectors β€” allocated by pci_alloc_1intr() inside tws_setup_irq() (tws.c:426) before any attach_fail_4/3 exit; pci_release_msi() exists only in tws_detach (tws.c:381-382), proving the authors knew the release was required and omitted it on the error path. Exits affected: bus_setup_intr failure (432-436), tws_init FAILURE incl. tws_ctlr_reset failure (289-292, 541-543), tws_init_ctlr FAILURE (293-296), tws_cam_attach error (297-300).
  2. Kernel heap β€” sc->reqs (kmalloc at :535), sc->sense_bufs (:537), sc->scan_ccb (:539), sc->aen_q.q (:580), and sc->trace_q.q β€” the trace queue is allocated at tws_attach:202, before the earliest failure label, so every failed attach leaks it.
  3. DMA resources β€” per-request maps from bus_dmamap_create (tws_init_reqs :610), the command DMA map + memory (:499-509), and the parent/cmd/data bus_dma tags (:465-530). (tws_detach likewise never frees the maps/tags/dma_mem β€” same gap, fixed together.)

Aggregate β‰ˆ200 KB M_TWS heap + DMA resources + 1 MSI vector per failed attach. The failure is most reachable exactly when hardware is flaky (tws_init returns FAILURE at :541-543 when tws_ctlr_ready() fails and tws_ctlr_reset() fails β€” the DF-1924/DF-1925 wedged-controller state), i.e. during operator load/unload retry loops β†’ unbounded repeatable leak.

Reproduction attempt on the audit guest

  • Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (env.txt).
  • run.sh / run.log / guest_probe.log: no LSI 3ware (0x13C1) PCI device; tws compiled into the kernel, dev.tws absent, no /dev/tws β€” the attach path never runs, so the leak cannot manifest: status not_reproduced, reproduced=0, impact none (not manifested here)*. Realistic impact ceiling on real hardware: permanent kernel memory / MSI-vector exhaustion via repeated failed loads (privileged, root trigger; Low severity).

Confidence: certain β€” the asymmetry with tws_detach:381-382/398-402 is mechanical; every leaked allocation is cited to its exact line.

Fix validation

fix.diff: attach_fail_4 gains callout cancellation + pci_release_msi; attach_fail_1 frees every guarded allocation in reverse order (maps β†’ reqs β†’ sense_bufs β†’ scan_ccb β†’ aen_q β†’ trace_q β†’ cmd dma map/mem β†’ data/cmd/parent tags); tws_detach gains the missing map/tag/dma_mem cleanup. git apply --check passes. Baseline vs patched cannot be exercised on this guest (no 3ware HW) β†’ fix_status: not_testable; validated by inspection.

Artifacts

  • trigger.sh β€” failed-attach load-cycle recipe + leak observation points
  • run.sh, run.log, guest_probe.log, env.txt β€” guest hardware-absence proof
  • fix.diff β€” error-unwind cleanup (git-apply clean, self-contained)
  • manifest.json / verdict.json

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 not validated on guest: baseline cannot run (no 3ware controller). fix.diff is git-apply clean and mirrors the cleanup tws_detach already performs for the same resources; validated by inspection.

findings/poc/DF-4591/fix.diff
↓ fix.diffper-fix-DF-4591

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (allocation-vs-unwind line map with the detach:381-382/398-402 symmetry proof); trigger.sh (failed-attach load-cycle recipe + vmstat observation points); run.log/env.txt/guest_probe.log (no 3ware HW on guest); fix.diff (guarded cleanup of maps/reqs/sense/scan_ccb/aen_q/trace_q/dma map+mem/3 tags in attach_fail_1 + pci_release_msi in attach_fail_4 + detach-side map/tag cleanup; git-apply clean)

PoC changes

No seed pack existed; trigger authored as documented failed-attach recipe plus guest hardware-absence probe.

Verified recommended fix

Release the MSI allocation in the attach-failure unwinds and free every guarded allocation (maps, reqs, sense_bufs, scan_ccb, aen_q, trace_q, cmd DMA map/mem, bus_dma tags) - see fix.diff.

Verdict

The attach-failure unwinds of tws_attach() (tws.c:309-341) release only the cdev, IRQ resource and BARs. They (1) never call pci_release_msi() for the MSI allocation made in tws_setup_irq() (tws.c:426) even though tws_detach() does exactly that at tws.c:381-382 - direct evidence the release was known-required and omitted on the error path; and (2) free none of sc->reqs (535), sc->sense_bufs (537), sc->scan_ccb (539), sc->aen_q.q (580), sc->trace_q.q (allocated at tws_attach:202, leaked even by the earliest attach_fail_1 exit), the per-request bus_dmamap_create maps (610), the command DMA map/memory (499-509) or the three bus_dma tags (465-530). Every failed attach leaks ~200 KB of M_TWS kernel heap + DMA resources + one MSI vector, and failure is most reachable exactly with flaky hardware (tws_init FAILURE at 541-543 when the controller fails tws_ctlr_ready/tws_ctlr_reset - the DF-1924/1925 wedged-controller state) where operators retry load/unload cycles. tws_detach() also never frees the maps/tags/dma_mem (same cleanup gap, included in fix.diff). Not reproduced on the audit guest: no 3ware 13C1:1010 device exists (run.log/env.txt/guest_probe.log; vmstat -m shows no twsbuf; dev.tws absent), and QEMU cannot emulate the controller. Defect certain from source by the detach/error-path asymmetry.