DF-5284 / verdict.json
{ "finding_id": "DF-5284", "status": "reproduced", "reproduced": 1, "impact": "leak", "confidence": "certain", "verdict": "Uninitialized kernel heap transmitted to the device in a SCSI MODE SELECT command. sasetparams() allocates ccomp without M_ZERO (scsi_sa.c:2757); when the embedded sagetparams() EINVAL fallback sets SA_QUIRK_NOCOMP it returns without filling *tcs, so 16 bytes of stale kernel heap are bcopyd into the MODE SELECT page (scsi_sa.c:2796), the driver branches on the uninitialized pagecode byte (scsi_sa.c:2863), and - when that stale byte is 0x10/0x0f - transmits the buffer to the device (scsi_sa.c:2954). Reproduced deterministically (first MTCOMP attempt) on a same-source no-INVARIANTS kernel with a heap groom: the captured 28-byte MODE SELECT carries a live slab-freelist pointer (0xfffff8004f0b1e.. range) and the marker kernel pointer &ev_lock (0xffffffff8260d5c0) in its 16 uninitialized bytes, stable across 3 runs. On the stock INVARIANTS audit kernel the transmit is masked (kmalloc poisons bytes[0..7] with -1, forcing the default/ENODEV branch) but the uninitialized-memory use is still visible: the kernel logs device does not seem to support compression, i.e. it branched on the uninitialized byte. With fix.diff (M_ZERO) the payload is zeroed and no 28-byte MODE SELECT occurs.", "exploit_chain": "evil tape device: MODE SENSE page 0x0f -> CAM_REQ_INVALID (EINVAL), page 0x10 -> CAM_REQ_INVALID -> sagetparams fallback sets NOCOMP and returns success without touching *tcs -> victim/operator issues MTCOMP (mt comp) -> sasetparams bcopy(ccomp, cpage, 16) with ccomp = kmalloc(16) without M_ZERO -> groomed freelist chunk returns with byte0=0x10 -> dconf branch -> MODE SELECT(6) len 28 carries the 16 uninit bytes to the device (two kernel pointers per call, repeatable per fresh mount)", "evidence": "run.log + dmesg lines: [EVILSA] MODE SELECT #1 len 28 payload[12..27]: 10 1e 0b 4f 00 f8 ff ff c0 d5 60 82 ff ff 01 ff - bytes[13..19]=heap freelist pointer, bytes[20..27]=&ev_lock marker (byte 26 overwritten by calg=1); run.inv-kernel.log ((sa0): device does not seem to support compression = branch on uninit byte); probe16 hexdumps showing the marker sitting in the returned chunks; run.patched.log (post-fix: ENODEV, no 28-byte MODE SELECT, no markers)", "kernel_refs": [ "sys/bus/cam/scsi/scsi_sa.c:2757", "sys/bus/cam/scsi/scsi_sa.c:2569", "sys/bus/cam/scsi/scsi_sa.c:2601", "sys/bus/cam/scsi/scsi_sa.c:2796", "sys/bus/cam/scsi/scsi_sa.c:2863", "sys/bus/cam/scsi/scsi_sa.c:2954", "sys/bus/cam/scsi/scsi_sa.c:3009" ], "poc_changes": "evilsa KLD (adapted from DF-5271 evilcd rig): T_SEQUENTIAL inquiry, mode-sense/mode-select choreography, 16B marker spray with address-selected free order so the chunk returned to ccomp has freelist-linkage byte0=0x10; poc_comp userland trigger flips the device personality after mount. One rig bug fixed mid-run (int aidx[4096] kernel-stack overflow -> .bss).", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT X86_64_NOINV #0: Fri Oct 2 02:12:33 UTC 2026 (leak reproduced; INVARIANTS stock build masked the transmit leg, see notes)", "runtime_sec": 1600, "guest_dirty": 1, "build_cmd": "cd /root/df5284 && touch opt_cam.h && make && cc -O -o poc_comp poc_comp.c", "run_cmd": "sh run.sh (kldload evilsa.ko; usched :1 ./poc_comp /dev/nsa0)", "code_hash": "evilsa.c: 968557745b0f060df9477a1280e6b4db9a7dbc0e807c5921babe83937a457d06, poc_comp.c: 9462d288ef3e10bc9aea8f49057d626f70c189569ad5204e60de813ba4d43207", "notes": "INVARIANTS kernels poison non-M_ZERO kmalloc bytes[0..7] with (void*)-1 making the uninit pagecode 0x7f -> ENODEV before transmit; production kernels ship without INVARIANTS where the leak is heap-groomable and deterministic (proved: 3/3 first-attempt hits). The MARKER HIT kprintf verdict line in evilsa mis-flags (rig cosmetic bug); the hexdump itself shows the marker bytes - decode documented in VERDICT.md. Trigger (MTCOMP) requires an open sa node (SYSCAP_RESTRICTEDROOT) or the device itself; the data crossing the trust boundary is kernel heap.", "recommended_fix": "kmalloc ccomp with M_ZERO in sasetparams() (fix.diff hunk 4; also M_ZERO for rblim and the DF-5285 min_blk guard).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Same-source X86_64_NOINV kernel rebuilt with fix.diff: identical PoC now returns ENODEV from MTCOMP (zeroed pagecode takes the default branch) and no 28-byte MODE SELECT is captured - the uninitialized bytes no longer reach the command buffer.", "fix_evidence": "run.patched.log (attempt 0: ENODEV, msel28 stays 0); fix_build.log" } |