# 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.
