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

sasetparams ENODEV early return leaks ccomp allocation

Summary

sasetparams() kmalloc ccomp at scsi_sa.c:2757 only frees on success path at 3023-3024. Early ENODEV return at 2938-2941 (taken when compression only thing being set device reports no compression support params_to_set&=~SA_PARAM_COMPRESSION then SA_PARAM_NONE check) frees mode_buffer releases ccb but does NOT free ccomp. Each such call leaks sizeof(sa_comp_t) (~16 bytes) M_SCSISA. Privileged-user-triggerable memory leak repeat via MTIOCTOP/MTCOMP on compression-unsupported drive. No unprivileged reach. [VERIFIED pass2: reproduced - 1000 ENODEV calls grow M_SCSISA by exactly 16000 bytes = 1000 x sizeof sa_comp_t with ccomp the unfreed allocation; fix-validated - 0 bytes growth, identical counts]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2265 Β· 13 files
FileTypeDescriptionSize
README.md β€” 1.3 KB ↓ raw
VERDICT.md β€” 3.4 KB ↓ raw
df_pre.c β€” 3.1 KB view raw
df_post.c β€” 20.4 KB view raw
gen.sh β€” 1.5 KB view raw
build.sh β€” 115 B view raw
quick.sh β€” 1.6 KB view raw
run.sh β€” 140 B view raw
run_unfixed.dmesg β€” 743 B ↓ download
run_fixed.dmesg β€” 978 B ↓ download
fix.diff β€” 329 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.6 KB view raw

DF-2265 verification pack β€” sasetparams ENODEV ccomp leak

Bug: sys/bus/cam/scsi/scsi_sa.c:2938-2941 β€” the compression- unsupported ENODEV exit frees mode_buffer and releases the ccb but never frees ccomp (allocated at :2757), leaking one sa_comp_t (16 bytes, M_SCSISA) per failed MTCOMP ioctl on drives reporting the DATA COMPRESSION page without the DCC bit.

Rig

Same harness kernel as DF-2263 (see ../DF-2263/README.md); the REAL sasetparams() and sagetparams() run against a fake transport (cam_periph_runccb interposed) that answers MODE SENSE(6) with a DCP page lacking DCC β€” the drive class the in-tree comment anticipates. Trigger equals saioctl(MTIOCTOP, MTCOMP) at :1266. Leak measured via in-kernel sums of M_SCSISA->ks_use[cpu].{memuse,calls}.

Build

./build.sh      # or reuse the kernel built from ../DF-2263

Run

vm.sh run_root 'sysctl debug.df226x.t2265=1000'
vm.sh run_root 'dmesg | grep DFH2265'

Expected

Unpatched: memuse grew by 16000 bytes = 1000 x sizeof(sa_comp_t) (16) after 1000 ENODEV returns (3 allocation calls per iteration, one object never freed). Patched (fix.diff adds kfree(ccomp, M_SCSISA); on the ENODEV path): memuse grew by 0 bytes.

Artifacts: VERDICT.md, df_pre.c, df_post.c, gen.sh, build.sh, quick.sh, run_unfixed.dmesg, run_fixed.dmesg, env.txt (in ../DF-2263), fix.diff.

VERDICT.md
↓ download raw

DF-2265 β€” sasetparams ENODEV path leaks the ccomp allocation (M_SCSISA)

File: sys/bus/cam/scsi/scsi_sa.c Claim under verification: sasetparams() allocates ccomp (:2757 ccomp = kmalloc(sizeof(sa_comp_t), M_SCSISA, M_INTWAIT)), fills it via sagetparams() with the drive's compression page, and on the "device does not support compression" path (:2924-2941) frees only mode_buffer and releases the ccb:

if (params_to_set == SA_PARAM_NONE) {
        kfree(mode_buffer, M_SCSISA);
        xpt_release_ccb(ccb);
        return (ENODEV);          /* <-- ccomp leaked */
}

ccomp is freed on every OTHER exit (:2772 error, :3031 tail) but not here: 16 bytes of M_SCSISA per failed call, unbounded over ioctl retries.

Rig

