# DF-3645 — verdict narrative

## Finding
UAF + double free of rdev->pm.dpm.{ps,priv}: ci_parse_power_table/ci_dpm_init free and leave dangling pointers, and radeon_pm_init_dpm ignores the dpm.init failure

## Kernel references
- `sys/dev/drm/radeon/ci_dpm.c:5608`
- `sys/dev/drm/radeon/ci_dpm.c:5620`
- `sys/dev/drm/radeon/ci_dpm.c:5624`
- `sys/dev/drm/radeon/ci_dpm.c:5669`
- `sys/dev/drm/radeon/ci_dpm.c:5703`
- `sys/dev/drm/radeon/ci_dpm.c:5764`
- `sys/dev/drm/radeon/ci_dpm.c:5782`
- `sys/dev/drm/radeon/ci_dpm.c:5835`
- `sys/dev/drm/radeon/radeon_pm.c:1448`
- `sys/dev/drm/radeon/radeon_pm.c:1458`
- `sys/dev/drm/radeon/ci_dpm.c:1904`
- `sys/dev/drm/radeon/cik.c:8730`
- `sys/dev/drm/radeon/radeon_atombios.c:2784`

## Was it reproduced?
No — not on the kernel path. The audit guest is virtio-only (0 AMD PCI devices, /dev/dri absent, no radeon/drm kld; env.txt), so `sys/dev/drm/radeon/ci_dpm.c` can never execute there. Per the pass-2 instruction for this hardware-gated file, the verdict is **inconclusive** (source-level certainty certain) rather than reproduced.


## Root cause and reasoning

Source-certain lifetime bug, radeon/ci twin of DF-3588 (amdgpu), DF-3611 (radeon si), DF-3624 (amdgpu ci). Three legs: (1) ci_parse_power_table's error path (ci_dpm.c:5669-5673) kfrees rdev->pm.dpm.ps and every ps_priv but leaves rdev->pm.dpm.ps dangling and num_ps at its parsed value. The path is reachable when the per-state kzalloc fails (5624-5628) or when the rdev->pm.power_state[i].clock_info guard (5620-5623) trips - which it does whenever radeon_atombios_parse_power_table_4_5 bailed out early leaving later entries NULL, and the guard itself reads rdev->pm.power_state[i] out of bounds when the legacy parser fell back to its single default power_state (radeon_atombios.c:2784) while ci iterates the crafted state_array->ucNumEntries. (2) ci_dpm_init error paths (5764/5770/5776/5782) kfree rdev->pm.dpm.priv without NULLing it. (3) radeon_pm_init_dpm (radeon_pm.c:1448) calls radeon_dpm_init(rdev) and DISCARDS the return, then immediately runs radeon_dpm_setup_asic() -> ci_dpm_setup_asic -> ci_read_clock_registers (ci_dpm.c:1904-1929), which WRITES 14 register values into the freed ci_power_info, and radeon_dpm_enable -> ci_dpm_enable which reads pi->voltage_control from freed memory. At teardown, radeon_driver_unload_kms -> radeon_device_fini -> cik_fini (cik.c:8730) -> radeon_pm_fini -> radeon_pm_fini_dpm -> ci_dpm_fini (ci_dpm.c:5703-5714) unconditionally kfrees the already-freed ps_priv array, ps array and priv -> double free. Kernel-level reproduction is impossible on this virtio-only guest; verdict inconclusive with source-level certainty.

## Exploitability

Force a ci_dpm_init failure with rdev->pm.dpm state already populated (memory-pressure kzalloc failure inside ci_parse_power_table, or a crafted VBIOS whose legacy power-state parse leaves power_state[i].clock_info NULL for some i < ucNumEntries): boot then writes into freed ci_power_info (UAF write of hardware register values) and unloading the driver double-frees the ps array - classic exploitable double-free on the DRM slab if the intervening UAF writes haven't already crashed. Requires the same crafted-VBIOS/allocation-pressure preconditions as the si twin DF-3611; no CI hardware on this guest so no kernel-path PoC was possible.

## Recommended fix
NULL out rdev->pm.dpm.{ps,priv} and reset num_ps on every free site in ci_dpm.c, and honor the radeon_dpm_init() return value in radeon_pm_init_dpm().
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.
