# DF-1203 — VERDICT

**Finding:** Heap buffer overflow via unbounded `ucStateEntrySize` in radeon
power-table parsing (`sys/dev/drm/radeon/ni_dpm.c`).
**Status:** NOT TESTABLE on this audit guest. **Confidence (bug is real):** certain.
**Impact ceiling:** kernel heap overflow (corruption / DoS); device/firmware-controlled.
**Fix:** authored in `fix.diff`, applies clean, compile-validated against the
radeon KLD source (see `fix_build.log`).

## Mechanism (confirmed line-by-line in `sys/`)

1. `ni_dpm_init()` → `ni_parse_power_table()` (`ni_dpm.c:3993`) reads the
   AtomPowerTable from the GPU VBIOS.
2. `ni_dpm.c:4020` — `ps = kzalloc(sizeof(struct ni_ps), GFP_KERNEL)` allocates
   one power-state object. `struct ni_ps` (`ni_dpm.h:172`) holds
   `performance_levels[NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE]`,
   and that constant is **16** (`nislands_smc.h:28`).
3. `ni_dpm.c:4030` — `for (j = 0; j < (power_info->pplib.ucStateEntrySize - 1); j++)`.
   `ucStateEntrySize` is a `u8` taken verbatim from the VBIOS (0-255). `j` is
   `int` (`ni_dpm.c:3990`), so no underflow when `ucStateEntrySize == 0`.
4. `ni_dpm.c:4035-4037` — calls `ni_parse_pplib_clock_info(rdev, &ps[i], j, clock_info)`.
5. `ni_dpm.c:3927` — `struct rv7xx_pl *pl = &ps->performance_levels[index];`
   with `index == j`. **No bounds check.**
6. For `ucStateEntrySize >= 17`, `j` reaches 16 and `performance_levels[16]`
   overflows the heap object. Each extra slot writes `sizeof(struct rv7xx_pl)`
   of VBIOS-controlled `sclk/mclk/vddc/vddci/flags` (`ni_dpm.c:3931-3938`).

The same code already defends against over-count later at `ni_dpm.c:2641`
(`if (state->performance_level_count > NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE)`),
confirming 16 is the intended bound — it is simply never enforced at the
parsing loop.

## Why it is NOT TESTABLE on this guest

- `sys/config/X86_64_GENERIC` contains **no** `device radeon`; `radeon` is
  `optional radeon drm` in `sys/conf/files` (loadable module, in `LINT64` only).
- `pciconf -lv` on the guest shows only `vgapci0@pci0:0:2:0` chip `0x11111234`
  (QEMU Standard VGA) — **no AMD GPU**, so `radeon.ko` cannot attach and the
  DPM/power-table parser is dead code at runtime here.
- The input that drives the bug (the AtomPowerTable) lives in the GPU VBIOS,
  not in any userspace syscall argument; there is no unprivileged path to
  supply it, and `kldload radeon` is a root-only action that still requires
  the matching hardware.

This is the valid "dead/unreachable at runtime on this guest" case from the
procedure. The bug is genuine (path:line confirmed above); it is simply latent
on a guest lacking the device. Per the realism test, the trigger precondition
("a radeon GPU with a malicious/crafted VBIOS") is a plausible real-world
threat (malicious peripheral / tampered firmware), not an unprivileged-user
escalation.

## Exploit chain
None developed: the primitive is reachable only via GPU firmware on a system
with the specific hardware and the radeon module loaded. There is no
unprivileged local path to it on this guest, so there is no unpriv→root chain
to build here. Documented impact ceiling: heap corruption / DoS from a
malicious VBIOS.

## Fix
`fix.diff` caps the loop index at the array bound:
```c
for (j = 0;
     j < (power_info->pplib.ucStateEntrySize - 1) &&
     j < NISLANDS_MAX_SMC_PERFORMANCE_LEVELS_PER_SWSTATE;
     j++) {
```
Minimal, targeted at the root cause, and consistent with the existing 16-slot
invariant. Supersedes the finding proposal's intent with an exact, line-accurate
diff.

## Build / run on this guest
`./build.sh && ./run.sh` runs a reachability probe (checks for an AMD radeon
GPU + the radeon module). On this guest it reports "no AMD GPU / radeon not
loaded → parser unreachable"; the bug itself is confirmed by the source trace
above and the compile-validated `fix.diff`.