Same kernel-compiled harness (verbatim sasetparams + sagetparams). The only fabricated element is the transport: cam_periph_runccb() is interposed by a fake transport that answers MODE SENSE(6) the way a real drive does when it returns the DATA COMPRESSION page (0x0f) WITHOUT the DCC ("compression capable", 0x40) bit β€” precisely the class of drive the in-tree comment at :2914-2922 anticipates ("Compression does not appear to be supported- at least via the DATA COMPRESSION page..."). Everything else β€” the two kmallocs, sagetparams' page parsing, the mode_buffer setup, the compression-page switch β€” is the real code. The trigger is exactly what saioctl(MTIOCTOP, MTCOMP) issues at :1266: sasetparams(periph, SA_PARAM_COMPRESSION, 0, 0, calg, 0).

Measurement: in-kernel sum of M_SCSISA->ks_use[cpu].{memuse,calls} across all CPUs before/after N=1000 iterations.

Results

UNPATCHED (run_unfixed.dmesg):

DFH2265: M_SCSISA before: memuse=0 calls=0, sizeof(sa_comp_t)=16, iterations=1000
DFH2265: M_SCSISA after: memuse=16000 calls=3000; returns: ENODEV=1000 other=0
DFH2265: *** LEAK: memuse grew by 16000 bytes = 1000 x sizeof(sa_comp_t) (16);
         allocation calls grew by 3000 (expect 3 per call: ccomp+mode_buffer+
         sagetparams buffer; ccomp is the one never freed on the ENODEV path)

Exactly one sa_comp_t (16 bytes) leaked per ENODEV return; the other two per-call allocations (mode_buffer, sagetparams' mode_buffer) are freed. Unbounded: a retry loop or script around mt comp on on such a drive grows M_SCSISA without bound (16 B/call; a 10 kHz ioctl loop leaks ~576 KB/hour; kernel memory never returned).

PATCHED (run_fixed.dmesg, fix2265.diff = add kfree(ccomp, M_SCSISA); before the ENODEV return):

DFH2265: M_SCSISA before: memuse=0 calls=0, sizeof(sa_comp_t)=16, iterations=1000
DFH2265: M_SCSISA after: memuse=0 calls=3000; returns: ENODEV=1000 other=0
DFH2265: *** LEAK: memuse grew by 0 bytes = 0 x sizeof(sa_comp_t) (16); ...

A/B clean: identical behavior (1000 ENODEVs, same 3000 allocations), zero net growth.

Reachability / severity honesty

Requires issuing MTIOCTOP/MTCOMP against a tape drive whose compression mode page reports no DCC capability β€” a privileged ioctl on the tape device node (root/operator). Slow, privileged-reach kernel memory leak: Info severity, exactly as filed. No privesc, no corruption.

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

fixed
A/B dmesg logs: 16000 bytes leaked over 1000 ENODEV calls unpatched versus 0 bytes patched, identical allocation counts
↓ fix.diffper-fix-DF-2265

Confirmed kernel references

Detail

Evidence (decisive lines)

run_unfixed.dmesg: M_SCSISA after memuse=16000 calls=3000 ENODEV=1000 and LEAK grew by 16000 bytes = 1000 x sizeof(sa_comp_t); run_fixed.dmesg: memuse=0 calls=3000 ENODEV=1000 grew by 0 bytes

PoC changes

Replaced seeded sketch with kernel-compiled harness: only cam_periph_runccb is interposed (fake SCSI transport echoing a DCP page without DCC), everything else real; leak measured in-kernel by summing M_SCSISA ks_use per-cpu memuse and calls before and after

Verified recommended fix

Free ccomp with kfree(ccomp, M_SCSISA) on the params_to_set == SA_PARAM_NONE ENODEV return path in sasetparams

Verdict

Fully reproduced and fix-validated A/B with the real sasetparams and sagetparams against a fake transport answering MODE SENSE with a DATA COMPRESSION page lacking the DCC bit. On the unpatched kernel 1000 compression-only sasetparams calls return ENODEV 1000 times and grow M_SCSISA memuse by exactly 16000 bytes = 1000 x sizeof(sa_comp_t) with 3000 allocation calls (ccomp leaked once per call; mode_buffer and the sagetparams buffer are freed). With fix265.diff adding kfree(ccomp, M_SCSISA) on the ENODEV path the same 1000 calls produce zero net growth. Privileged-reach unbounded kernel memory leak, 16 bytes per failed MTCOMP ioctl on such drives; Info severity confirmed.