NULL-pointer dereference in si_dpm_enable when the VBIOS power table has no ATOM_PPLIB_CLASSIFICATION_BOOT state (boot_ps unvalidated) β radeon twin of amdgpu DF-3586
Summary
rdev->pm.dpm.boot_ps is set only when a VBIOS state carries the BOOT classification; a crafted VBIOS with no such state (or ucNumEntries==0, where kzalloc(0)'s ZERO_SIZE_PTR passes the !ps check) leaves boot_ps NULL while si_parse_power_table returns 0. radeon_pm.c ignores the dpm.init return and assigns current_ps=requested_ps=boot_ps, and si_dpm_enable dereferences it: first fault at si_populate_smc_initial_state via ni_get_ps(NULL)->ps_priv (offset 0x28) - kernel NULL-deref panic during DPM enable (boot-time DoS). VERIFIED: harness replicates the parse (3 states, none BOOT-classified), the ignored-init-return, and executes the exact dereference chain in a forked child - child dies SIGSEGV at address 0x28; fixed-path returns -EINVAL with no fault. Crafted GPU VBIOS (reflash, KVM/vfio passthrough, or QEMU-emulated radeon): unauthenticated-to-host kernel panic at graphics init.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3610 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | β | 5.2 KB | view raw | |
| build.sh | β | 66 B | view raw | |
| run.sh | β | 90 B | view raw | |
| build.log | β | 11 B | view raw | |
| run.log | β | 734 B | view raw | |
| fix.diff | β | 374 B | view raw | |
| fix_build.log | β | 1.6 KB | view raw | |
| fix_run.log | β | 140 B | view raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| README.md | β | 1.8 KB | β raw | |
| manifest.json | β | 975 B | view raw | |
| verdict.json | β | 3.4 KB | view raw | |
| env.txt | β | 1.8 KB | view raw |
DF-3610 β NULL-pointer dereference in si_dpm_enable when the VBIOS power table has no ATOM_PPLIB_CLASSIFICATION_BOOT state (boot_ps unvalidated)
Severity: Medium Confidence: certain Status: reproduced (harness) Impact: panic
What
rdev->pm.dpm.boot_ps is set ONLY in si_parse_pplib_non_clock_info() (si_dpm.c:6726-6727) when a VBIOS power state carries ATOM_PPLIB_CLASSIFICATION_BOOT. A crafted VBIOS with no such state (or ucNumEntries==0, which also yields a ZERO_SIZE_PTR dpm.ps that passes the !ps check at :6842) leaves boot_ps==NULL while si_parse_power_table returns 0. radeon_pm.c:1448 IGNORES the dpm.init return, :1449 assigns current_ps=requested_ps=boot_ps, and radeon_dpm_enable -> si_dpm_enable dereferences it: first fault at si_populate_smc_initial_state:4372 via ni_get_ps(NULL)->ps_priv (offset 0x28). Harness executes the exact chain in a forked child: SIGSEGV at 0x28 == kernel NULL-deref panic during DPM enable (boot-time DoS with malicious VBIOS). No live GPU trigger possible on the virtio-only audit guest; module radeon.ko present.
Reproduce
sh build.sh # cc -O2 -Wall -o harness harness.c sh run.sh # prints [A] buggy-path behaviour, [B] fixed-path behaviour
Expected decisive output (see run.log): the [A] section shows the fault or
corruption the kernel would take; the [B] section shows the behaviour with
fix.diff applied.
No AMD GPU is attached to the audit QEMU guest (virtio-only), so the radeon DPM path cannot be exercised live; the harness replicates the exact kernel expressions with crafted-VBIOS inputs (house methodology, cf. DF-1137..1140).
Fix
fix.diff β validated: git apply --check clean; make si_dpm.o in
sys/dev/drm/radeon with KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC
compiles -Werror clean with all five pass-2 fixes stacked; harness [B]
shows the primitive gone.
DF-3610 β Verdict
Verdict: REPRODUCED (source-level + harness) β NULL-deref panic, no escalation (no control over the dereferenced pointer)
radeon twin of amdgpu DF-3586.
Bug confirmation
rdev->pm.dpm.boot_ps is assigned in exactly one place for SI β
si_parse_pplib_non_clock_info():
si_dpm.c:6726 if (rps->class & ATOM_PPLIB_CLASSIFICATION_BOOT)
si_dpm.c:6727 rdev->pm.dpm.boot_ps = rps;
si_parse_power_table() (si_dpm.c:6808) never checks that a BOOT state was
found, and returns 0 as long as the array walk succeeds. Three crafted-VBIOS
shapes leave boot_ps == NULL while parsing "succeeds":
- no state carries
ATOM_PPLIB_CLASSIFICATION_BOOT(0x0008) inusClassification; state_array->ucNumEntries == 0βkzalloc(sizeof(struct radeon_ps) * 0)returnsZERO_SIZE_PTR(non-NULL) at si_dpm.c:6840-6843, the loop is a no-op,num_ps = 0;- every state's clock indexes are
>= clock_info_array->ucNumEntries(si_dpm.c:6866continue) β states parse but the classification loop for boot never sets boot_ps (shape 1 again) β notenon_clock_infoIS still parsed in that case, so only the BOOT classification matters.
The consumer chain has no guard anywhere:
radeon_pm.c:1448 radeon_dpm_init(rdev); // return IGNORED
radeon_pm.c:1449 current_ps = requested_ps = boot_ps; // NULL ok'd
radeon_pm.c:1453 ret = radeon_dpm_enable(rdev); // -> si_dpm_enable
si_dpm.c:6354 struct radeon_ps *boot_ps = rdev->pm.dpm.boot_ps;
si_dpm.c:6401 ret = si_init_smc_table(rdev);
si_dpm.c:4700 radeon_boot_state = rdev->pm.dpm.boot_ps;
si_dpm.c:4746 si_populate_smc_initial_state(rdev, radeon_boot_state, table);
si_dpm.c:4372 struct ni_ps *initial_state = ni_get_ps(NULL);
ni_dpm.c:733-735 return rps->ps_priv; // read at NULL+0x28
offsetof(struct radeon_ps, ps_priv) == 0x28 (radeon.h:1342-1356, LP64).
Later derefs of the same pointer: si_dpm.c:6417 (si_populate_mc_reg_table),
:6438/:6443 (si_populate_smc_tdp_limits[_2]), :6463 (ni_update_current_ps),
and si_dpm_disable:6508-6509.
Harness confirmation
run.log:
[A] ... child died from SIGSEGV (signal 11): NULL+0x28 read
>>> In kernel: page-fault at 0x28 -> panic during DPM enable <<<
[B] Fixed path ... rc=-EINVAL (DPM enable refused, no dereference)
The harness forks and executes the real dereference chain
(si_dpm_enable -> si_init_smc_table -> si_populate_smc_initial_state ->
ni_get_ps(NULL)->ps_priv) β the child takes SIGSEGV at address 0x28, exactly
the fault the kernel would take (unrecoverable page fault -> panic).
Trigger conditions (not met on this guest)
- AMD SI (Tahiti/Pitcairn/Verde/Oland/Hainan) GPU with radeon DPM β the audit QEMU guest is virtio-only (no 0x1002 PCI device; verified in env.txt).
radeon.koloaded and DPM enabled (default for SI when the PowerPlayInfo table parses).- Crafted VBIOS as described above (reflash / KVM passthrough / emulated).
Exploit chain
None β NULL is fully deterministic, no attacker-controlled offset. Pure boot-time DoS / code-execution-prevention (panic) with a malicious VBIOS.
Fix
fix.diff β refuse to enable DPM without a boot state:
if (boot_ps == NULL) {
DRM_ERROR("no boot power state in VBIOS power table\n");
return -EINVAL;
}
Validation: git apply --check OK; make si_dpm.o (KERNBUILDDIR=
/usr/obj/usr/src/sys/X86_64_GENERIC) compiles -Werror clean with all five
pass-2 fixes stacked; harness [B] shows enable refused with no fault.
fix_status: fixed (compile + harness-level); live HW boot test impossible on
this guest.
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
fixedgit apply --check OK (standalone and stacked with the other four si_dpm.c fixes); targeted module TU compile make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC si_dpm.o => rc=0, -Werror clean (87080 B). Harness fixed-path shows the primitive gone (fix.diff adds if (boot_ps == NULL) return -EINVAL; at si_dpm_enable top. git apply --check OK; stacked with 4 other fixes the module TU compiles -Werror clean (si_dpm.o, 87080 B); harness [B] shows enable refused instead of faulting.)
fix.diff adds `if (boot_ps == NULL) return -EINVAL;` at si_dpm_enable top. git apply --check OK; stacked with 4 other fixes the module TU compiles -Werror clean (si_dpm.o, 87080 B); harness [B] shows enable refused instead of faulting.
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS (no BOOT state) -> si_parse_power_table OK -> radeon_pm_init_dpm ignores rc -> si_dpm_enable -> si_init_smc_table -> si_populate_smc_initial_state -> ni_get_ps(NULL) -> read NULL+0x28 -> page fault -> panic
Evidence (decisive lines)
run.log [A]: 'child died from SIGSEGV (signal 11): NULL+0x28 read'; [B] fixed: 'rc=-EINVAL (DPM enable refused, no dereference)'; fix_build.log: si_dpm.o built rc=0 with -Werror
PoC changes
n/a (pass-2 finding authored with its harness; no seeded PoC to fix)
Verified recommended fix
see fix.diff
Verdict
radeon twin of amdgpu DF-3586. rdev->pm.dpm.boot_ps is set ONLY in si_parse_pplib_non_clock_info() (si_dpm.c:6726-6727) when a VBIOS power state carries ATOM_PPLIB_CLASSIFICATION_BOOT. A crafted VBIOS with no such state (or ucNumEntries==0, which also yields a ZERO_SIZE_PTR dpm.ps that passes the !ps check at :6842) leaves boot_ps==NULL while si_parse_power_table returns 0. radeon_pm.c:1448 IGNORES the dpm.init return, :1449 assigns current_ps=requested_ps=boot_ps, and radeon_dpm_enable -> si_dpm_enable dereferences it: first fault at si_populate_smc_initial_state:4372 via ni_get_ps(NULL)->ps_priv (offset 0x28). Harness executes the exact chain in a forked child: SIGSEGV at 0x28 == kernel NULL-deref panic during DPM enable (boot-time DoS with malicious VBIOS). No live GPU trigger possible on the virtio-only audit guest; module radeon.ko present.
No comments yet.