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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3610 Β· 13 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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":

  1. no state carries ATOM_PPLIB_CLASSIFICATION_BOOT (0x0008) in usClassification;
  2. state_array->ucNumEntries == 0 β€” kzalloc(sizeof(struct radeon_ps) * 0) returns ZERO_SIZE_PTR (non-NULL) at si_dpm.c:6840-6843, the loop is a no-op, num_ps = 0;
  3. every state's clock indexes are >= clock_info_array->ucNumEntries (si_dpm.c:6866 continue) β€” states parse but the classification loop for boot never sets boot_ps (shape 1 again) β€” note non_clock_info IS 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)

  1. 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).
  2. radeon.ko loaded and DPM enabled (default for SI when the PowerPlayInfo table parses).
  3. 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

fixed
baseline reproduced→ patch + rebuild →patched clean

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

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.