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)
PoC verification
Evidence pack
findings/poc/DF-5593 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
Fix (recommended, in the fixed-free branch of cam_sim_release)
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--;
}
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_testableleak-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
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.
No comments yet.