Deprecated ACPIIO_SETSLPSTATE ioctl bypasses the pc64 suspend-support gate and drives the unimplemented S2-S4 suspend path (stubbed machdep half), returning false success on failure
Summary
The supported entry point refuses S1-S4 with EOPNOTSUPP because the pc64 machdep half is stubbed - but the deprecated SETSLPSTATE ioctl still calls acpi_SetSleepState directly, bypassing the gate. Guest-reproduced 4/4 as root: ioctl(S3/S4) walks the entire device-tree suspend machinery (dmesg 'device_suspend failed' per call, with bus_generic_suspend's inline PARTIAL RESUME of already-suspended siblings) and returns 0 - false success - because status stays AE_OK through the failure break. On suspend-clean hardware the same path executes a phantom sleep: _PTS -> no-op acpi_sleep_machdep -> SLEPT marked anyway -> _WAK + full DEVICE_RESUME on a machine that never slept, with icu_reinit/elcr_resume never invoked. Root-only ceiling: privileged DoS/driver-state corruption. route-through-ReqSleepState fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5669 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| setslp.c | β | 1.9 KB | view raw | |
| build.sh | β | 190 B | view raw | |
| run.sh | β | 189 B | view raw | |
| build.log | β | 67 B | view raw | |
| run.log | β | 828 B | view raw | |
| run.unpriv.log | β | 129 B | view raw | |
| run.reqstate.log | β | 385 B | view raw | |
| run.s4.log | β | 259 B | view raw | |
| dmesg.txt | β | 257 B | view raw | |
| env.txt | β | 410 B | view raw | |
| fix.diff | β | 501 B | view raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| verdict.json | β | 4.5 KB | view raw |
DF-5669 β VERDICT
status: reproduced (privileged local; impact ceiling: dos) β confidence: certain for everything demonstrated below.
What reproduced (guest-live, 4/4 deterministic runs)
Guest: DragonFly 6.5-DEVELOPMENT #0 (X86_64_GENERIC), QEMU i440FX,
hw.acpi.supported_sleep_state: S3 S4 S5 (see env.txt).
- Unprivileged boundary holds β
open("/dev/acpi", O_RDWR)as uid 1001 βPermission denied(run.unpriv.log). Device is root:wheel 0644 (acpi.c:730), FWRITE checked atacpi.c:3424-3429. - The supported entry point is gated β
ACPIIO_REQSLPSTATES3 as root βerrno 45 (Operation not supported)(run.reqstate.log), exactly the platform gate atsys/dev/acpica/acpi.c:2417-2420. - The deprecated ioctl bypasses the gate and lies.
ACPIIO_SETSLPSTATES3/S4 as root βioctl() = 0, errno = 0whiledmesggainsacpi0: warning: acpi_SetSleepState() deprecated...+acpi0: device_suspend failedfor every call (4 lines after 4 calls β run.log, dmesg.txt). So the kernel walked the whole device tree suspend path (withbus_generic_suspend's inline partial RESUME of already- suspended siblings,sys/kern/subr_bus.c:2894-2908) and then returned success for an operation that (a) is not supported on this platform and (b) failed.
Root cause of the false success: in acpi_EnterSleepState()
(sys/dev/acpica/acpi.c:2557), status is set to AE_OK by
AcpiGetSleepTypeData() (acpi.c:2597-2605); the DEVICE_SUSPEND != 0
branch only prints and breaks (acpi.c:2624-2627) without setting an
error, so return_ACPI_STATUS(status) (acpi.c:2698) yields AE_OK β
error = 0 at acpi.c:3455-3456.
What the deeper path would do (code-cited; blocked on this guest)
On this guest a driver deterministically vetoes DEVICE_SUSPEND (silent
error; identity not attributed), so the flow stops at stage 2 of 5
(ACPI_SS_GPE_SET). On suspend-clean hardware the same ioctl continues:
AcpiEnterSleepStatePrep(state)β_PTS+ SLP_TYP written to PM1 control (acpi.c:2630-2636);acpi_sleep_machdep(sc, state)β no-op stub on pc64 (sys/platform/pc64/acpica/acpi_wakeup.c:51-54); its return value is ignored. On real FreeBSD this function is the actual sleep transition (wake APs, save CPU state,AcpiEnterSleepStatefrom the trampoline); here nothing sleeps;slp_state = ACPI_SS_SLEPTunconditionally (acpi.c:2653);- backout:
AcpiLeaveSleepState(state)(PM1-control writes, WAKE_STATUS clears,_WAKβsys/contrib/dev/acpica/source/components/hardware/ hwsleep.c:408-479) andDEVICE_RESUME(root_bus)(acpi.c:2669-2673) on a machine that never slept; - ICU/ELCR/LAPIC state is never resynced after the phantom cycle:
icu_reinit()(sys/platform/pc64/icu/icu.c:128) andelcr_resume()(sys/platform/pc64/icu/elcr.c:121) have zero callers (the latter is already tracked as DF-5654 β not re-reported here).
This is a full device-tree teardown/rebuild driven through an entry point the platform explicitly declares unsupported β unvalidated on pc64 by design of the stubs.
Why not uid0 / not a panic
Not memory corruption β a control-flow/logic defect with a privileged-only entry. No unprivileged route exists (proven). On this guest the driver veto prevents the phantom stages, so no wedge/panic was observable; the impact ceiling on capable hardware is a wedged/half-resumed system (privileged DoS).
Fix
Route the deprecated ioctl through the same gate as the modern one
(fix.diff): acpi_ReqSleepState(sc, state) already implements the platform
policy (S5 allowed β poweroff; S1βS4 β EOPNOTSUPP; range β EINVAL). Also
worth a follow-up in acpi_EnterSleepState(): set status = AE_ERROR on the
device_suspend failed break so failures are never masked.
Fix validation: not tested β this is a Low-severity logic finding, not a
reproduced memory-corruption bug, so the single-fix kernel rebuild cycle was
not warranted per the audit contract. The diff is trivially reviewable
against acpi.c:3451-3456.
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
not_testablefix.diff authored and line-verified against acpi.c:3451-3456 (routes the deprecated ioctl through the acpi_ReqSleepState gate, keeping S5 poweroff working); not kernel-rebuilt/validated because this is a Low-severity logic finding, not reproduced memory corruption, and the audit contract requires the single-fix kernel cycle only for memory-corruption findings.
fix.diff (git-apply-able vs sys/dev/acpica/acpi.c); baseline behavior in run.log/dmesg.txt
Confirmed kernel references
- sys/dev/acpica/acpi.c:3451
- sys/dev/acpica/acpi.c:2378
- sys/dev/acpica/acpi.c:2557
- sys/dev/acpica/acpi.c:2417
- sys/dev/acpica/acpi.c:2624
- sys/dev/acpica/acpi.c:2653
- sys/dev/acpica/acpi.c:2669
- sys/dev/acpica/acpi.c:3424
- sys/platform/pc64/acpica/acpi_wakeup.c:51
- sys/platform/pc64/acpica/acpi_wakeup.c:57
- sys/platform/pc64/acpica/acpi_machdep.c:60
- sys/kern/subr_bus.c:2894
- sys/contrib/dev/acpica/source/components/hardware/hwsleep.c:408
Detail
Evidence (decisive lines)
run.log: 'ioctl() = 0, errno = 0' for the deprecated ioctl vs run.reqstate.log: 'errno = 45 (Operation not supported)' for the gated one; dmesg.txt: one 'acpi0: device_suspend failed' per deprecated-ioctl call (4 lines, 4 calls); run.unpriv.log: unpriv open denied; VERDICT.md: full path trace with the guest-specific driver-veto blocker for the deeper phantom stages
PoC changes
n/a β trigger authored fresh for this file's audit (no orchestrator seed existed); ACPIIO_SETSLPSTATE/_IOW('P',3,int) defined inline from sys/dev/acpica/acpiio.h:38 to avoid include-path issues
Verified recommended fix
Route ACPIIO_SETSLPSTATE through acpi_ReqSleepState() so the platform's EOPNOTSUPP gate applies; optionally also set status=AE_ERROR on the device_suspend-failure break in acpi_EnterSleepState.
Verdict
The deprecated-but-live ACPIIO_SETSLPSTATE ioctl (acpi.c:3451) bypasses the platform's explicit 'suspend not supported' gate (acpi_ReqSleepState, acpi.c:2417-2420 -> EOPNOTSUPP) and drives the pc64 suspend machinery whose machdep half is stubbed out (acpi_wakeup.c:51-59, invoked from acpi_machdep.c:60). Reproduced 4/4 on the guest: as root, ioctl(SETSLPSTATE, S3/S4) returns 0 (success) while dmesg records 'device_suspend failed' per call β the entire device tree is walked through suspend with bus_generic_suspend's inline partial resume (subr_bus.c:2894-2908), and the failure is masked because acpi_EnterSleepState leaves status=AE_OK from AcpiGetSleepTypeData through the device_suspend-failure break (acpi.c:2624-2627, 2698). Unprivileged users are correctly denied (EPERM on O_RDWR open of root:wheel 0644 /dev/acpi). On suspend-clean hardware the same path continues into a phantom sleep cycle (AcpiEnterSleepStatePrep/_PTS -> no-op acpi_sleep_machdep -> ACPI_SS_SLEPT -> AcpiLeaveSleepState/_WAK + DEVICE_RESUME on a machine that never slept, with icu_reinit/elcr_resume never called β zero callers), an unvalidated full device-tree teardown/rebuild on a platform that declares the operation unsupported. Impact ceiling: privileged local DoS / kernel driver-state corruption; no unprivileged escalation path exists.
No comments yet.