radeon_pm.c:1448 discards radeon_dpm_init() return (and the asic *_init files discard radeon_pm_init's): the root enabler of the six-file NULL-boot_ps / dangling-ps family β central hub fix consolidating DF-4022/3972/4117/4118/4124/4125/4143/4144
Summary
radeon_pm_init_dpm calls radeon_dpm_init as a void statement, discarding the error upstream honors. Two failure shapes: (A) parse errors swallowed; (B) a VBIOS with no BOOT-classified state returns 0 with boot_ps NULL because NO dispatch init validates it. Either way :1449 publishes current_ps=requested_ps=boot_ps (NULL/dangling), :1452-1453 run enable whose per-asic bodies deref boot_ps -> attach panic; the dangling ps survives into fini (double free); suspend/resume/fini-disable re-deref boot_ps because pm_method stays DPM - a second swallow layer (every asic *_init calls radeon_pm_init as a statement). CENTRAL HUB FIX: honor the init return + explicit boot_ps gate + scrub ps/num_ps/boot_ps/current/requested on failure + gate suspend/resume/fini-disable on boot_ps. Applying it makes the six family enable-side panics unreachable at boot AND suspend/resume/unload, and neutralizes the ps double-frees; residual per-asic table double-frees still need the dispatch fixes.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4162 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| fix.diff | β | 2.3 KB | view raw | |
| env.txt | β | 804 B | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw |
DF-4162 β radeon_pm.c:1448 swallows radeon_dpm_init() failure: the root enabler of the six-file NULL-boot_ps panic / dangling-ps double-free family (DF-4022/3972/4117/4118/4124/4125/4143/4144)
File: sys/dev/drm/radeon/radeon_pm.c (pass 2)
What
Two layers of discarded returns sit in front of every radeon DPM defect:
- The hub β
radeon_pm_init_dpm()callsradeon_dpm_init(rdev);as a void statement (radeon_pm.c:1448), discarding the int returned byrdev->asic->dpm.init(radeon.h:2793#define radeon_dpm_init(rdev) rdev->asic->dpm.init((rdev))). Upstream'sradeon_pm_init_dpmdoesret = radeon_dpm_init(rdev); if (ret) goto dpm_failed;β the check was dropped in the DFly port. - The dispatch files β every
*_asic_init(si.c:6874, ni.c:2432, cik.c:8653, evergreen.c:5235, rv770.c:1962, r600.c:3321, r100.c:4107, β¦) callsradeon_pm_init(rdev);as a void statement too, so even a propagated error dies there and the driver comes up inPM_METHOD_DPMwith a dead DPM state.
Consequence (why this is the family root)
radeon_dpm_init failure modes leave rdev->pm.dpm.boot_ps == NULL (no
BOOT-classified state in the VBIOS power table β boot_ps is only assigned
inside the per-state parse loops, e.g. rv770_dpm.c:2172-2173, rv6xx_dpm.c:1811,
trinity_dpm.c:1705, si_dpm.c:6727; no dispatch init validates it) or leave
rdev->pm.dpm.ps dangling (parse error paths kfree without NULL β the
DF-4117/4125/4144 twins). With the return swallowed, radeon_pm.c:1449-1457 then:
- publishes
current_ps = requested_ps = boot_ps(possibly NULL/dangling), - runs
radeon_dpm_print_power_states()(radeon_dpm == 1β walksps[i]), - runs
radeon_dpm_setup_asic(), - runs
radeon_dpm_enable()β the per-asic enables that derefboot_ps(rv770_dpm.c:1901, rv6xx_dpm.c:1577, btc/trinity/cypress twins) β page-fault panic at drm attach, and - leaves the state in place so
radeon_pm_suspend_dpm/radeon_pm_resume_dpm(:1262/:1320, unguarded) andradeon_pm_fini_dpmβradeon_dpm_disable(:1694) re-deref the same NULL/dangling pointers at suspend/resume/unload, and*_dpm_finidouble-frees the danglingps.
Holding the return makes the whole enable-side family unreachable at boot; the
boot_ps gate additionally covers the "init returns 0 but no BOOT state" case
(parse succeeded, classification absent) which honoring the return alone does
NOT close β verified: rv7xx_parse_power_table returns 0 with boot_ps unset
(rv770_dpm.c:2271-2329).
Trigger
Crafted VBIOS PowerPlayInfo (no BOOT-classified state, or ucStateEntrySize<=1)
on rv6xx/rv7xx/evergreen/btc/trinity/si hardware, or any dpm.init failure
(SMC/parse/OOM). Same trigger set as DF-4022 β this finding supplies the
central fix that consolidates the family.
Fix
fix.diff (this pack) β the systemic fix in radeon_pm.c only, covering all six
dispatch files at once:
1. honor radeon_dpm_init's return,
2. fail init when boot_ps is NULL,
3. scrub dangling ps/boot_ps/current_ps on init failure so
radeon_dpm_fini cannot double-free,
4. guard radeon_pm_suspend_dpm / radeon_pm_resume_dpm /
radeon_pm_fini_dpm's radeon_dpm_disable on boot_ps (needed because the
asic *_init files also swallow radeon_pm_init's return, so the device
still comes up with pm_method == PM_METHOD_DPM).
Status on this audit guest
Hardware-gated (virtio VGA only, no 0x1002 device β env.txt). Per the DF-4022 family convention: source-certain / runtime-inconclusive.
DF-4162 β VERDICT
Status: inconclusive (source-level certain, runtime hardware-gated). Expected impact if hardware present: drm-attach panic / suspend-resume panic / double-free at unload on crafted-VBIOS or init-failed radeon DPM β the systemic root enabler of DF-4022/3972/4117/4118/4124/4125/4143/4144.
Reproduced?
No β the audit guest has no AMD/ATI GPU (env.txt: virtio VGA only, zero 0x1002
PCI devices, hw.drm unknown oid). Per the DF-4022 family convention: source
certain, runtime inconclusive.
Root-cause proof (path:line)
radeon_pm_init_dpm()invokes the asic init as a statement:radeon_dpm_init(rdev);(radeon_pm.c:1448). The macro expands tordev->asic->dpm.init((rdev))(radeon.h:2793) β anintreturn β which is discarded. Upstream'sradeon_pm_init_dpmcontainsret = radeon_dpm_init(rdev); if (ret) goto dpm_failed;at the same point.- Failure semantics of the dispatch inits:
* hard failure (
-EINVAL/-ENOMEM) from parse errors β swallowed at :1448; * silent success withboot_ps == NULL:boot_psis only assigned when a state carriesATOM_PPLIB_CLASSIFICATION_BOOTinside the per-state parse loops (rv770_dpm.c:2172-2173, rv6xx_dpm.c:1811-1812, trinity_dpm.c:1705, si_dpm.c:6727, kv_dpm.c:2599); no*_dpm_initvalidates it (grep: zeroboot_ps == NULLchecks in sys/dev/drm/radeon).rv7xx_parse_power_tablereturns 0 withboot_psunset (rv770_dpm.c:2271-2329) β so honoring the return alone does NOT close this half; the hub-levelboot_psgate in fix.diff does. - With the error dropped, :1449 publishes
current_ps = requested_ps = boot_ps(NULL/dangling), :1450-1451 can walkps[]viaradeon_dpm_print_power_states, :1452 runsradeon_dpm_setup_asic, :1453 runsradeon_dpm_enableβ per-asic enables derefboot_ps(rv770_dpm.c:1901, rv6xx_dpm.c:1577, btc/trinity/cypress twins) β page fault at drm attach (= DF-4022/4118/4124/4143/3972 boot panics). - Even when
radeon_dpm_enablefails intodpm_failed, the danglingpssurvives;radeon_pm_fini_dpmβradeon_dpm_finiβkfree(ps)again (= DF-4117/4125/4144 double frees), andradeon_pm_suspend_dpm(:1262)/radeon_pm_resume_dpm(:1320)/fini'sradeon_dpm_disable(:1694) re-derefboot_psbecausepm_methodstaysPM_METHOD_DPMβ the second swallow layer: every asic*_initcallsradeon_pm_init(rdev);as a statement (si.c:6874, ni.c:2432, cik.c:8653, evergreen.c:5235, rv770.c:1962, r600.c:3321, β¦).
Does the central fix make the six family NULL-boot_ps panics unreachable?
Yes for the enable/boot side, and for suspend/resume/fini-disable, via one
file. With fix.diff:
* init error β dpm_init_failed β no setup_asic, no enable, dpm_enabled
stays false β radeon_pm_compute_clocks_dpm returns at :1817 β no
change_power_state_locked, no set_power_state β the family's
get_sclk/get_mclk/enable NULL derefs cannot run;
* no-BOOT-state-but-init-returned-0 β caught by the explicit boot_ps gate;
* suspend/resume/fini-disable are gated on boot_ps (needed because the asic
*_init swallow layer keeps pm_method == DPM after failed init);
* dangling ps is scrubbed to NULL so the unload-time *_dpm_fini kfree is a
no-op instead of a double free.
Residual: per-asic extended-table double frees (DF-4023-style ext tables freed
by parse error paths but left non-NULL) are not fully covered by the hub scrub;
those need the already-filed dispatch fixes. The asic *_init void-call of
radeon_pm_init (second layer) is left as-is to keep the fix single-file and
minimal; with the hub gating, that layer only causes a lost error message.
Trigger (on hardware)
Identical to DF-4022's: crafted VBIOS PowerPlayInfo without a BOOT-classified
state (or ucStateEntrySize<=1), or any dpm.init failure (missing/failing SMC
upload, parse OOM) on rv6xx/rv7xx/evergreen/btc/trinity/si/cik. Expected:
unpatched β attach panic (family signature); patched β "radeon: dpm
initialization failed", driver up in degraded no-DPM mode, clean unload.
Fix validation
git apply --check clean against the read-only tree. No runtime validation
possible (guest lacks radeon hardware). Composes with DF-4163's
cancel_work_sync hunk (different region of radeon_pm_fini_dpm).
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.diff applies clean (git apply --check) against the read-only tree; single-file central fix that makes the six family boot/suspend/resume NULL-boot_ps panics unreachable and neutralizes the ps double-frees; cannot be runtime-validated without radeon hardware.
findings/poc/DF-4162/fix.diff
Confirmed kernel references
- sys/dev/drm/radeon/radeon_pm.c:1448
- sys/dev/drm/radeon/radeon_pm.c:1449
- sys/dev/drm/radeon/radeon_pm.c:1453
- sys/dev/drm/radeon/radeon_pm.c:1262
- sys/dev/drm/radeon/radeon_pm.c:1320
- sys/dev/drm/radeon/radeon_pm.c:1694
- sys/dev/drm/radeon/radeon.h:2793
- sys/dev/drm/radeon/si.c:6874
- sys/dev/drm/radeon/rv770_dpm.c:2172
- sys/dev/drm/radeon/rv770_dpm.c:2271
- sys/dev/drm/radeon/rv770_dpm.c:1901
Detail
Exploit chain
On radeon hardware: crafted/reflash VBIOS PowerPlayInfo without BOOT state (passthrough/hotplug) -> drm attach panics (family signature) or unload double-free. No write primitive controlled by the attacker; primarily reboot-loop DoS plus allocator poisoning window on the double free.
Evidence (decisive lines)
VERDICT.md (two-layer swallow proof + fix-coverage assessment, path:line); env.txt (hardware absence + md5 962543e6e0739668ac24e0b255a44b61 == audited tree); fix.diff (central hub fix, git apply --check clean, 76 lines)
PoC changes
No seed PoC. Trigger is the DF-4022 family VBIOS trigger; this pack contributes the central fix.diff instead.
Verified recommended fix
Honor radeon_dpm_init's return, gate on boot_ps, scrub dangling ps, and guard suspend/resume/fini-disable paths in radeon_pm.c (see fix.diff).
Verdict
Source-level certain, runtime hardware-gated (inconclusive per family convention). radeon_pm_init_dpm calls the asic init as a void statement -- 'radeon_dpm_init(rdev);' at radeon_pm.c:1448, macro radeon.h:2793 expands to rdev->asic->dpm.init((rdev)) returning int -- discarding the error upstream honors ('ret = radeon_dpm_init(rdev); if (ret) goto dpm_failed;'). Verified failure semantics: (a) parse errors return -EINVAL/-ENOMEM (swallowed here); (b) a VBIOS table with no BOOT-classified state returns 0 with boot_ps NULL because boot_ps is only assigned inside the per-state loops (rv770_dpm.c:2172, rv6xx_dpm.c:1811, trinity_dpm.c:1705, si_dpm.c:6727) and NO dispatch init validates it. With the error dropped, :1449 publishes current_ps=requested_ps=boot_ps (NULL/dangling), :1452-1453 run setup_asic+enable whose per-asic bodies deref boot_ps (rv770_dpm.c:1901, rv6xx_dpm.c:1577, btc/trinity/cypress twins = DF-4022/4118/4124/4143/3972 attach panics); the dangling ps survives into radeon_dpm_fini at unload (DF-4117/4125/4144 double frees); suspend/resume/fini-disable re-deref boot_ps (:1262/:1320/:1694) because pm_method stays PM_METHOD_DPM -- the second swallow layer being the asic *_init files calling radeon_pm_init(rdev); as a statement (si.c:6874, ni.c:2432, cik.c:8653, evergreen.c:5235, rv770.c:1962, r600.c:3321). Central fix (fix.diff, one file, covers all six dispatch files): honor the return; explicit 'if (!rdev->pm.dpm.boot_ps) goto dpm_init_failed' gate (closes case (b) which the return alone does not -- rv7xx_parse_power_table returns 0 with boot_ps unset, rv770_dpm.c:2271-2329); scrub ps/boot_ps/current_ps/num_ps on init failure so dpm_fini cannot double-free; gate suspend_dpm/resume_dpm/fini's radeon_dpm_disable on boot_ps. Assessment: applying it makes the six family enable-side NULL-boot_ps panics unreachable at boot AND at suspend/resume/unload-disable, and neutralizes the ps double-frees; residual per-asic ext-table double-frees still need the already-filed dispatch fixes. Guest: virtio VGA only, zero 0x1002 devices, hw.drm unknown (env.txt) -> not runtime-testable.
No comments yet.