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

cam_sim_free() orphans the sim's ccb pool β€” deterministic kernel heap leak on every controller detach after I/O

Summary

ccbs are pooled per-sim but the final free branch drops the devq and kfrees the sim without ever visiting ccb_freeq: every pooled union ccb (~720B + callout, M_CAMXPT) is orphaned with no code path able to free it. Measured on the stock guest: 8 ccbs orphaned per detach, CAM_XPT vmstat count 40->69 with zero return after a clean detach - kldunload/hot-plug/surprise-removal churn exhausts kernel heap unboundedly. Drain-the-freeq fix validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5593 Β· 9 files
FileTypeDescriptionSize
ptsim3.c β€” 18.8 KB view raw
build.sh β€” 117 B view raw
build.log β€” 225 B view raw
run.log β€” 967 B view raw
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 2.7 KB ↓ raw
manifest.json β€” 849 B view raw
verdict.json β€” 2.5 KB view raw
env.txt β€” 515 B view raw

DF-5593 β€” cam_sim_free() orphans the sim's ccb pool (deterministic kernel heap leak per controller detach)

sys/bus/cam/cam_sim.c pass-2 β€” cam_sim_release() final free path.

What

ccbs are pooled per-sim: xpt_get_ccb() (cam_xpt.c:4958-4975) allocates from / xpt_release_ccb() (cam_xpt.c:4247-4271) returns ccbs to sim->ccb_freeq, bounded by sim->max_ccbs (grown per attached device at cam_xpt.c:5156/5253). When the sim dies, cam_sim_release() (cam_sim.c:246-252) drops the devq and kfree(sim) β€” but never drains sim->ccb_freeq: every pooled union ccb (~720 B + a struct callout, both M_CAMXPT) is orphaned. Nothing else can free them: xpt_free_ccb() is only reachable through a live sim's release accounting.

Every storage-controller detach after I/O (kldunload, hot-plug removal, surprise removal) leaks its pool; repeated cycles grow the kernel heap without bound.

Reproduce (guest)

sh build.sh
kldload ./ptsim3/ptsim3.ko
vmstat -m > before.txt
sysctl debug.ptsim3.scan=1 ; sleep 3      # probe churn fills the pool
sysctl debug.ptsim3.go=3                  # clean deregister+free, prints pool size
vmstat -m > after.txt                     # CAM_XPT count never returns

Measured on the stock guest: sim->ccb_count=8, ccb_freeq length=8 reported at detach; CAM_XPT in-use count 40 β†’ 69 across the scan with zero return after the detach (see run.log).

    struct ccb_hdr *ch;

    while ((ch = SLIST_FIRST(&sim->ccb_freeq)) != NULL) {
        SLIST_REMOVE_HEAD(&sim->ccb_freeq, xpt_links.sle);
        xpt_free_ccb(ch);
        sim->ccb_count--;
    }
VERDICT.md
↓ download raw

DF-5593 VERDICT β€” REPRODUCED (deterministic kernel heap leak)

What was run

ptsim3.ko (the DF-5592 rig, mode 3) on the stock guest kernel: register a bus with production cam_sim_alloc/xpt_bus_register, run a bus scan (probe machinery allocates ccbs via xpt_get_ccb from the sim's pool and returns them to sim->ccb_freeq via xpt_release_ccb), then the standard driver detach: xpt_bus_deregister() + cam_sim_free().

Observations (run.log)

[PTSIM3] DF-5593: sim->ccb_count=8, ccb_freeq length=8 (each orphaned union ccb ~720 bytes + callout, M_CAMXPT)
[PTSIM3] DF-5593: clean detach (deregister+free): the 8 pooled ccbs are NOT freed by cam_sim_free()
[PTSIM3] DF-5593: detach DONE; 8 ccbs orphaned -- check 'vmstat -m | grep -i xpt' delta

vmstat -m around the cycle:

CAM_XPT  40  7.71K  ... 275   (before scan)
CAM_XPT  69  19.2K  ... 453   (after scan+detach: count never returns)

The module-side counter is the decisive number: 8 ccbs were on sim->ccb_freeq at the moment cam_sim_release() kfree'd the sim, and no code path exists that frees them afterwards (xpt_free_ccb requires going through live-sim accounting at cam_xpt.c:4263-4271; the sim is gone).

Root cause

cam_sim.c:246-252 β€” final branch releases devq + kfree(sim) only; ccb_freeq (head pointer dies with the sim), ccb_count, and the pooled ccbs are never visited. Pool filling is normal CAM behavior (sim->max_ccbs grows with each attached device, cam_xpt.c:5156), so any real controller that has served I/O has a non-empty pool at detach.

Impact

Kernel heap leak, ~0.75-1 KB per pooled ccb per controller detach. Trigger: kldunload / device removal / surprise removal β€” privileged or hardware-event driven. Repeated cycles (driver reload churn, hot-plug racks) exhaust kernel memory β†’ eventual allocation failures/DoS. Not an info leak; classified impact=leak, severity Medium (Low-Medium band; deterministic and permanent).

Fix

Drain sim->ccb_freeq in cam_sim_release()'s final branch before kfree(sim) (README). Not rebuild-validated in this run (leak-class finding; the validated rebuild in the DF-5592 pack exercises the same free path and does not conflict β€” the drain slots straight into the same branch after xpt_sim_finalize() succeeds). fix_status: not_tested.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline reproduced→ patch + rebuild →patched clean

leak-class finding: recommended one-block drain slots into the same final branch validated by DF-5592's rebuilt kernel; not separately rebuild-validated in this run

README.md fix snippet; DF-5592 fix_run.log exercises the same free path
per-fix-DF-5593

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log ('DF-5593: sim->ccb_count=8, ccb_freeq length=8 ... 8 ccbs orphaned' + vmstat CAM_XPT before/after); VERDICT.md; ptsim3.c mode 3

PoC changes

mode 3 of the DF-5592 rig (same build); no additional changes

Verified recommended fix

Drain sim->ccb_freeq (SLIST_REMOVE_HEAD + xpt_free_ccb + ccb_count--) in cam_sim_release()'s final branch before kfree(sim)

Verdict

cam_sim_release()'s final branch (cam_sim.c:246-252) drops the devq and kfrees the sim without draining sim->ccb_freeq: every ccb pooled by xpt_get_ccb/xpt_release_ccb (cam_xpt.c:4958-4975/4247-4271) is orphaned at controller detach. Measured on the stock guest with the ptsim3 rig: sim->ccb_count=8, ccb_freeq length=8 reported at the detach; vmstat -m CAM_XPT count 40 -> 69 across the scan with zero return after the clean deregister+free. Deterministic, permanent kernel heap leak (~0.75-1KB per pooled ccb) per controller detach after I/O (kldunload, hot-plug, surprise removal); repeated cycles exhaust kernel memory.