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
Summary
err_free_ps kfrees rdev->pm.dpm.ps and ps_privs leaving dangling pointers and num_ps>0; ci_dpm_init error paths kfree rdev->pm.dpm.priv without NULLing; radeon_pm.c:1448 then DISCARDS the radeon_dpm_init() return and runs radeon_dpm_setup_asic -> ci_read_clock_registers which WRITES 14 register values into the freed ci_power_info, and ci_dpm_enable reads freed pi->voltage_control; teardown -> ci_dpm_fini unconditionally kfrees the already-freed arrays -> double free. The :5620 guard itself reads rdev->pm.power_state[i] OOB when the legacy parser fell back to its 1-element default array. Alloc failure or crafted VBIOS -> UAF write into freed ci_power_info at boot, double-free of the DRM ps array at unload (twin of DF-3588/DF-3611/DF-3624).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3645 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | doc | 669 B | β raw | |
| VERDICT.md | doc | 3.8 KB | β raw | |
| fix.diff | doc | 1.8 KB | view raw | |
| env.txt | doc | 799 B | view raw | |
| verdict.json | doc | 4.0 KB | view raw | |
| manifest.json | doc | 1.2 KB | view raw |
DF-3645 β 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
File: sys/dev/drm/radeon/ci_dpm.c (pass 2, GLM 5.3 re-audit)
Severity: Medium | CWE-416 Use After Free / CWE-415 Double Free | confidence: certain
Reproduce
No harness: the defect is a NULL/UAF control-flow property (source-level certainty); the kernel path needs CI-class hardware absent from the guest β see env.txt.
(source review only; see VERDICT.md)
Full narrative: VERDICT.md. Proposed fix: fix.diff (authored against the read-only sys/ tree; never applied there).
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:5608sys/dev/drm/radeon/ci_dpm.c:5620sys/dev/drm/radeon/ci_dpm.c:5624sys/dev/drm/radeon/ci_dpm.c:5669sys/dev/drm/radeon/ci_dpm.c:5703sys/dev/drm/radeon/ci_dpm.c:5764sys/dev/drm/radeon/ci_dpm.c:5782sys/dev/drm/radeon/ci_dpm.c:5835sys/dev/drm/radeon/radeon_pm.c:1448sys/dev/drm/radeon/radeon_pm.c:1458sys/dev/drm/radeon/ci_dpm.c:1904sys/dev/drm/radeon/cik.c:8730sys/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.
Fix verification
not_testableFix validation would require a CI-class GPU with a crafted VBIOS (baseline panic/corruption) and a single-fix kernel build; the audit guest is virtio-only, so the fix is source-reviewed only (fix.diff).
fix.diff in this pack; not applied to sys/ (read-only audit target).
Confirmed 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
Detail
Exploit chain
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.
Evidence (decisive lines)
VERDICT.md (full source trace); env.txt (guest has no AMD GPU: pciconf 0x1002 count=0, /dev/dri absent, no drm kld); fix.diff
PoC changes
n/a β no seeded PoC; source-trace only
Verified 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().
Verdict
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.
No comments yet.