# DF-3888 — `num_levels == 0` → `levels[num_levels - 1]` wild read (+~48 GB) in `kv_dpm_get_sclk` / `kv_set_valid_clock_range`

**File:** `sys/dev/drm/amd/amdgpu/kv_dpm.c:2938` (get_sclk), `:1791`/`:1817` (set_valid_clock_range)
**Class:** CWE-191 unsigned count-1 underflow → wild OOB read (count-1 family: DF-3686/DF-3858 twins)
**Verdict:** `inconclusive` at runtime on this guest (no AMD GPU); **source-certain**; harness proves the index/offset and SIGSEGV on the verbatim access.

## Build (on the DragonFly guest, as root)

    mkdir -p /tmp/DF-3888 && cp num_levels_underflow.c /tmp/DF-3888/h.c
    cd /tmp/DF-3888 && cc -Wall -O0 -o t h.c

## Run

    ./t          # accounting — index and byte offset
    ./t crash    # verbatim read -> SIGSEGV (exit 139)

## Expected

- accounting: `num_levels = 0`; `levels[num_levels - 1] = levels[4294967295]`;
  byte offset `4294967295 * 12 = 51539607540` (~51.5 GB) past `levels` — far
  outside the 5-entry array (60 bytes) inside `kv_power_info`.
- crash: `Segmentation fault`, exit=139 (kernel equivalent: page fault in kernel
  mode → panic).

## Kernel trigger

A parsed power state whose `ucNumDPMLevels == 0` (or all `clockInfoIndex` entries
`>= ucNumEntries`, `continue` at kv_dpm.c:2758-2759) keeps `ps->num_levels == 0`
from `kzalloc`. Sinks, once such a state becomes the requested state:

- `kv_dpm_get_sclk(handle, false)` :2938 — **unprivileged** via the
  `AMDGPU_INFO` ioctl (`AMDGPU_INFO_DEV_INFO`, amdgpu_kms.c:700,
  `DRM_AUTH|DRM_RENDER_ALLOW`) and display modeset (dce_v8_0.c:971);
- `kv_set_valid_clock_range` :1791 / :1817 (else-branch), on the first power
  state transition to it (needs `check_state_equal` to differ, kv_dpm.c:3261).
