# DF-4163 — `radeon_pm_fini_dpm` never cancels `pm.dpm.thermal.work` nor clears `dpm_enabled`: post-fini UAF in the thermal work handler

File: `sys/dev/drm/radeon/radeon_pm.c` (pass 2)

## What
`radeon_pm_fini_dpm()` (radeon_pm.c:1690-1719) tears the DPM state down
(`radeon_dpm_fini()` at :1705 frees `rdev->pm.dpm.ps`, `ps_priv[]`,
`rdev->pm.dpm.priv` — e.g. si_dpm_fini, si_dpm.c:7078-7094, **kfree without
NULL and `num_ps` left > 0**) but:

* never calls `cancel_work_sync(&rdev->pm.dpm.thermal.work)`,
* never sets `rdev->pm.dpm_enabled = false`.

Meanwhile the thermal IRQ is **still installed**: the asic fini order runs
`radeon_pm_fini` before the IRQ teardown (si.c si_fini: `radeon_pm_fini` at
:6945, `si_irq_fini` :6950, `radeon_irq_kms_fini` after; cik.c: :8731 vs
:8736/:8742; same in r600.c/evergreen.c). The IRQ handler schedules the work
whenever `dpm_enabled` is set (`if (queue_thermal && rdev->pm.dpm_enabled)
schedule_work(...)` — si.c:6429-6430, r600.c:4343, evergreen.c:4902,
cik.c:8125).

## Race
thermal event at driver unload (kldunload / device detach):

```
IRQ thread                            unload thread
-----------                           --------------
IH src 230/231: queue_thermal = true
  dpm_enabled == true (stale)         radeon_pm_fini_dpm()
  schedule_work(thermal.work)           radeon_dpm_disable()
                                         radeon_dpm_fini() -> kfree(ps),
                                           kfree(ps_priv[i]), kfree(priv)
handler runs:
  :881 dpm_enabled? -> still true       (fini never cleared it)
  :895 mutex_lock(pm.mutex)
  compute_clocks -> change_power_state_locked
    :1062 dpm_enabled? -> still true
    :945-947 ps = &rdev->pm.dpm.ps[i]  -> FREED memory (ps dangling,
                                           num_ps > 0)
    -> requested_ps = dangling ps ->
       radeon_dpm_set_power_state -> si_set_power_state derefs
          ps_priv (freed) and rdev->pm.dpm.priv (freed) for SMC
          programming                    <-- UAF read/consume
```

The mutex does not save the handler: `radeon_dpm_fini` is called *outside*
`pm.mutex` (only the preceding `radeon_dpm_disable` is under it), so nothing
orders the handler against the frees.

## Preconditions
radeon DPM machine (thermal IRQ armed, i.e. `irq.dpm_thermal`) whose driver is
unloaded/detached while a thermal threshold event fires — root-triggered
unload racing a hardware/thermal event. Result: UAF read of freed slab memory
feeding GPU SMC programming (garbage clocks/voltages) or, with
slab-reuse/poisoning, worse.

## Fix
`cancel_work_sync()` + `dpm_enabled = false` at the top of
`radeon_pm_fini_dpm`, before any state is freed (fix.diff). Closure argument:
after the flag is cleared, a late-scheduled straggler handler returns at its
entry check (:881) or at the under-mutex recheck in
`radeon_dpm_change_power_state_locked` (:1062) — and a handler that already
passed the under-mutex check must hold `pm.mutex`, which forces it to complete
before fini's own mutex section, i.e. before anything is freed.

Apply order: DF-4162/fix.diff first, then this diff (generated against the
DF-4162-patched tree; both verified to compose).

## Status on this audit guest
Hardware-gated (no radeon GPU — env.txt); source-certain structure,
timing-dependent trigger → per family convention: inconclusive.
