β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4162

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4162 Β· 6 files
FileTypeDescriptionSize
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:

  1. The hub β€” radeon_pm_init_dpm() calls radeon_dpm_init(rdev); as a void statement (radeon_pm.c:1448), discarding the int returned by rdev->asic->dpm.init (radeon.h:2793 #define radeon_dpm_init(rdev) rdev->asic->dpm.init((rdev))). Upstream's radeon_pm_init_dpm does ret = radeon_dpm_init(rdev); if (ret) goto dpm_failed; β€” the check was dropped in the DFly port.
  2. 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, …) calls radeon_pm_init(rdev); as a void statement too, so even a propagated error dies there and the driver comes up in PM_METHOD_DPM with 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 β€” walks ps[i]),
  • runs radeon_dpm_setup_asic(),
  • runs radeon_dpm_enable() β†’ the per-asic enables that deref boot_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) and radeon_pm_fini_dpm β†’ radeon_dpm_disable (:1694) re-deref the same NULL/dangling pointers at suspend/resume/unload, and *_dpm_fini double-frees the dangling ps.

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.

VERDICT.md
↓ download raw

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)

  1. radeon_pm_init_dpm() invokes the asic init as a statement: radeon_dpm_init(rdev); (radeon_pm.c:1448). The macro expands to rdev->asic->dpm.init((rdev)) (radeon.h:2793) β€” an int return β€” which is discarded. Upstream's radeon_pm_init_dpm contains ret = radeon_dpm_init(rdev); if (ret) goto dpm_failed; at the same point.
  2. Failure semantics of the dispatch inits: * hard failure (-EINVAL/-ENOMEM) from parse errors β€” swallowed at :1448; * silent success with boot_ps == NULL: boot_ps is only assigned when a state carries ATOM_PPLIB_CLASSIFICATION_BOOT inside 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_init validates it (grep: zero boot_ps == NULL checks in sys/dev/drm/radeon). rv7xx_parse_power_table returns 0 with boot_ps unset (rv770_dpm.c:2271-2329) β€” so honoring the return alone does NOT close this half; the hub-level boot_ps gate in fix.diff does.
  3. With the error dropped, :1449 publishes current_ps = requested_ps = boot_ps (NULL/dangling), :1450-1451 can walk ps[] via radeon_dpm_print_power_states, :1452 runs radeon_dpm_setup_asic, :1453 runs radeon_dpm_enable β†’ per-asic enables deref boot_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).
  4. Even when radeon_dpm_enable fails into dpm_failed, the dangling ps survives; radeon_pm_fini_dpm β†’ radeon_dpm_fini β†’ kfree(ps) again (= DF-4117/4125/4144 double frees), and radeon_pm_suspend_dpm (:1262)/ radeon_pm_resume_dpm (:1320)/fini's radeon_dpm_disable (:1694) re-deref boot_ps because pm_method stays PM_METHOD_DPM β€” the second swallow layer: every asic *_init calls 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, …).

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

fix.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
↓ fix.diffper-fix-DF-4162

Confirmed kernel references

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.