# DF-4023 — Dangling `rdev->pm.dpm.ps` after rv7xx_parse_power_table alloc failure → double free in rv770_dpm_fini (+ ps_priv leak)

**File:** `sys/dev/drm/radeon/rv770_dpm.c` (pass 2)
**Class:** CWE-415 double free (plus CWE-401 leak of parsed `ps_priv` states)
**Hardware gate:** RV770/RV730/RV710/RV740 GPU, plus a forced `kzalloc`
failure at rv770_dpm.c:2306 during attach (heap-pressure window at boot with
a many-state VBIOS). This is the rv770-family instance of the family
DF-3645(ci)/DF-3755(ni)/DF-3955(kv)/DF-3973(btc) — the **shared root site is
in this file** (rv770_dpm.c:2308), as DF-3973's notes anticipated; the rv770
dispatch (`rv770_asic`, radeon_asic.c:1216/1285) consumption via
`rv770_dpm_init`/`rv770_dpm_fini` is new in this finding.
**Status:** source-level certain (mechanism); runtime hardware+OOM-gated →
`inconclusive` per family convention.

## Root cause and chain

1. `rv7xx_parse_power_table()` (rv770_dpm.c:2271-2329) allocates the state
   array at :2289-2292 and one `struct rv7xx_ps` per state at :2306. If the
   per-state `kzalloc` fails at state *i*, the error path at :2307-2310 does
   `kfree(rdev->pm.dpm.ps);` **without NULLing it and without freeing the
   `ps_priv[0..i-1]` already stored**, then returns `-ENOMEM`.
   `rdev->pm.dpm.num_ps` is only set after the loop (:2327), so it stays 0.
2. `rv770_dpm_init()` propagates the error at :2368-2370 but leaves
   `rdev->pm.dpm.priv = pi` allocated (fine — fini frees it) and
   `rdev->pm.dpm.ps` **dangling**.
3. `radeon_pm_init_dpm()` **ignores** the init failure (radeon_pm.c:1448) and
   proceeds to `radeon_dpm_enable()` at :1453. That call dereferences
   `boot_ps == NULL` (see DF-4022) — unless it fails *earlier and
   gracefully*, which it does whenever
   * `rv770_construct_vddc_table()` returns `-EINVAL` (steps > 32,
     rv770_dpm.c:1912-1916), or
   * `rv770_upload_firmware()` fails (missing SMC ucode, :1951-1955).
   Then `radeon_pm_init_dpm()` takes the graceful `dpm_failed` path
   (radeon_pm.c:1456/1469-1486) — attach completes with `dpm_enabled=false`
   and `rdev->pm.dpm.ps` still dangling.
4. On driver unload / reboot: `rv770_fini()` (rv770.c:1996-1998) →
   `radeon_pm_fini()` (radeon_pm.c:1721-1724) → `radeon_pm_fini_dpm()` →
   `radeon_dpm_fini()` unconditionally (radeon_pm.c:1705) = `rv770_dpm_fini()`
   (rv770_dpm.c:2543-2552):
   * `num_ps == 0` ⇒ the `ps_priv[]` loop is skipped ⇒ the *i* already
     parsed per-state allocations **leak permanently**;
   * `kfree(rdev->pm.dpm.ps)` at :2550 frees an **already-freed block** →
     kmalloc freelist corruption (double free), i.e. panic at best,
     heap-metadata corruption at worst, triggered by detach/shutdown.

## Impact

Kernel heap double-free at driver unload/reboot after a memory-pressure
window during attach on RV770-family hardware; plus a deterministic leak of
`i * sizeof(struct rv7xx_ps)` (~100 B/state, up to ~25 KB). Primary actor:
kernel heap corruption (panic/DoS; heap grooming at kldunload-time is
root-gated). Precondition is an allocation failure inside the parse loop —
same trigger class accepted for DF-3755 (ni) and DF-3955 (kv).

## Fix

Free the already-parsed `ps_priv` states, NULL `rdev->pm.dpm.ps` on the
error path, and make `rv770_dpm_fini()` idempotent (NULL after kfree).
Combined with the DF-4022 fix (propagating `dpm.init` failure) the dangling
publication is unreachable. See `fix.diff`.

## 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.
