DF-5402 / verdict.json
{ "finding_id": "DF-5402", "status": "reproduced", "reproduced": true, "impact": "dos", "confidence": "certain", "verdict": "sg(4)s data-carrying SG_* ioctls (SG_IO, SG_SET_TIMEOUT, SG_GET_VERSION_NUM, SG_GET_RESERVED_SIZE, SG_GET_SCSI_ID) all pass ap->a_data straight to copyin/copyout, but for IOC_VOID commands DragonFlys mapped_ioctl() passes a KERNEL stack slot holding the user pointer BY VALUE (sys/kern/sys_generic.c:681-683) and std_copyin/std_copyout reject kernel-range addresses, so every one of these ioctls returns EFAULT on a stock kernel: the Linux-compatible SG ioctl ABI, including the SG_IO passthrough, is completely non-functional. Proven at runtime on the stock guest kernel (all probes rc=-1 errno=14, even with a perfectly valid user buffer and struct layout), and proven bidirectionally by applying only the ABI indirection fix (addr = *(caddr_t *)ap->a_data, the same idiom scsi_ses.c:510 already uses): SG_GET_VERSION_NUM then returns 30125 and SG_IO returns real drive INQUIRY data. The bug also currently blocks the runtime reachability of the SG_IO-path findings DF-1052/DF-1053 and masks DF-5403, which becomes live the moment this ABI bug is fixed (see DF-5403, verified on the ABI-fixed kernel). Device is root/operator-gated (0600 + SYSCAP_RESTRICTEDROOT), so impact is a functional DoS of a privileged interface rather than unprivileged corruption.", "exploit_chain": "open(/dev/sg0) as root -> ioctl(SG_GET_VERSION_NUM or SG_IO, &arg) -> mapped_ioctl stores &arg by value in kernel stkbuf -> sgioctl copyin/copyout(ap->a_data == &stkbuf, a kernel address) -> std_copyin/std_copyout VM_MAX_USER_ADDRESS check -> EFAULT on every call. Fixing the indirection alone un-masks DF-5403 (deterministic panic); both fixes must land together.", "evidence": "run.stock-ioctls.log (stock kernel: GET_VERSION_NUM/GET_RESERVED_SIZE/SG_IO all rc=-1 errno=14, including a valid-buffer SG_IO); run.log (first stock run, errno=14); run.abifixed.log (ABI-fix-only kernel #0: version=30125, sanity SG_IO returns QEMU DVD-ROM); run.patched.log (full-fix kernel #2: ioctls still work); panic.abifixed.txt in the DF-5403 pack shows what the EFAULT was hiding; VERDICT.md has the 3-kernel behavior table.", "kernel_refs": [ "sys/bus/cam/scsi/scsi_sg.c:483", "sys/bus/cam/scsi/scsi_sg.c:488", "sys/bus/cam/scsi/scsi_sg.c:503", "sys/bus/cam/scsi/scsi_sg.c:569", "sys/bus/cam/scsi/scsi_sg.c:583", "sys/bus/cam/scsi/scsi_sg.c:601", "sys/kern/sys_generic.c:681", "sys/platform/pc64/x86_64/support.s:290", "sys/bus/cam/scsi/scsi_ses.c:510" ], "poc_changes": "poc5402.c written from scratch (no seed). dbg/dbg2 probes were used first to isolate the EFAULT to the ioctl plumbing before the ABI root cause was identified; final PoC has abi/sanity/null modes covering DF-5402 and DF-5403.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly v6.5.0.1712.g89e6a-DEVELOPMENT #0..#2 x86_64 (stock Jun 29 2026; ABI-fixed Oct 4; full-fix Oct 4; QEMU Virtual CPU 2.5+, 6 vCPU)", "runtime_sec": 3873, "guest_dirty": 1, "build_cmd": "cc -O -o poc5402 poc5402.c (in-guest, gcc 8.3)", "run_cmd": "./poc5402 abi [/dev/sg0] (root; modes: abi | sanity | null)", "code_hash": "1618e8e10dad5df503a028973b0fca9cb031d1ec5cae423066b9de22ddb1b1c6", "notes": "Two-kernel choreography for verification: kernel #0 = ABI fix only (un-masks DF-5403 panic), kernel #2 = ABI + NULL-data validation (everything works, no panic). Kernel #1 (validation inserted before the direction switch, dead check) still panicked - caught by gcc -Werror=maybe-uninitialized and relocated; this accidentally confirmed the sink location. Also note DF-1052/DF-1053 (SG_IO-path pass-1 findings) cannot execute at runtime on a stock kernel because of this bug; their source analysis stands.", "recommended_fix": "In sgioctl, extract the user pointer once with the standard IOC_VOID idiom (addr = *(caddr_t *)ap->a_data, as scsi_ses.c:510 does) and use it at all six copyin/copyout sites; land together with the DF-5403 NULL-data validation.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 1, "fix_verdict": "With fix.diff applied (make -j6 nativekernel KERNCONF=X86_64_GENERIC, gcc 8.3 -Werror, rc=0; make installkernel; reboot): SG_GET_VERSION_NUM returns 30125, SG_GET_RESERVED_SIZE returns 32768, sanity SG_IO returns drive INQUIRY - the EFAULT bad behavior is gone, and the fix introduces no regressions (validated again on the full-fix kernel #2).", "fix_evidence": "run.abifixed.log and run.patched.log vs run.stock-ioctls.log; fix.diff in pack; kernel identity via uname (#0 Oct 4 07:01:19 ABI-fix, #2 Oct 4 07:33:55 full-fix)." } |