DF-5332 / verdict.json
{ "finding_id": "DF-5332", "status": "reproduced", "verdict": "chgetelemstatus() allocates both READ ELEMENT STATUS transfer buffers without M_ZERO (scsi_ch.c:1089, :1129) and never checks csio->resid; an evil medium changer that completes the command GOOD but transfers only the 8-byte status header (legal SCSI; resid>0, CAM_REQ_CMP) leaves the page header and all descriptors as stale kernel heap, which copy_element_status() parses and copyout()s to the CHIOGSTATUS caller. Reproduced deterministically on the QEMU guest with the evilcd3 rig: marked runs returned the exact 0x26 stamp bytes in ces_int_addr/ces_flags/ces_sense*/ces_scsi_id (28 non-zero bytes per run, 3/3 runs), unmarked runs returned natural stale-heap bytes (0xa0cf, 0x805a, 0xff..., 17 non-zero bytes per run, 3/3 runs). Distinct from DF-1028/DF-2271 (those walk PAST the allocation; this discloses uninitialized memory INSIDE it) -- the DF-5272 class in the ch driver. Leak only; no write primitive.", "reproduced": true, "impact": "leak", "confidence": "certain", "exploit_chain": "", "evidence": "run.log (marked: LEAK CONFIRMED: 84 non-zero bytes across 3 runs, full per-element hexdumps showing 0x26 markers); run.2.log (unmarked: LEAK CONFIRMED: 51 non-zero bytes, natural heap 0xa0cf/0x805a/0xff); leak_sample.txt; dmesg lines in run.log [EVILCH] RES evil short: sea=256 count=4 dxfer=80 SENT=8B only; fixedA.log (patched kernel: all-zero structs, PoC exits 3 while rig still short-transfers, dmesg count SENT=8B = 13)", "kernel_refs": [ "sys/bus/cam/scsi/scsi_ch.c:1089", "sys/bus/cam/scsi/scsi_ch.c:1129", "sys/bus/cam/scsi/scsi_ch.c:1115-1122", "sys/bus/cam/scsi/scsi_ch.c:1156-1188", "sys/bus/cam/scsi/scsi_ch.c:984-1051", "sys/bus/cam/cam_periph.c:960-969" ], "poc_changes": "Derived rig from DF-5327s evilcd2.c (kept fake T_CHANGER + SIM skeleton, added READ ELEMENT STATUS emulation, markres heap-stamping knob, invalidate knob); PoC written fresh (CHIOGSTATUS struct from guest <sys/chio.h>); marker bytes constrained to 0x21..0x2F so the stale page-header flags cannot set PVOLTAG/AVOLTAG and drag in the separately-filed OOB-walk class (DF-1028/2271)", "attempts": 6, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Fri Oct 2 22:55:55 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/EVIL x86_64 (GENERIC + device ch)", "runtime_sec": 2280, "guest_dirty": 1, "build_cmd": "cd /root/df5332 && sh build.sh", "run_cmd": "cd /root/df5332 && sh run.sh", "code_hash": "abe6b65e2d7ddf140e65bd4d5cde8015ee29b493e920dd285e9793fa5b6510f7", "notes": "Severity aligned with DF-5272 (Medium, kernleak): /dev/chN is 0600 root:operator, so the direct reader is root/operator, but the disclosed content is arbitrary prior M_DEVBUF-occupant data and the trigger is a fully device-controlled short transfer. desclen from a short buffer #1 (<12B) also feeds the size/stride computation. fix.diff adds M_ZERO to both allocations (matches what FreeBSDs ch driver does).", "recommended_fix": "Add M_ZERO to both chgetelemstatus() kmallocs (scsi_ch.c:1089, :1129) so short-but-GOOD transfers parse as zeros", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Applied fix.diff (M_ZERO x2) to the guest /usr/src, rebuilt EVIL kernel (#1 Fri Oct 2 23:30:20 UTC 2026), rebooted, re-ran the exact same PoC: marked and unmarked runs both return 0 non-zero bytes (PoC exits 3 all returned bytes zero) while the rig still performs the identical 8-byte short transfers (dmesg SENT=8B count 13) -- disclosure eliminated, device behaviour unchanged. Baseline on the vulnerable kernel: 84 (marked) + 51 (unmarked) non-zero bytes leaked.", "fix_evidence": "findings/poc/DF-5332/fixedA.log (patched-kernel run: all-zero hexdumps, MARKED_RC=0/UNMARKED_RC=3); findings/poc/DF-5332/run.log + run.2.log (vulnerable baseline)" } |