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

Per-I/O dmamap unloaded only on IOP_RESULT_SUCCESS: every BAD_TARGET/BUSY/FAIL/CHECK_CONDITION completion leaks a loaded map β€” deterministic per absent-target bus probe; bounce-pool exhaustion on bounce-capable configs

Summary

hptiop_action loads the CCB buffer for every data command, but all three completion callbacks sync/unload only inside case IOP_RESULT_SUCCESS. Every other result returns the srb to the freelist with the map still loaded; the next use double-loads it and busdma's bounce reservation accounting accumulates on bounce-capable tags until the pool is exhausted -> permanent CAM_BUSY. BAD_TARGET from every absent-tid probe makes the leak deterministic. Hoist-the-unload fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3965 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.2 KB ↓ raw
VERDICT.md β€” 1.7 KB ↓ raw
trigger.c β€” 1.8 KB view raw
build.sh β€” 67 B view raw
run.sh β€” 91 B view raw
run.log β€” 12 B view raw
env.txt β€” 1021 B view raw
fix.diff β€” 3.3 KB view raw
build.log β€” 7.1 KB view raw
manifest.json β€” 370 B view raw
verdict.json β€” 2.8 KB view raw

DF-3965 β€” per-I/O dmamap released only on IOP_RESULT_SUCCESS: leaked loaded maps on every failed/BAD_TARGET/BUSY/CHECK_CONDITION completion

File: sys/dev/raid/hptiop/hptiop.c (pass 2; checklist item (e) DMA map lifetimes)

What

The CCB buffer is bus_dmamap_loaded per command (2304–2309) but the three completion callbacks sync/unload only inside case IOP_RESULT_SUCCESS (itl 393–409, mv 547–561, mvfrey 654–668). Every other result frees the srb with the map still loaded. Each bus-scan probe of an absent target returns BAD_TARGET β†’ one leaked map state per probe, at every attach/rescan. Next use of that srb double-loads the map; on bounce-capable tags the reservation accounting accumulates until the bounce pool is exhausted (guest hw.busdma.max_bpages=1024) β†’ permanent CAM_BUSY on those slots. On the current no-bounce io_dmat (lowaddr=MAXADDR) amd64 impact is bookkeeping-only β†’ Low severity, but it is a deterministic correctness leak that hardens into I/O failure on 32-bit/bounce configurations.

Build / Run

Hardware-gated (HBA required): camcontrol rescan all on an HBA with absent targets; observe hw.busdma.total_bpages growth / map-load failures.

VERDICT.md
↓ download raw

DF-3965 verdict β€” certain lifetime bug, Low on this platform

Reproduced?

No HBA on the guest (env.txt).

Analysis

  • Load: hptiop.c:2304-2309 (every data CCB).
  • Unload only-on-success: hptiop.c:393-409 (itl), 547-561 (mv), 654-668 (mvfrey) β€” the switch (result) puts sync/unload inside the SUCCESS arm; BAD_TARGET/BUSY/INVALID/FAIL/RESET/CHECK_CONDITION fall through to hptiop_free_srb (457/599/706) with the map loaded.
  • BAD_TARGET is generated by ordinary bus probes (absent tids) β€” CAM scans at attach (hptiop_rescan_bus, 2067) and via the HPT_SCAN_BUS ioctl (219-221), so the leak is deterministic, not exceptional.
  • DragonFly amd64 busdma (sys/platform/pc64/x86_64/busdma_machdep.c:616-677): bounce reservation state (map->pagesneeded) is only cleared by bus_dmamap_unload; re-loading a loaded map with pagesneeded!=0 skips the recount and layers reservations. With the current io_dmat (lowaddr = BUS_SPACE_MAXADDR) pagesneeded stays 0 on amd64 β†’ bookkeeping-only today, but the driver's own srb_dmat/ctlcfg use 32-bit lowaddrs and other platforms/configs bounce for real.

Fix

fix.diff hoists the direction sync/unload in front of the result switch in ALL THREE callbacks (itl :393, mv :547, mvfrey :654) so the map is released on every completion. Verified to compile: hptiop.ko builds clean with -Werror on the guest /usr/src (build.log).

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 demonstrates the itl hunk (default-arm unload); the mv/mvfrey callbacks need the identical pattern. git apply --check clean fix.diff applied to the guest /usr/src copy and the hptiop.ko module rebuilt clean with -Werror (build.log; combined with the other four hptiop fix.diffs of this batch); runtime validation still impossible without the HBA.

fix.diff; fix.diff applied to the guest /usr/src copy and the hptiop.ko module rebuilt clean with -Werror (build.log; combined with the other four hptiop fix.diffs of this batch); runtime validation still impossible without the HBA.
↓ fix.diffper-fix-DF-3965

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (load/unload asymmetry + busdma accounting path), trigger.c (deterministic BAD_TARGET source), env.txt (hw.busdma.max_bpages=1024), fix.diff (unload on all completions)

PoC changes

Informational trigger only.

Verified recommended fix

Unload/sync the srb dma_map on every completion, not only IOP_RESULT_SUCCESS (fix.diff pattern for all three callbacks).

Verdict

Certain DMA-map lifetime bug, Low on this platform: the three completion callbacks sync/unload the per-SRB io map only inside case IOP_RESULT_SUCCESS (hptiop.c:393-409/547-561/654-668) although every data CCB loads it (2304-2309); all non-success results (BAD_TARGET from every absent-tid bus probe, BUSY, FAIL, CHECK_CONDITION, ...) return the srb to the freelist with the map still loaded, and the next use double-loads it. On bounce-capable tags/platforms the reservation accounting (busdma_machdep.c pagesneeded) accumulates until the bounce pool is exhausted -> permanent CAM_BUSY; on the current amd64 no-bounce io_dmat the leak is bookkeeping-only. Not reproduced: guest lacks the HBA (env.txt).