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)
PoC verification
Evidence pack
findings/poc/DF-4261 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 errorwithpst_creg.pr_resstill 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 β 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_testablefix.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
Confirmed kernel references
- sys/dev/acpica/acpi_cpu_pstate.c:500
- sys/dev/acpica/acpi_cpu_pstate.c:512
- sys/dev/acpica/acpi_cpu_pstate.c:547
- sys/dev/acpica/acpi_cpu_pstate.c:554
- sys/dev/acpica/acpi_cpu_pstate.c:574
- sys/dev/acpica/acpi_cpu_pstate.c:594
- sys/dev/acpica/acpi_cpu_pstate.c:601
- sys/dev/acpica/acpi_cpu_pstate.c:609
- sys/dev/acpica/acpi_cpu_pstate.c:690
- sys/dev/acpica/acpi_cpu_pstate.c:736
- sys/dev/acpica/acpi_cpu_pstate.c:1508
- sys/dev/acpica/acpi_package.c:120
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).
No comments yet.