NULL boot_ps page-fault panic in trinity_dpm_enable (:1129) / trinity_dpm_disable (:1177) when the VBIOS power table has no BOOT-classified state or dpm.init failed β Trinity/Richland twin of DF-4022/DF-4118/DF-4124/DF-3972
Summary
boot_ps is assigned only under the BOOT classification and stays NULL otherwise (or after a failed trinity_dpm_init whose return radeon_pm.c:1448 discards - enable still runs). trinity_dpm_enable ends with trinity_update_current_ps(rdev, boot_ps) -> trinity_get_ps reads rps->ps_priv at NULL+offset -> kernel page fault at GPU probe/DPM-enable and every suspend. POWER_STATE_TYPE_INTERNAL_BOOT selection additionally returns NULL into pre_set_power_state's requested_ps deref. Crafted-VBIOS reboot-loop DoS on default DPM config.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4143 Β· 19 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.2 KB | β raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| verdict.json | β | 6.0 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| fix.diff | β | 879 B | view raw | |
| build.log | β | 8.6 KB | view raw | |
| run.log | β | 12.5 KB | view raw | |
| run.2.log | β | 12.5 KB | view raw | |
| env.txt | β | 1.0 KB | view raw | |
| rig/gen_rig.sh | β | 15.5 KB | view raw | |
| rig/check_verbatim.sh | β | 3.0 KB | view raw | |
| rig/Makefile | β | 690 B | β download | |
| rig/build.sh | β | 502 B | view raw | |
| rig/run.sh | β | 834 B | view raw | |
| rig/trinity_slice_verbatim.c | β | 54.8 KB | view raw | |
| rig/trinity_rig.c | β | 35.2 KB | view raw | |
| rig/rig_shims.h | β | 7.8 KB | view raw | |
| rig/radeon_structs_verbatim.h | β | 7.1 KB | view raw | |
| rig/atom_defs_verbatim.h | β | 20.9 KB | view raw |
DF-4143 β NULL boot_ps page-fault in trinity_dpm_enable (:1129) / trinity_dpm_disable (:1177) / trinity_dpm_pre_set_power_state (:1240) when the VBIOS power table has no BOOT-classified state or trinity_dpm_init failed and radeon_pm.c:1448 swallowed the error
- File:
sys/dev/drm/radeon/trinity_dpm.c(Trinity/Richland APU DPM) - Class: kernel NULL-pointer dereference β page fault β panic (CWE-476)
- Verified via the verbatim-code rig (
rig/, proven sibling evidence class DF-4165/DF-4247/DF-4388/DF-4396): the production parser, enable/disable/ pre_set paths and every layout-relevant struct were machine-extracted byte-identical fromsys/(gen_rig.sh), andcheck_verbatim.shre-proved block identity on the DragonFly guest against/usr/src/sysβ the tree its running kernel was built from (68 blocks identical; seebuild.log). - Hardware gating: the guest has only the Bochs SVGA (0x1234:1111), no AMD
ASIC (
env.txt), so the path never executes in this guest's kernel. In kernel context the demonstrated faults are page faults β panic at GPU attach / first suspend β the finding's threat model (crafted/corrupted VBIOS on a Trinity/Richland APU, or a root-written pp_table aftersysctl hw...style DPM reinit).
What the rig demonstrates (baseline build = production code)
| mode | result |
|---|---|
good |
well-formed image: init/enable/disable/pre_set/get_sclk/fini all clean (regression control) |
4143-enable |
table with no BOOT-classified state β boot_ps stays NULL β verbatim trinity_dpm_enable faults reading NULL->ps_priv at address 0x28 = 40 = offsetof(struct radeon_ps, ps_priv) (trinity_dpm.c:1129 β trinity_update_current_ps:1075 β trinity_get_ps:355) |
4143-presel |
requested_ps NULL (the value radeon_pm.c:1449 leaves after a NULL boot_ps) β verbatim trinity_dpm_pre_set_power_state faults at address 0x0 (*rdev->pm.dpm.requested_ps, :1240) |
4143-initfail |
parse aborts with -EINVAL (NULL power_state[1].clock_info, :1777-1778) β the rig simulates radeon_pm.c:1448 DISCARDING the init error and :1453 calling enable β fault at 0x28 again |
Fixed build (this finding's fix.diff + the sibling fixes, A/B on identical images)
All three modes return -EINVAL from the guards (no boot state; dpm init did
not succeed) β no fault, clean rejection; good still passes.
Build (on the DragonFly guest)
cd rig && sh ./build.sh /usr/src DF-4143
verifies all VERBATIM blocks against /usr/src/sys, verifies the
APPLY_DF4143_FIX-guarded lines equal this pack's fix.diff additions, then
compiles rig_base (production code) and rig_fixed.
Run
cd rig && sh ./run.sh
14 modes Γ both builds; exit codes 0 = clean, 88 = fault demonstrated, 89 = double free demonstrated.
Verdict
Reproduced (verbatim-rig): the exact production code, byte-identical to the
running kernel's source, NULL-dereferences boot_ps->ps_priv at offset 40 in
trinity_dpm_enable, boot_ps-derived NULL requested_ps in
trinity_dpm_pre_set_power_state, and after the swallowed init error β page
fault / panic in kernel context. fix.diff (NULL guards at all three
dispatch entries) eliminates every mode. See VERDICT.md, verdict.json.
DF-4143 β VERDICT
REPRODUCED via the verbatim-code rig on the DragonFly guest
(DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 ... x86_64),
with all 68 VERBATIM blocks of the rig proven byte-identical to
/usr/src/sys/dev/drm/radeon/{trinity_dpm.c,trinity_dpm.h,sumo_dpm.c,sumo_dpm.h,r600_dpm.c,atom.c,atom-bits.h,radeon.h,pptable.h,atombios.h,atom-types.h,trinityd.h}
β the tree the running kernel was built from (build.log: 68Γ "identical to
production"; guest and host tree md5s match).
Root cause (confirmed line-by-line)
rdev->pm.dpm.boot_psis assigned ONLY insidetrinity_parse_pplib_non_clock_infounderrps->class & ATOM_PPLIB_CLASSIFICATION_BOOT(trinity_dpm.c:1704-1706). A crafted VBIOS whose non-clock infos carry no BOOT classification parses successfully (init rc=0, num_ps=2, boot_ps=0x0β run.log mode4143-enable) leavingboot_ps == NULL.radeon_pm_init_dpmthen runsradeon_dpm_init(rdev);with the return value DISCARDED (radeon_pm.c:1448), setscurrent_ps = requested_ps = boot_ps(NULL, :1449) and callsradeon_dpm_enable(rdev)(:1453).- The verbatim
trinity_dpm_enablewalks its MMIO prep on the rig's fake register file and ends withtrinity_update_current_ps(rdev, rdev->pm.dpm.boot_ps)(trinity_dpm.c:1129) βtrinity_get_ps(rps)(:1075 β :355) readsrps->ps_privat NULL + 40.
Rig observations (baseline build, decisive lines)
4143-enable:RESULT: FAULT addr=0x28 ... NULL-pointer dereference at member offset 40β 40 == offsetof(struct radeon_ps, ps_priv), printed from the verbatim struct inlayoutmode (sizeof(struct radeon_ps)=48, ps_priv@40, last member). In kernel context: page fault β panic at GPU attach; same code path on every suspend (radeon_pm.c:1257-1260).4143-presel:RESULT: FAULT addr=0x0βtrinity_dpm_pre_set_power_statedereferencesrdev->pm.dpm.requested_ps(:1240), which radeon_pm.c:1449 left NULL. In kernel: panic on the first power-state transition.4143-initfail: parse aborts -EINVAL at :1777-1778 (the DF-4146 NULL-word abort); the rig prints the radeon_pm.c:1448-1453 swallow sequence and the verbatim enable faults at 0x28 β the exact swallowed-error panic chain.good: a well-formed crafted image drives init/enable/disable/pre_set/ get_sclk/fini to completion β the fault is specific to the missing BOOT state, not to the rig environment.
Fix validation (A/B, identical crafted images)
fix.diff adds NULL guards to trinity_dpm_enable, trinity_dpm_disable
and trinity_dpm_pre_set_power_state (the three in-file dispatch entries).
Fixed build: all three 4143 modes return -EINVAL with no fault
(RESULT: OK (fixed) ... rejected cleanly), good unchanged. The checker
additionally proves the APPLY_DF4143_FIX-guarded lines are exactly the
fix.diff additions (build.log). Kernel-rebuild validation was not
performed: the fix site executes in-kernel only on Trinity/Richland ASICs,
absent from this guest (env.txt); per the established verbatim-rig protocol
(sibling findings DF-4165/4247/4388/4396) the fixed slice was validated in
the same rig. fix.diff is authored against the pristine read-only sys/
tree and was never applied there (verified git apply --check clean).
Honest limitations
- No AMD GPU on the audit guest β no in-kernel execution; the panic itself is the kernel-context consequence of the demonstrated NULL dereference (identity of code proven against the running kernel's source tree).
- The long-term fix is honoring
trinity_dpm_init's return in radeon_pm.c:1448 (different file, out of scope for this finding's fix.diff); the in-file guards are the minimal safety net for the cited lines.
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
fixedA/B validated in the verbatim rig: identical crafted images, same slice, only the fix.diff lines added under -DAPPLY_DF4143_FIX. Baseline: NULL-deref faults at 0x28/0x0 in all three modes. Fixed: every mode returns -EINVAL cleanly (guards fire before any deref), good-table regression unchanged. Kernel-rebuild validation not performed (fix site executes only on AMD Trinity ASICs, absent from this guest); per the verbatim-rig protocol the fixed slice was validated in the same rig. fix.diff authored against the pristine read-only sys/ tree, never applied there.
run.log FIXED sections (RESULT: OK (fixed) ... rejected cleanly, rc=-22); build.log ('DF-4143 fix lines == fix.diff additions (8 lines)'); fix.diff
Confirmed kernel references
- sys/dev/drm/radeon/trinity_dpm.c:1129
- sys/dev/drm/radeon/trinity_dpm.c:1177
- sys/dev/drm/radeon/trinity_dpm.c:1240
- sys/dev/drm/radeon/trinity_dpm.c:1704
- sys/dev/drm/radeon/trinity_dpm.c:1075
- sys/dev/drm/radeon/trinity_dpm.c:355
- sys/dev/drm/radeon/radeon_pm.c:1448
- sys/dev/drm/radeon/radeon_pm.c:1453
- sys/dev/drm/radeon/radeon_pm.c:1705
Detail
Exploit chain
Crafted/corrupted VBIOS (flashed ROM, vfio-pci romfile=, malicious device) on a Trinity/Richland APU with no BOOT-classified pplib state (or any parse failure whose error radeon_pm.c:1448 discards) -> radeon_pm_init_dpm:1448 swallows trinity_dpm_init error -> :1449 current_ps=requested_ps=boot_ps=NULL -> :1453 radeon_dpm_enable -> trinity_dpm_enable:1129 trinity_update_current_ps(rdev, NULL) -> trinity_get_ps reads rps->ps_priv at NULL+40 -> kernel page fault -> panic at drm attach and at every suspend (radeon_pm.c:1257-1260 radeon_dpm_disable -> :1177 same deref). No unprivileged-userland trigger: the attacker data is the GPU ROM, not syscalls.
Evidence (decisive lines)
run.log (all 14 modes, baseline vs fixed; modes 4143-enable/4143-presel/4143-initfail: FAULT addr=0x28 / 0x0 / 0x28 with the NULL-member-offset notes), run.2.log (stability repeat), build.log (68x 'identical to production' against guest /usr/src/sys + 'DF-4143 fix lines == fix.diff additions (8 lines)' + zero-error build of both binaries), rig/ (sources: trinity_slice_verbatim.c carries the production functions between VERBATIM markers; trinity_rig.c drives the crafted images with guard/canary pages), VERDICT.md, env.txt (hardware gating)
PoC changes
Seed pack had no runnable PoC (status untested, hardware-gated). Built the verbatim rig from scratch: gen_rig.sh machine-extracts the production slice; check_verbatim.sh proves block identity (guest /usr/src/sys) and that the APPLY_DF4143_FIX lines are exactly fix.diff's additions; the harness crafts full AtomBIOS images (master data table indirection, PowerPlayInfo table, state/clock/non-clock arrays, IntegratedSystemInfo v1_7 with crev=7) so the verbatim atom_parse_data_header -> trinity_dpm_init -> enable/disable/pre_set chain executes; guarded page allocator + SIGSEGV classifier names NULL-deref/UAF/OOB classes with exact offsets.
Verified recommended fix
NULL-guard rdev->pm.dpm.boot_ps at the head of trinity_dpm_enable/trinity_dpm_disable and rdev->pm.dpm.requested_ps in trinity_dpm_pre_set_power_state (fix.diff, A/B validated); long-term: honor trinity_dpm_init's return in radeon_pm.c:1448.
Verdict
REPRODUCED via the verbatim-code rig: trinity_dpm_enable/trinity_dpm_disable/trinity_dpm_pre_set_power_state and every layout-relevant struct were machine-extracted byte-identical from sys/ (68 blocks re-verified identical ON THE GUEST against /usr/src/sys, the tree its running kernel was built from) and driven with crafted Trinity DPM VBIOS images. Baseline production code: a table with no BOOT-classified state parses successfully (init rc=0, num_ps=2) leaving boot_ps NULL; the verbatim trinity_dpm_enable then faults reading NULL->ps_priv at exactly address 0x28 (40 = offsetof(struct radeon_ps, ps_priv), printed from the verbatim struct) at trinity_dpm.c:1129 -> trinity_update_current_ps:1075 -> trinity_get_ps:355. requested_ps NULL (the radeon_pm.c:1449 value) faults pre_set_power_state at address 0x0 (:1240). A parse aborted by a NULL power_state[i].clock_info word (:1777-1778, the DF-4146 chain) with the error swallowed per radeon_pm.c:1448-1453 faults enable at 0x28 as well. A good-table regression passes end-to-end (init/enable/disable/pre_set/get_sclk/fini). In kernel context each fault is a page fault -> panic at GPU attach or first suspend (reboot-loop DoS on a crafted-VBIOS Trinity/Richland APU; root-writable pp_table after DPM reinit). Gating: no AMD GPU on the audit guest (Boches 0x1234:1111 only, env.txt), so the guest kernel never executes the path; reproduction is in the verbatim rig per the established evidence class for crafted-firmware parsers (DF-4165/DF-4247/DF-4388/DF-4396). fix.diff (NULL guards at all three dispatch entries) A/B-validated in the same rig on identical images: all three modes return -EINVAL cleanly, good table unchanged.
No comments yet.