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

acpi_pst_attach() leaks allocated _PCT bus resources (and burns cpu_next_rid) on every error path after acpi_pst_alloc_resource()

Summary

Once the _PCT control register is allocated (resource set and rid incremented), any subsequent attach failure returns without releasing: status-GAS extraction failure (leaks pst_creg), _PSS eval/parse failures (leak both), _PPC/_PDL non-Integer (leak both). The device never attaches so nothing ever releases the I/O/memory window or the rid. Crafted ACPI tables (firmware/loader privilege) - boot-time only. The rest of the parsing proved sound (_PSS clamp 32, per-element validation, GAS bounds, _PSD invariants making both KKASSERTs unreachable). fail-path cleanup fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4261 Β· 4 files
FileTypeDescriptionSize
README.md file 1.6 KB ↓ raw
VERDICT.md file 2.6 KB ↓ raw
fix.diff file 2.7 KB view raw
verdict.json file 2.2 KB view raw
README.md file
↓ download raw

DF-4261 β€” acpi_pst_attach() leaks the allocated _PCT bus resources on every error path after acpi_pst_alloc_resource()

Impact

Info (hardening / correctness). Boot-time bus-resource leak (I/O port or memory window + permanently bumped cpu_next_rid in the parent cpu softc) when the _PSS/_PPC/_PDL objects of a crafted ACPI table fail validation after _PCT registers were successfully allocated. Requires attacker-controlled ACPI tables (firmware/loader privilege), so no runtime privilege boundary is crossed β€” but it is a real error-path asymmetry that a maintainer can act on, and resource leaks at failed attach are exactly the class that later turns into use-after-free/range-confusion when drivers are re-probed.

Where (sys/dev/acpica/acpi_cpu_pstate.c)

  • :499-504 control register allocated (sc->pst_creg.pr_res, rid bumped at acpi_pst_alloc_resource:1508-1516)
  • :511-516 status-register GAS extraction fails -> return error with pst_creg.pr_res still held
  • :545-547, :551-554, :570-574, :589-594, :596-603, :605-611 every _PSS validation failure returns with BOTH registers held
  • :688-690 (_PPC), :734-736 (_PDL) same

Fix

fix.diff: add acpi_pst_release_resource() (type derived from pr_gas.SpaceId exactly like acpi_bus_alloc_gas, sys/dev/acpica/acpi.c:1261-1270) and route all post-_PCT failures through a failed: label. Not compile-validated (Info-severity, not exercised at runtime).

Verification

Not executed (Info hardening finding; live trigger needs a crafted ACPI SSDT via -acpitable, and the guest's QEMU SSDT exposes no _PSS/_PCT at all β€” dev.cpu.0 has no freq nodes). Code-path proof is complete in VERDICT.md.

VERDICT.md file
↓ download raw

VERDICT β€” DF-4261

Status: untested (Info-severity hardening finding; not PoC-verified per audit policy for Info findings). Confidence in the code defect: certain.

Code-path proof

acpi_pst_attach() allocates the _PCT control register via acpi_pst_alloc_resource() (sys/dev/acpica/acpi_cpu_pstate.c:500), which on success stores a live struct resource * in sc->pst_creg.pr_res and burns a rid from sc->pst_parent->cpu_next_rid (acpi_cpu_pstate.c:1508-1516). From that point on, every error return leaves the resource(s) allocated:

path trigger (BIOS-craftable) leaked
:512-515 status GAS element is not a β‰₯15-byte Buffer (acpi_PkgRawGas EINVAL, sys/dev/acpica/acpi_package.c:120-128) pst_creg
:533-547 _PSS evaluation fails after _PCT succeeded both
:551-555 _PSS not a package / 0 elements (ACPI_PKG_VALID(obj,1)) both
:570-575 _PSS entry count differs from the BSP's both
:589-595, :596-604, :605-611 per-entry package <6 elements / non-Integer element / freq bit31 set both
:688-690 _PPC object is not an Integer (eval_ppc ENXIO) both
:734-736 _PDL object is not an Integer (eval_pdl ENXIO) both

The device never attaches on these paths, so nothing ever releases pr_res; the rid counter in the parent acpi_cpu_softc is also consumed. Attacker model: whoever controls the ACPI namespace (firmware image, loader-injected DSDT/SSDT) β€” i.e., already-privileged at boot. Hence Info.

Not runtime-verified: exercising it would require injecting a crafted SSDT (Processor + _PCT with a valid SystemIO GAS + _PSS/_PPC/_PDL that then fail validation) via QEMU -acpitable; the audit guest's firmware exposes no _PSS/_PCT at all (verified live: dev.cpu.0 carries no freq/px nodes, and hw.acpi.cpu.* exposes only C-state knobs), so there is no in-guest surface to observe the leak on without building custom AML, which the policy for Info findings does not warrant.

Fix

fix.diff (authored, not compile-validated β€” mechanical goto-fail refactor): acpi_pst_release_resource() releases pr_res using the resource type derived from pr_gas.SpaceId (mirroring acpi_bus_alloc_gas, sys/dev/acpica/acpi.c:1261-1270), called from a new failed: label covering all post-_PCT error returns, and directly on the status-register failure path.

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff routes all post-_PCT failures through a failed: label that releases pr_res (type derived from pr_gas.SpaceId, mirroring acpi_bus_alloc_gas). Authored from line-accurate confirmation; not compile-validated and not runtime-validated (Info finding, no test surface on the guest).

fix.diff; VERDICT.md path table
↓ fix.diffper-fix-DF-4261

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md path:line table for all 9 leaking returns; fix.diff

PoC changes

No PoC (Info hardening finding; untested by policy)

Verified recommended fix

Release pst_creg/pst_sreg pr_res on all acpi_pst_attach() error paths after allocation (goto-fail cleanup)

Verdict

Code-proven boot-time resource leak: every acpi_pst_attach() error path after acpi_pst_alloc_resource() (acpi_cpu_pstate.c:500) returns without releasing the allocated _PCT control/status bus resources (and leaves cpu_next_rid consumed) -- reachable only with attacker-controlled ACPI tables, hence Info. Not runtime-verified (Info policy; the guest firmware exposes no _PSS/_PCT, confirmed live).