β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5402

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5402 Β· 14 files
FileTypeDescriptionSize
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
VERDICT.md verdict 3-kernel narrative
↓ download 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-683 mapped_ioctl(): for IOC_VOID commands 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 as ap->a_data.
  • sys/platform/pc64/x86_64/support.s:290-328 std_copyin/std_copyout reject kernel-range addresses (> VM_MAX_USER_ADDRESS) β†’ EFAULT.
  • sys/bus/cam/scsi/scsi_sg.c:483,488,503,569,583,601 uses ap->a_data directly 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:510 addr = *((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

  1. 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() checks SYSCAP_RESTRICTEDROOT β†’ root/operator only.
  2. 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.
  3. 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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

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).

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).
↓ fix.diffper-fix-DF-5402

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.