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

Dangling rdev->pm.dpm.ps after swallowed cypress_dpm_init failure: double free in cypress_dpm_fini at unload/reboot (cypress dispatch of the DF-4023/DF-3973 family)

Summary

rv7xx_parse_power_table's per-state kzalloc failure path kfree's dpm.ps without NULLing and without freeing ps_priv[0..i-1]; num_ps stays 0. cypress_dpm_init propagates the error but it is swallowed TWICE (radeon_pm.c:1448 discards .dpm.init's return; evergreen.c:5235 calls radeon_pm_init as void) - attach completes with ps dangling. At unload/reboot radeon_dpm_fini runs UNCONDITIONALLY -> cypress_dpm_fini: the num_ps==0 loop leaks the parsed states, then kfree(dpm.ps) double-frees the block -> freelist corruption/panic. Also leaks eg_pi on the earlier platform_caps failure. Three-part family fix (parse cleanup + init cleanup + idempotent fini).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4117 Β· 6 files
FileTypeDescriptionSize
README.md β€” 1.7 KB ↓ raw
VERDICT.md β€” 3.0 KB ↓ raw
fix.diff β€” 1.6 KB view raw
env.txt β€” 583 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 3.1 KB view raw

DF-4117 β€” Dangling rdev->pm.dpm.ps + ignored cypress_dpm_init error: double free in cypress_dpm_fini (cypress dispatch) ================================================================== File: sys/dev/drm/radeon/cypress_dpm.c β€” pass 2 (GLM 5.3). Cypress-dispatch instance of the DF-3645/3755/3955/3973/4023 family (shared root site rv770_dpm.c:2306-2310; btc twin DF-3973, rv770 twin DF-4023 β€” same class, this file's own consumption sinks).

MECHANISM (all path:line in VERDICT.md): * rv7xx_parse_power_table kzalloc(ps_priv) failure mid-loop kfree's rdev->pm.dpm.ps WITHOUT NULLing it; num_ps stays 0; ps_priv[0..i-1] leak (rv770_dpm.c:2306-2310). * cypress_dpm_init:2050-2052 propagates the error but leaves rdev->pm.dpm.priv set (also leaks eg_pi on the earlier r600_get_platform_caps failure). * radeon_pm.c:1448 discards .dpm.init's return; evergreen.c:5235 calls radeon_pm_init() as a void statement β€” attach completes with dpm_enabled=false and ps dangling. * unload/reboot: radeon_pm_fini_dpm -> radeon_dpm_fini runs UNCONDITIONALLY (radeon_pm.c:1705) -> cypress_dpm_fini:2150-2153: num_ps==0 skips the ps_priv loop (leak), kfree(rdev->pm.dpm.ps) DOUBLE-FREES the block freed at parse time.

TRIGGER (hardware-gated): evergreen-family GPU + crafted many-state VBIOS + memory pressure at attach; unload/reboot detonates. IMPACT: kmalloc double free (heap corruption/panic at detach); realistic ceiling DoS (trigger point is boot/attach + root unload). Hardware absent on audit guest => runtime inconclusive, source-level certain (family precedent: DF-3973/DF-4023 packs). FIX: fix.diff (parse error path cleanup + init cleanup + idempotent fini), mirrors the accepted family fixes.

VERDICT.md
↓ download raw

DF-4117 VERDICT β€” double free in cypress_dpm_fini via dangling dpm.ps

STATUS: inconclusive (runtime hardware-gated; no evergreen GPU on the audit guest); mechanism source-certain; family convention per DF-3973/DF-4023. impact ceiling (not demonstrated): heap corruption / panic at unload. confidence: certain.

CHAIN (path:line): 1. rv7xx_parse_power_table (SHARED, rv770_dpm.c:2271-2333): kzalloc(rv7xx_ps) failure inside the state loop at :2306-2310 executes kfree(rdev->pm.dpm.ps) (:2308) WITHOUT setting it to NULL, WITHOUT freeing the ps_priv[0..i-1] stored earlier, and returns -ENOMEM before num_ps is assigned (:2331) β€” num_ps stays 0. 2. cypress_dpm_init (cypress_dpm.c:2050-2052) propagates the error but leaves rdev->pm.dpm.priv = eg_pi (allocated :2032-2035); the earlier r600_get_platform_caps failure (:2046-2048) returns without cleanup as well (eg_pi leak). 3. The error is swallowed TWICE: radeon_pm.c:1448 radeon_dpm_init(rdev); discards .dpm.init's int return, and every asic init calls radeon_pm_init() as a void statement (evergreen.c:5235 for this family). radeon_pm.c:1449 assigns current_ps/requested_ps = boot_ps (NULL, see DF-4118), then calls radeon_dpm_setup_asic + radeon_dpm_enable; enable fails (or panics per DF-4118); attach completes with dpm_enabled=false and rdev->pm.dpm.ps DANGLING. 4. Detonation: radeon_pm_fini (unload/reboot) -> radeon_pm_fini_dpm (radeon_pm.c:1690) -> radeon_dpm_fini UNCONDITIONALLY (:1705, not gated on dpm_enabled) -> cypress_dpm_fini (cypress_dpm.c:2146-2155): the i < num_ps loop skips (num_ps == 0) so the i parsed ps_priv states LEAK, then kfree(rdev->pm.dpm.ps) at :2153 double-frees the block freed at step 1 β€” kmalloc freelist corruption, typically panic at unload/reboot; on a foreign reclaim, memory-safety consequences in principle (realistic trigger point is boot/attach under memory pressure + root unload β€” ceiling DoS).

RELATION TO KNOWNS: this file's knowns (DF-1385/1386/1387) do not cover lifecycle. The btc/rv770 twins (DF-3973/DF-4023) established the shared root; the cypress dispatch (radeon_asic.c:1405 for CYPRESS/HEMLOCK/JUNIPER/REDWOOD/CEDAR) consumption is this finding.

FIX: fix.diff β€” (1) shared parse error path frees ps_priv[0..i-1] and NULLs rdev->pm.dpm.ps; (2) cypress_dpm_init frees eg_pi on early failure; (3) cypress_dpm_fini NULLs ps/priv after kfree. Kernel-level fix validation impossible on this guest (no hardware); fix_status not_testable. Diff is the same shape already validated for the btc twin family (DF-3973).

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

Diff authored (same shape as the family fixes) but kernel-level validation requires evergreen hardware absent from the audit guest.

fix.diff; VERDICT.md
↓ fix.diffper-fix-DF-4117

Confirmed kernel references

Detail

Exploit chain

On CYPRESS/HEMLOCK/JUNIPER/REDWOOD/CEDAR hardware: many-state crafted VBIOS + attach-time kernel memory pressure forcing the kzalloc at rv770_dpm.c:2306 to fail; double error-swallow keeps the driver attached; kldunload/reboot double-frees the ps array. Trigger point is boot/attach (physical/vBIOS access) β€” realistic ceiling DoS, not an unprivileged remote path.

Evidence (decisive lines)

VERDICT.md (chain with path:line); fix.diff (parse-path cleanup + init cleanup + idempotent fini); env.txt (hardware absence)

PoC changes

No seed PoC; hardware-gated (no AMD evergreen GPU on the audit guest), pack follows the DF-3973/DF-4023 convention.

Verified recommended fix

Free ps_priv[0..i-1] and NULL rdev->pm.dpm.ps in the parse error path; free eg_pi on cypress_dpm_init early failure; make cypress_dpm_fini idempotent (fix.diff).

Verdict

Source-level certain, runtime hardware+OOM-gated (family convention of DF-3973/DF-4023). rv7xx_parse_power_table's per-state kzalloc failure (rv770_dpm.c:2306-2310) kfree's rdev->pm.dpm.ps without NULLing it and without freeing ps_priv[0..i-1]; num_ps stays 0. cypress_dpm_init (cypress_dpm.c:2050-2052) propagates but leaks eg_pi; the error is swallowed twice (radeon_pm.c:1448 discards .dpm.init's return; evergreen.c:5235 calls radeon_pm_init() as a void statement), so attach completes with dpm_enabled=false and ps dangling. At unload/reboot radeon_pm_fini_dpm -> radeon_dpm_fini runs unconditionally (radeon_pm.c:1705) -> cypress_dpm_fini:2150-2153: num_ps==0 skips the ps_priv loop (leak) and kfree(rdev->pm.dpm.ps) at :2153 double-frees the block freed at parse time => kmalloc freelist corruption/panic at unload. Cypress dispatch (radeon_asic.c:1405) instance of the DF-3645/3755/3955/3973/4023 family; this file's knowns (DF-1385/1386/1387) do not cover lifecycle.