# 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:

```c
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.
