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

acpi_parse_prw() copies _PRW elements 0..N-3 instead of 2..N as wake power resources (off-by-two)

Summary

The code's own comment (and the ACPI spec) define _PRW elements 2..N as power-resource references and power_count = Count-2, but the copy loop indexes Elements[i] for i in [0, power_count) instead of Elements[i+2]. The mis-copied elements 0/1 (GPE indicator and lowest-wake INTEGER) are dropped by acpi_GetReference()'s type check, so with Count==3 the sole genuine power resource is never referenced and larger packages get a shifted prefix - wake power rails are never raised at sleep nor cleaned on resume. Inherited verbatim from FreeBSD main. Type-safe (no memory corruption): availability/functional defect on firmware with _PRW power resources (PCIe WOL, lid/USB wake on laptops). Not runtime-triggerable on the audit guest (QEMU DSDT contains zero _PRW objects - verified); consumer trace documented; fix compiled into the patched validation kernel.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3795 Β· 4 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 2.4 KB ↓ raw
fix.diff β€” 310 B view raw
verdict.json β€” 2.7 KB view raw

DF-3795 β€” acpi_parse_prw() copies the wrong _PRW elements as power resources

  • File: sys/dev/acpica/acpi.c (pass 2)
  • Lines: 2962–2970 (off-by-two at 2969–2970)
  • Class: CWE-682 (incorrect computation) / incorrect index
  • Severity: Low (functional/availability, firmware-dependent) Β· Confidence: certain

What

Per ACPI spec (and the code's own comment), _PRW packages are:

Package { <gpe>, <lowest_wake_sstate>, <power-resource ref>, ... }
          elem0    elem1                elem2 .. elemN

acpi_parse_prw() computes power_count = Package.Count - 2 β€” the number of power-resource references β€” but then copies elements 0 .. count-1 instead of 2 .. N:

    /* Elements 2 to N of the _PRW object are power resources. */
    power_count = res->Package.Count - 2;
    ...
    for (i = 0; i < power_count; i++)
    prw->power_res[i] = res->Package.Elements[i];   /* should be [i + 2] */

Consequences in the consumer (acpi_powerres.c:552-556 acpi_pwr_wake_enable() β†’ acpi_pwr_reference_resource() β†’ acpi_GetReference(), acpi_package.c:132):

  • Elements 0 and 1 are an INTEGER (GPE bit) or a PACKAGE (GPE handle+bit) and an INTEGER (lowest wake state). acpi_GetReference() type-checks and returns NULL for both β‡’ dropped (no memory-safety impact).
  • With exactly one power resource (Count == 3) the real reference at element 2 is never copied; with more, a shifted prefix (2..Count-3) is used. Net effect: wake power resources are not powered on at sleep (acpi_wake_sleep_prep, acpi.c:2752-2754) nor cleaned up on wake (acpi.c:2789-2790), so devices whose wake depends on those rails cannot wake the machine (availability), and stale/extra references may keep rails on after resume.

This bug is inherited from FreeBSD (verified identical in freebsd-src/main sys/dev/acpica/acpi.c ~line 4117), so it is a long-standing MI defect, not a DFly-only divergence β€” but it is still a defect in this tree.

Guest testability

The QEMU guest's DSDT contains no _PRW objects at all (acpidump -b -n DSDT; iasl -d; grep -c _PRW β†’ 0), so the mis-parse cannot be triggered on this guest; the verdict is code-certain but runtime-not_reproduced. Hardware with wake-capable devices whose _PRW lists power resources (Count > 2 β€” common for PCIe WOL, wake-on-lid, Suns/USB on laptops) is affected.

Fix

See fix.diff: prw->power_res[i] = res->Package.Elements[i + 2];

VERDICT.md
↓ download raw

DF-3795 VERDICT

Status: not_reproduced on this guest β€” code-certain by trace; no _PRW in guest firmware

Guest: QEMU-emulated DragonFly 6.5-DEVELOPMENT (X86_64_GENERIC).

Why not reproduced at runtime

acpidump -b -n DSDT && iasl -d on the guest: its DSDT contains zero _PRW objects (grep -c _PRW dsdt.dsl β†’ 0). The mis-parse only fires for _PRW packages with Count > 2 (i.e. wake-capable devices listing power resources). Exercising it requires different firmware, not different userspace β€” a guest-runtime proof is impossible on this DSDT, so the honest status is not_reproduced rather than a forced demonstration.

Why the code defect is certain

  • The function's own comment (acpi.c:2962) states elements 2 to N are the power resources; the loop at acpi.c:2969-2970 copies Elements[i] for i in [0, power_count), i.e. elements 0 to Count-3.
  • ACPI spec semantics of _PRW: elem0 = GPE indicator (INTEGER or PACKAGE), elem1 = lowest wake sleep state, elem2.. = power-resource references.
  • Consumer path is type-safe β€” acpi_pwr_reference_resource() (acpi_powerres.c:582) β†’ acpi_GetReference() (acpi_package.c:132-160) returns NULL for INTEGER/PACKAGE elements β€” so elements 0/1 are dropped, and with Count==3 the single genuine power resource at element 2 is never referenced. Effect: acpi_wake_sleep_prep (acpi.c:2752-2754) never turns on the device's wake power rails; acpi_wake_run_prep (acpi.c:2789-2790) never cleans them. Availability/functional impact on wake-capable hardware; no memory-safety impact.
  • Cross-check: FreeBSD main has the identical line (freebsd-src/sys/dev/acpica/acpi.c ~4117: Elements[i]) β€” inherited MI bug, not a DFly divergence.

Fix validation

fix.diff (Elements[i + 2]) was compiled into the patched test kernel together with the DF-3793/DF-3794 fixes (one-line change; the affected branch is unreachable on this guest's firmware), so runtime behavioral validation of this specific change is honestly not_testable here; the index arithmetic is self-evident against the comment and the consumer.

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 is a one-line index correction compiled successfully into the patched test kernel; no _PRW-bearing firmware on this guest so runtime before/after comparison is impossible.

guest build.log shows the patched acpi.c (all three fixes) compiling and linking into X86_64_GENERIC.
↓ fix.diffper-fix-DF-3795

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md: DSDT survey (grep -c _PRW -> 0); code trace acpi.c:2962-2970 vs consumer acpi_powerres.c:582 + acpi_package.c:132-160; FreeBSD cross-check.

PoC changes

no executable PoC possible on this guest (firmware lacks _PRW); dsdt-survey commands documented in VERDICT.md

Verified recommended fix

prw->power_res[i] = res->Package.Elements[i + 2];

Verdict

acpi_parse_prw() computes power_count = Package.Count - 2 (elements 2..N per its own comment and the ACPI spec) but copies res->Package.Elements[i] for i in [0, power_count) instead of [i+2] (acpi.c:2969-2970). The mis-copied elements 0/1 (GPE indicator / lowest wake state) are dropped by acpi_GetReference()'s type check (acpi_package.c:132-160), so with Count==3 the genuine power resource at element 2 is never referenced and larger packages get a shifted prefix: wake power rails are not raised at sleep nor cleaned on resume (acpi_powerres.c:552-556, acpi.c:2752-2754/2789-2790). Functional/availability defect on firmware with _PRW power resources; no memory-safety impact. Not reproducible on the audit guest: its QEMU DSDT contains zero _PRW objects (verified via acpidump+iasl), so runtime status is honestly not_reproduced; the defect is certain from the code/comment/consumer trace and is inherited verbatim from FreeBSD main (~line 4117).