sg(4) data-carrying SG_* ioctls are dead on DragonFly: IOC_VOID argument indirection misuse returns EFAULT for every SG_IO/SG_GET_VERSION_NUM/SG_SET_TIMEOUT/SG_GET_RESERVED_SIZE/SG_GET_SCSI_ID call
Summary
sgioctl passes ap->a_data (a kernel stack slot holding the user arg BY VALUE for _IO()-defined IOC_VOID commands) straight to copyin/copyout, which reject kernel addresses - the entire Linux-compatible SG ioctl ABI (incl. sg3_utils SG_IO) is non-functional on stock kernels. Runtime-proven on the stock guest (all six probes EFAULT with valid buffers) AND bidirectionally by applying only the indirection fix (scsi_ses.c:510 idiom): SG_GET_VERSION_NUM returns 30125, SG_IO returns real INQUIRY data. Also currently MASKS DF-1052/1053 and hides DF-5403 - which becomes a live panic the moment this alone is fixed; both must land together. addr-deref fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5402 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| poc5402.c | poc-source | abi/sanity/null modes | 5.6 KB | view raw |
| build.sh | build | in-guest compile | 81 B | view raw |
| run.sh | run | baseline probe | 112 B | view raw |
| build.log | build-log | PoC compile (stock guest) | 211 B | view raw |
| run.log | run-log | stock kernel: first single-shot run, errno=14 | 171 B | view raw |
| run.stock-ioctls.log | run-log | stock kernel ioctl probe - all EFAULT | 979 B | view raw |
| run.abifixed.log | run-log | ABI-fix-only kernel: ioctls work | 377 B | view raw |
| run.patched.log | run-log | full-fix kernel: ioctls work | 687 B | view raw |
| run.null-trigger-hang.log | run-log | DF-5403 trigger hang (context) | 168 B | view raw |
| panic.txt | panic | DF-5403 panic on ABI-fixed kernel (context) | 2.1 KB | view raw |
| fix.diff | suggested-fix | ABI indirection + NULL-data validation | 2.4 KB | view raw |
| env.txt | environment | guest uname/controller/toolchain | 501 B | view raw |
| VERDICT.md | verdict | 3-kernel narrative | 2.9 KB | β raw |
| verdict.json | verdict | machine verdict | 4.6 KB | view raw |
DF-5402 VERDICT β REPRODUCED (certain)
Impact: dos (functional DoS of the sg(4) ioctl ABI incl. SG_IO; root/operator-gated) Status: reproduced β baseline stock kernel; inverse-proven by the ABI-fix kernel.
Root cause (source)
sys/kern/sys_generic.c:681-683mapped_ioctl(): forIOC_VOIDcommands the user argument is stored by value in a kernel stack slot (*(caddr_t *)data = uspc_data) and the slot address is what the driver receives asap->a_data.sys/platform/pc64/x86_64/support.s:290-328std_copyin/std_copyoutreject kernel-range addresses (> VM_MAX_USER_ADDRESS) β EFAULT.sys/bus/cam/scsi/scsi_sg.c:483,488,503,569,583,601usesap->a_datadirectly as the user pointer in all six data-carrying cases (SG_GET_VERSION_NUM, SG_SET_TIMEOUT, SG_IO copyin+copyout, SG_GET_RESERVED_SIZE, SG_GET_SCSI_ID).- Correct in-family idiom:
sys/bus/cam/scsi/scsi_ses.c:510addr = *((caddr_t *)ap->a_data);
Runtime proof (guest: DragonFly 6.5-DEVELOPMENT, QEMU PIIX3 ATAPI DVD at scbus1 = /dev/sg0)
| Kernel | SG_GET_VERSION_NUM | SG_GET_RESERVED_SIZE | SG_IO (sanity INQUIRY) |
|---|---|---|---|
stock #0 Jun 29 14:18 |
rc=-1 errno=14 (EFAULT) | rc=-1 errno=14 | rc=-1 errno=14 |
#0 Oct 4 07:01 + ABI fix |
rc=0 version=30125 | rc=0 size=32768 | rc=0, inquiry data "QEMU DVD-ROM" |
#2 Oct 4 07:33 + ABI fix + DF-5403 validation |
rc=0 version=30125 | rc=0 size=32768 | rc=0 (unchanged β fix has no false positives) |
The EFAULT disappears with exactly the one-idiom ABI change
(addr = *(caddr_t *)ap->a_data), proving the mechanism bidirectionally.
Security relevance
- Functional DoS of the device's documented Linux-compatible ioctl
ABI: sg3_utils-style consumers (and the kernel's own SG_IO
passthrough) always fail. Gate: /dev/sgN is 0600 root:operator and
sgopen()checksSYSCAP_RESTRICTEDROOTβ root/operator only. - It currently masks the runtime reachability of DF-1052/DF-1053 (SG_IO-path defects): on a stock kernel those paths cannot execute through the ioctl entry. Their source analysis stands; triage should know the entry is blocked only until this ABI bug is fixed.
- Fixing this bug activates DF-5403 (deterministic NULL-page-write panic) β see that finding; both must land together.
Fix
fix.diff in this pack (part 1 = ABI indirection for all six sites;
part 2 = DF-5403 validation). Validated: full-fix kernel keeps all
ioctls working and turns the DF-5403 trigger into EINVAL
(run.patched.log).
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
fixedWith 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).
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).
Confirmed kernel references
Detail
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 (decisive lines)
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.
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.
Verified 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.
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 DragonFly's 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.
No comments yet.