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

NULL-pointer dereference of rdev->pm.dpm.boot_ps in btc_dpm_enable/btc_dpm_disable when the VBIOS power table has no BOOT-classified state (or ucStateEntrySize<=1) β€” btc twin of DF-3610/3646/3754/3953

Summary

boot_ps is only set when ucStateEntrySize>1 AND a state carries the BOOT classification; radeon_pm_init_dpm ignores btc_dpm_init's return and btc_dpm_enable unconditionally dereferences the NULL boot_ps (NULL-page read at btc_init_smc_table -> populate_smc_initial_state). Physical access / malicious reflashed Barts/Turks/Caicos card: kernel panic at drm attach with default-enabled DPM. Same enabling sequence as the reproduced ni twin DF-3754.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3972 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.8 KB ↓ raw
VERDICT.md β€” 3.2 KB ↓ raw
verdict.json β€” 2.2 KB view raw
manifest.json β€” 872 B view raw
fix.diff β€” 825 B view raw
env.txt β€” 1.1 KB view raw

DF-3972 β€” NULL-pointer dereference of rdev->pm.dpm.boot_ps in btc_dpm_enable/btc_dpm_disable (crafted-VBIOS Barts/Turks/Caicos)

  • File: sys/dev/drm/radeon/btc_dpm.c (pass 2)
  • Class: CWE-476 NULL deref β†’ kernel panic (Medium)
  • Family: radeon DPM boot_ps twins (DF-3610 si / DF-3646 ci / DF-3754 ni / DF-3953 kv) β€” this is the btc (Barts/Turks/Caicos) sibling; distinct enable path, distinct file. Known DF-1314 (mc_reg_table off-by-one) is unrelated.

Build / Run / Expected

Hardware-gated: requires an AMD Barts/Turks/Caicos (NI-generation discrete GPU) whose VBIOS PowerPlayInfo table either (a) contains no state with ATOM_PPLIB_CLASSIFICATION_BOOT set, or (b) declares ucStateEntrySize <= 1 so the non-clock-info (classification) parse is skipped entirely.

No buildable PoC exists without the card: the trigger data lives in the GPU ROM image, parsed by rv7xx_parse_power_table() at driver attach. See VERDICT.md for the line-level trace.

Root cause (source-level, certain)

  1. rv7xx_parse_power_table() β€” sys/dev/drm/radeon/rv770_dpm.c:2271-2329 β€” is the only code that sets rdev->pm.dpm.boot_ps for BTC asics. It does so inside rv7xx_parse_pplib_non_clock_info() (rv770_dpm.c:2172-2173), which is only reached when power_info->pplib.ucStateEntrySize - 1 != 0 (rv770_dpm.c:2304) and a parsed state carries the BOOT classification bit. A VBIOS with no BOOT-classified state (or ucStateEntrySize<=1) leaves boot_ps == NULL while btc_dpm_init() still returns 0 (parse "succeeded").
  2. radeon_pm_init_dpm() β€” sys/dev/drm/radeon/radeon_pm.c:1448 β€” ignores the return of radeon_dpm_init(rdev) (= btc_dpm_init) and unconditionally calls radeon_dpm_enable(rdev) (line 1453). There is no boot_ps validation anywhere on the path.
  3. btc_dpm_enable() β€” sys/dev/drm/radeon/btc_dpm.c:2373-2488 β€” takes struct radeon_ps *boot_ps = rdev->pm.dpm.boot_ps; (2377) and dereferences it at: - 2446 btc_init_smc_table(rdev, boot_ps) β†’ cypress_populate_smc_initial_state(rdev, NULL, table) β†’ rv770_get_ps(NULL) then initial_state->low.mclk (cypress_dpm.c:1242,1266) β†’ NULL-page read. - 2451 cypress_populate_mc_reg_table(rdev, boot_ps) - 2485 btc_update_current_ps(rdev, rdev->pm.dpm.boot_ps)
  4. btc_dpm_disable() β€” btc_dpm.c:2490-2527 β€” same NULL at 2526 (btc_update_current_ps).
  5. Also note radeon_pm.c:1449 assigns current_ps = requested_ps = boot_ps (NULL is tolerated there until later consumers run).

DPM is enabled by default (radeon.dpm=1 default on this family), so a crafted/flashed VBIOS panics the kernel at drm attach β€” local DoS with physical access (or a supply-chain-flashed card).

Fix

Validate boot_ps after parse (upstream-style guard) β€” see fix.diff.

VERDICT.md
↓ download raw

VERDICT β€” DF-3972

status: inconclusive (hardware-gated; source-level certainty: certain) impact: none demonstrated on available hardware; real-world impact = local kernel panic (DoS) at drm attach on Barts/Turks/Caicos with crafted VBIOS

