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)
PoC verification
Evidence pack
findings/poc/DF-3965 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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 tohptiop_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_testablefix.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.
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).
No comments yet.