β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3645 Β· 6 files
FileTypeDescriptionSize
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
README.md doc
↓ download 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).

VERDICT.md doc
↓ download raw

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

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.

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_testable
baseline no→ patch + rebuild →patched clean

Fix 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).
↓ fix.diffper-fix-DF-3645

Confirmed kernel references

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.