# DF-1203 — Heap overflow via unbounded ucStateEntrySize (radeon ni_dpm.c)

## Claim
`ni_parse_power_table()` (`sys/dev/drm/radeon/ni_dpm.c:4030`) loops
`for (j = 0; j < ucStateEntrySize - 1; j++)` driven by a VBIOS-supplied
`u8 ucStateEntrySize` (0-255) and passes the loop index `j` straight into
`ni_parse_pplib_clock_info(... index=j)`, which indexes
`ps->performance_levels[index]` at `ni_dpm.c:3927`. That array has exactly
`NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE = 16` slots
(`nislands_smc.h:28`). The backing object is a heap `kzalloc(sizeof(struct
ni_ps))` (`ni_dpm.c:4020`). A VBIOS with `ucStateEntrySize >= 17` therefore
writes attacker-chosen clock/voltage fields past the end of the `struct
ni_ps` heap object.

## Verdict
**NOT TESTABLE on this audit guest** (real bug, traced line-by-line in
`sys/`, fix authored and compile-validated). See `VERDICT.md`.

## Why not reproduced here
- `radeon` is **not** in the default kernel config (`sys/config/X86_64_GENERIC`
  has no `device radeon`); it is `optional radeon drm` in `sys/conf/files`
  (a loadable KLD, present only in `LINT64`).
- The guest has no AMD radeon GPU (`pciconf` shows only the QEMU stdvga
  `0x11111234` at pci0:0:2:0), so `radeon.ko` cannot attach and
  `ni_dpm_init()` / `ni_parse_power_table()` never run.
- `kldload` of `radeon.ko` requires root (forbidden precondition) and still
  needs an AMD GPU to call the parser.

## Trigger (threat model)
A malicious or buggy GPU **VBIOS** AtomPowerTable with `pplib.ucStateEntrySize`
set >= 17. Parsed on any system that loads the radeon DRM module against an
AMD Northern Islands (Cayman/Barts/Turks/Antilles) GPU. Impact: kernel heap
overflow of `sizeof(struct rv7xx_pl)` per extra slot, content = GPU-supplied
clock/voltage/flags — corruption / DoS on default GENERIC; primitive-only on
unhardened builds.

## Reproduce (on appropriate hardware)
On a host with an AMD NI-class GPU + radeon DRM loaded, a crafted VBIOS image
flashed/supplied to the GPU triggers the overflow during DPM init. No
userspace syscall reaches this path; the input is the GPU firmware table, so
there is no runnable userspace PoC.

## Fix
`fix.diff` bounds the loop index to
`NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE`, matching the existing
post-check at `ni_dpm.c:2641`.
