# DF-3891 — double free of `adev->pm.dpm.ps` on the allocation-failure path of `kv_parse_power_table`

**File:** `sys/dev/drm/amd/amdgpu/kv_dpm.c:2750` (first free, pointer not cleared) + `:2919` (`kv_dpm_fini` frees again); related leak of `ps_priv` allocations at `:2753`
**Class:** CWE-415 double free (+ CWE-401 leak of the already-parsed state privs)
**Verdict:** `inconclusive` at runtime on this guest (no AMD GPU); **source-certain** call sequence; harness shows the second `free()` accepted silently by the allocator.

## Build (on the DragonFly guest, as root)

    mkdir -p /tmp/DF-3891 && cp double_free.c /tmp/DF-3891/h.c
    cd /tmp/DF-3891 && cc -Wall -O0 -o t h.c

## Run

    ./t          # accounting — exact call sequence
    ./t crash    # performs the verbatim free(); free() sequence

## Expected

- accounting: `kv_parse_power_table: kfree(ps) at :2750 (pointer left dangling;
  num_ps still 0)` then `kv_dpm_fini: kfree(ps) at :2919` on the same pointer.
- crash: the second `free()` is accepted **silently** (no allocator detection,
  exit=0) — in the kernel the same `kfree()` re-inserts an already-freed block
  into the kmalloc freelist, i.e. latent heap corruption (two future allocations
  may alias); the userspace allocator did not hand out aliased blocks in this
  short run, which is reported honestly.

## Kernel trigger

`kzalloc` failure for a `struct kv_ps` while parsing state `i` (memory pressure
during driver init) → `kfree(adev->pm.dpm.ps)` at :2750, `return -ENOMEM` with
`num_ps` still 0 → `kv_dpm_sw_init` dpm_failed → `kv_dpm_fini`:
0-iteration priv loop (states 0..i-1 **leak**) + `kfree(adev->pm.dpm.ps)` again
at :2919 → **double free**.