Why inconclusive, not false-positive

The QEMU guest (env.txt) has no AMD GPU: only Intel/Red Hat QEMU PCI devices, no /dev/dri, no drm modules. The bug's trigger bytes live in the GPU's ATOM BIOS PowerPlayInfo table; there is no way to feed a radeon VBIOS to the guest's non-existent radeon device, and radeon.ko is not even loadable there. Per audit convention for the DF-3610/3646/3754/3953 family this is recorded as inconclusive with confidence: certain at source level.

Reproduction reasoning (line-level, no hardware needed)

Attacker model: physical access (or malicious reseller/reflash) supplying a GPU ROM whose _ATOM_PPLIB_POWERPLAYTABLE:

  • ucNumStates >= 1, and either
  • no state's _ATOM_PPLIB_NONCLOCK_INFO.usClassification has ATOM_PPLIB_CLASSIFICATION_BOOT, or
  • ucStateEntrySize == 1 (skips the whole per-state parse block, rv770_dpm.c:2304-2325).

Trace:

radeon_pm_init (radeon_pm.c:1505)
 └─ radeon_pm_init_dpm (radeon_pm.c:1422)
     β”œβ”€ radeon_dpm_init(rdev)            # 1448 β€” btc_dpm_init, RETURN IGNORED
     β”‚   └─ btc_dpm_init (btc_dpm.c:2549)
     β”‚       └─ rv7xx_parse_power_table (rv770_dpm.c:2271)
     β”‚           └─ boot_ps NEVER set  (2172 only under classification parse)
     β”œβ”€ current_ps = requested_ps = boot_ps   # 1449, NULL ok
     β”œβ”€ radeon_dpm_setup_asic β†’ btc_dpm_setup_asic (btc_dpm.c:2529)  # no boot_ps use
     └─ radeon_dpm_enable β†’ btc_dpm_enable (btc_dpm.c:2373)
         └─ 2377 boot_ps = rdev->pm.dpm.boot_ps == NULL
         └─ 2446 btc_init_smc_table(rdev, NULL) (btc_dpm.c:1624)
             └─ 1661 cypress_populate_smc_initial_state(rdev, NULL, table)
                 └─ cypress_dpm.c:1242 rv770_get_ps(NULL) == NULL->ps_priv β†’ #PF

rv770_get_ps() is a plain container_of-style cast (rps->ps_priv), so the first dereference reads address ~0x0 + offsetof(ps_priv) on the NULL page β†’ kernel page fault β†’ panic. This is the exact enabling sequence already proven for the ni twin (DF-3754, reproduced/panic on that batch's harness).

Not a duplicate

DF-3754 (ni_dpm.c) covers Cayman's ni_dpm_enable; DF-3610/3646/3953 cover si/ci/kv. Barts/Turks/ Caicos use btc_dpm_enable via the btcd.h asic table β€” a different function and file, and the orchestrator's pass-2 checklist explicitly requested it. DF-1314 (this file's known finding) is the mc_reg_table off-by-one β€” unrelated.

Fix validation

fix_status: not_testable β€” no Barts-class GPU on the guest; a kernel rebuild cannot exercise the path. fix.diff authored against the read-only sys/ tree (guard + error propagation), mirroring the upstream si/ci/kv hardening pattern.

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

fix.diff authored (boot_ps guard + error propagation) but not build-testable: guest lacks any radeon GPU.

findings/poc/DF-3972/fix.diff
↓ fix.diffper-fix-DF-3972

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md full trace; env.txt (guest PCI inventory: no 0x1002 device, no /dev/dri); kernel_refs below

PoC changes

No PoC built: hardware-gated (Barts/Turks/Caicos VBIOS required). Evidence pack is a line-level source trace instead.

Verified recommended fix

Validate rdev->pm.dpm.boot_ps != NULL after rv7xx_parse_power_table in btc_dpm_init and propagate btc_dpm_init failure in radeon_pm_init_dpm (radeon_pm.c:1448).

Verdict

Source-certain NULL-deref: rdev->pm.dpm.boot_ps is only set under a BOOT-classified, fully-parsed VBIOS state (rv770_dpm.c:2172 within the ucStateEntrySize>1 block at 2304); radeon_pm_init_dpm ignores btc_dpm_init's return (radeon_pm.c:1448) and btc_dpm_enable dereferences the NULL boot_ps at btc_dpm.c:2446/2451/2485 via cypress_populate_smc_initial_state (cypress_dpm.c:1242,1266). Cannot reproduce: the QEMU guest has no AMD GPU (env.txt), and the trigger bytes live in the card's VBIOS PowerPlayInfo table. Same enabling sequence as the reproduced ni twin DF-3754.