DF-5073 / verdict.json
{ "finding_id": "DF-5073", "status": "reproduced", "reproduced": true, "impact": "none", "confidence": "certain", "verdict": "Mechanism reproduced on the audit guest with an injected SSDT: an ACPI0003 AC-adapter whose _PSR evaluates OK but returns NO object makes hw.acpi.acline read 0 (for root and uid 1001) where the sentinel-correct value is -1. 0 is reachable only through acpi_GetIntegers uninitialized stack ACPI_OBJECT (AcpiEvaluateObject leaves ReturnBuffer->Pointer untouched on AE_OK for no-return methods, nsxfeval.c:541-545), whose residue here reads {Type=ACPI_TYPE_INTEGER, Value=0}; acpi_acad.c:111/116 persists it into sc->status and acpi_acad.c:289/233 exports it through the world-readable sysctl on every read. Impact honestly bounded to none: 21 same-thread kernel-stack grooms (connect(2), KERN_PROC_ALL sysctls, block reads) could not vary the leaked dword from 0 \u2014 the slot is owned by ACPI-evaluation frames, so no pointer-bearing kernel memory was demonstrated disclosed. Trigger is firmware-shape (boot-time AML) only; reader side is fully unprivileged. Severity Low (kernleak class).", "exploit_chain": "", "evidence": "run.sane.log (control boot: acline=1 for uid 1001 proves injected-SSDT->acpi_acad attach->_PSR->world-readable sysctl chain); run.2.log (trigger boot: acline=0 instead of -1); run.log (groom.c: 21 unprivileged reads after deep same-thread grooms, constant 0); VERDICT.md (full why-0-only-via-uninit-path derivation and honest impact=none reasoning); fix.diff (1-line root-cause fix in acpi_GetInteger)", "kernel_refs": [ "sys/dev/acpica/acpi_acad.c:110", "sys/dev/acpica/acpi_acad.c:111", "sys/dev/acpica/acpi_acad.c:115", "sys/dev/acpica/acpi_acad.c:116", "sys/dev/acpica/acpi_acad.c:288", "sys/dev/acpica/acpi_acad.c:289", "sys/dev/acpica/acpi_acad.c:233", "sys/dev/acpica/acpi.c:2175", "sys/dev/acpica/acpi.c:2177", "sys/dev/acpica/acpi.c:2179", "sys/dev/acpica/acpi.c:2180", "sys/contrib/dev/acpica/source/components/namespace/nsxfeval.c:541" ], "poc_changes": "No seed PoC existed (new pass-2 finding). Wrote both SSDTs from scratch; had to model the no-return _PSR as If (LGreater (0,1)) {Return(One)} so iasl accepts it while still falling off the end (iasl warns 3115/3107, which is exactly the malformed-firmware shape). First groom attempt (process-level sysctl -a / ls -lR between reads) failed to move the value because each command is a separate process with a different kstack; rewrote groom.c to groom and read in the SAME thread.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 960, "guest_dirty": 0, "build_cmd": "./build.sh (scp ASL to guest; guest iasl compiles ssdt_acad_sane.asl + ssdt_acad_leak.asl; guest cc builds groom.c)", "run_cmd": "./run.sh (boot A: QEMU_EXTRA=-acpitable file=ssdt_acad_sane.aml ./vm.sh up 300; boot B: QEMU_EXTRA=-acpitable file=ssdt_acad_leak.aml; then run_root/run_user sysctl hw.acpi.acline and /tmp/groom)", "code_hash": "46852741a9d4d858c093a39ee91d995560203556a5a079f97b586dfb4850a37a (groom.c); c18cf1d8f19214bd82589db48e15a2e079c3505efdef46a98c3f458791b73f40 (ssdt_acad_leak.asl)", "notes": "Root cause is DF-3793 (acpi.c acpi_GetInteger, pass 1); this finding is the call-site export primitive in acpi_acad.c (unaudited in pass 1): the garbage becomes persistent driver state and is served world-readably on every sysctl read, which also re-runs the AML evaluation. The same defect pattern exists in every acpi_GetInteger consumer that pre-seeds a sentinel; acad is the one with a world-readable, re-evaluating export. Trigger-boot also flips power_profile_set_state on the first garbage read (acpi_acad.c:118) - functional misbehavior, not security impact.", "recommended_fix": "In acpi_GetInteger (sys/dev/acpica/acpi.c), pre-set param.Type = ACPI_TYPE_ANY before AcpiEvaluateObject so a no-return evaluation yields AE_TYPE without reading uninitialized stack (fix.diff); defensively, acpi_acad.c:111 could also treat acpi_GetInteger failure as unknown explicitly.", "fix_status": "not_testable", "fix_baseline_reproduced": 1, "fix_patched_reproduced": null, "fix_verdict": "Fix validation skipped per contract (mandatory only for reproduced memory-corruption findings; this is Low severity, impact none). fix.diff is a 1-line root-cause fix in acpi.c authored after verification; with it, the trigger boot must read hw.acpi.acline = -1 instead of 0. Never applied to sys/.", "fix_evidence": "fix_baseline_reproduced=1: trigger boot on stock kernel reads acline=0 (run.2.log). Patched-kernel run not performed." } |