# DF-3858 — `vega20_get_sclk_od`/`get_mclk_od`: `count-1` underflow wild read + divide-by-zero on zero golden clock (DF-3686/DF-3707 twins)

## What

`sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1318-1381`:

```c
int value        = sclk_table->dpm_levels[sclk_table->count - 1].value;        /* :1326 */
int golden_value = golden_sclk_table->dpm_levels[golden_sclk_table->count - 1].value; /* :1327-28 */
value -= golden_value;
value = DIV_ROUND_UP(value * 100, golden_value);                               /* :1332  <-- #DE */
```

(twin at `:1372-1378` for memclk.)

**Case A — divide-by-zero (DF-3707 twin).** When the DPM_GFXCLK (resp.
DPM_UCLK) SMU feature is not enabled, `vega20_setup_gfxclk_dpm_table()` takes
the boot-clock branch (`:544-547`): `count = 1; dpm_levels[0].value =
data->vbios_boot_state.gfx_clock / 100;` — the raw VBIOS
`bootup_sclk_in10khz` copied with no zero check
(`ppatomfwctrl.c:523`/`:556` → `vega20_hwmgr.c:745`). A crafted/blank VBIOS
value 0 ⇒ `golden_value == 0` ⇒ `DIV_ROUND_UP(x, 0)` ⇒ #DE panic.

**Case B — unsigned `count-1` underflow (DF-3686 twin).** If DPM (re-)enable
aborts inside `vega20_setup_default_dpm_tables()` (any SMU message failure,
`PP_ASSERT` at `:594/:613/...` returns early *after* the `memset` of the
whole `dpm_table` at `:588`), `dpm_table`/`golden_dpm_table` stay all-zero
(`count == 0`). Boot-time failure tears the device down
(`hwmgr_hw_init` → `err2` frees the backend, probe fails) — but the
**resume path has no cleanup** (`hwmgr_resume`, hwmgr.c:308-330: returns the
error with the backend intact and `pm_en` untouched), and `pm_en` was set at
powerplay-create time (`amd_powerplay.c:51`), so after a failed SMU re-enable
on resume the world-readable sysfs node still dispatches into
`get_sclk_od` ⇒ `dpm_levels[(uint32_t)0 - 1]` = `dpm_levels[0xFFFFFFFF]`
⇒ read at **+34,359,738,360 bytes** (~32 GiB) past `dpm_levels[0]` inside
the kzalloc'd `struct vega20_hwmgr` backend — a wild kernel OOB read; if it
lands on a zero, case A's divide then panics.

## Reach (world-readable)

`/sys/class/drm/card0/device/pp_sclk_od` and `pp_mclk_od` are `S_IRUGO`
(`amdgpu_pm.c:1019-1022`) → `amdgpu_get_pp_sclk_od` (`:794`, no other check)
→ `pp_dpm_get_sclk_od` (`amd_powerplay.c:757`, only `!hwmgr || !pm_en`
guard) → `vega20_get_sclk_od`. Any local user can `cat` it.

## Reproduce (arithmetic-level; kernel reach hardware-gated)

```
build.sh    # cc -O2 -Wall -o /tmp/od_div0 /tmp/od_div0.c
run.sh      # /tmp/od_div0 0   (case A)   /tmp/od_div0 1   (case B)
```

Expected (observed on the audit guest, `run.log`):

```
case A: ... DIV_ROUND_UP(x, 0)
Floating point exception (core dumped)        # exit=136 (SIGFPE = #DE), 3/3 runs
case B: golden gfx_table.count = 0 -> index (uint32_t)0-1 = 4294967295
        effective read offset = 34359738360 bytes past dpm_levels[0] (elem size 8)
```

Disassembly on the guest confirmed the case-B load
`mov 0x401ef8(,%rax,8),%ecx` with `rax = 0xFFFFFFFF` really executes
(+34 GB address); in this userland process it happened to land on an
unrelated mapped page (returned garbage 0) — in the kernel the backend is a
kmalloc object and a +34 GB landing is near-certainly unmapped (page-fault
panic) or feeds garbage into the case-A divide.

## Impact

Local DoS (panic) and/or wild OOB read via an **unprivileged** sysfs read, on
a Vega20 (Radeon VII / MI50 / MI60) in the affected states (crafted VBIOS
zero boot clock; or DPM bring-up failure). Hardware/VBIOS-gated.
