# DF-4144 — Dangling `rdev->pm.dpm.ps` after kfree-without-NULL at `trinity_parse_power_table:1781` → double free in `trinity_dpm_fini:2089`, UAF via `boot_ps` at enable, `ps_priv[0..i-1]` leak

- File: `sys/dev/drm/radeon/trinity_dpm.c`
- Class: kernel heap double-free / use-after-free / leak (CWE-415/CWE-416)
- **Verified via the verbatim-code rig** (`rig/`): production slice
  byte-identical to the guest's `/usr/src/sys` (68 blocks, build.log); driven
  with a crafted Trinity DPM image and a deterministic fault-injecting
  allocator that fails the 2nd per-state `kzalloc(sizeof(struct sumo_ps))`,
  poisons freed allocations (PROT_NONE) and aborts on double free.
- Hardware gating: no AMD ASIC on the audit guest (env.txt) — kernel-context
  consequence of the demonstrated primitives is heap corruption / panic at
  driver unload (reboot or module unload) under the crafted-VBIOS /
  memory-pressure threat model.

## What the rig demonstrates (baseline build = production code)

| mode | result |
|---|---|
| `4144-doublefree` | state0 (BOOT) parses, state1 kzalloc fails → verbatim parser kfrees `dpm.ps` at :1781 WITHOUT NULLing (`num_ps` still 0, `ps_priv[0]` still attached, `boot_ps` still points into the array); the rig's allocator confirms the pointer is freed while `rdev` still holds it; the unload path (`radeon_pm.c:1705 radeon_dpm_fini`, unconditional) runs the verbatim `trinity_dpm_fini` → `kfree(rdev->pm.dpm.ps)` at :2089 frees it a SECOND time → `RESULT: DOUBLEFREE` (rc=89); the leak report shows state 0's `ps_priv` (128 B) never freed because `num_ps==0` hides it from fini |
| `4144-uaf` | same injection; instead of unload, the rig simulates radeon_pm.c:1448 discarding the init error and :1453 calling enable → the verbatim `trinity_dpm_enable` reads `boot_ps->ps_priv` at :1129 → `RESULT: FAULT ... USE-AFTER-FREE in freed allocation ... (+40 bytes in)` — the +40 is exactly offsetof(radeon_ps, ps_priv) |

## Fixed build (this finding's `fix.diff`, A/B on the identical image)

The parser error paths unwind (`ps_priv[0..i-1]` freed BEFORE `dpm.ps` is
freed, then `dpm.ps`/`boot_ps`/`uvd_ps` NULLed), and `trinity_dpm_fini` is
idempotent (`ps=NULL`, `num_ps=0`, `priv=NULL` after frees). Fixed run:
inject → init -ENOMEM → fini clean, **no double free, zero leaked
allocations**, and enable returns -EINVAL (the DF-4143 guard sees the NULLed
`boot_ps`) — no UAF.

## Build / Run (on the DragonFly guest)

```
cd rig && sh ./build.sh /usr/src DF-4144     # verbatim + fix-lines checks, cc
cd rig && sh ./run.sh                        # 14 modes x both builds
```

## Verdict

Reproduced (verbatim-rig): the exact production code performs a
kernel-equivalent double free at unload and a UAF read through the dangling
`boot_ps` at enable; `fix.diff` (unwind + idempotent fini) removes both and
the leak. See `VERDICT.md`, `verdict.json`.
