# DF-1198 — radeon_atombios_parse_power_table_6 state_index/i mismatch (heap overflow)

## Verdict
**REPRODUCED (harness) — real bug confirmed by source trace + userspace replica.**
Impact class: kernel heap overflow of `power_state[].clock_info`. No
local-unprivileged trigger on the audit guest (no AMD GPU); trigger requires a
malicious VBIOS (malicious PCIe GPU / VFIO passthrough) at driver probe.
`uid=0` chain N/A — hardware/firmware-attacker class.

## Mechanism (confirmed `path:line`)
`radeon_atombios_parse_power_table_6()` (`sys/dev/drm/radeon/radeon_atombios.c`)
loops over the VBIOS power-state array. It **allocates** `clock_info` keyed on
the loop index `i`, but **writes** through a *second* index `state_index` that
only advances when a state yields ≥1 valid clock mode:

- `sys/dev/drm/radeon/radeon_atombios.c:2711-2714` —
  `rdev->pm.power_state[i].clock_info = kzalloc(sizeof(radeon_pm_clock_info) * (ucNumDPMLevels ? ucNumDPMLevels : 1), GFP_KERNEL);`
  (allocation sized for state **i**),
- `sys/dev/drm/radeon/radeon_atombios.c:2722-2724` —
  `radeon_atombios_parse_pplib_clock_info(rdev, state_index, mode_index, clock_info)`
  which writes `power_state[state_index].clock_info[mode_index]`
  (`radeon_atombios.c:2484` etc.),
- `sys/dev/drm/radeon/radeon_atombios.c:2729-2732` — the no-DPM branch also
  writes `power_state[state_index].clock_info[0]`,
- `state_index` increments **only** in the `if (mode_index)` branch
  (`radeon_atombios.c:2736-2739`).

When state[0] has DPM levels that all fail validation (`mode_index` stays 0 →
`state_index` does **not** advance), state[1]'s writes land in
`power_state[state_index==0].clock_info` — a buffer sized for state[0]'s single
DPM level. State[1] writing N valid modes overflows it by
`(N − state[0].alloc) * sizeof(struct radeon_pm_clock_info)` bytes into the
adjacent heap. The allocation (keyed on `i`) and the writes (keyed on
`state_index`) diverge precisely when a prior state stalls `state_index`.

## Harness proof (`run.log`)
`harness.c` replicates the loop with VBIOS state[0]={1 DPM level, invalid} and
state[1]={4 DPM levels, all valid}, with a canary slot after state[0]'s
1-element allocation:

```
  state i=1 writes via state_index=0 (alloc for state[0]=1 entries)
  [!] OOB WRITE clock_info[1] into buffer sized 1 (state_index=0)
  state i=1 writes via state_index=0 (alloc for state[0]=1 entries)
  [!] OOB WRITE clock_info[2] into buffer sized 1 (state_index=0)
  state i=1 writes via state_index=0 (alloc for state[0]=1 entries)
  [!] OOB WRITE clock_info[3] into buffer sized 1 (state_index=0)
[BUG REPRODUCED] 3 OOB clock_info writes, victim canary clobbered 48 bytes
```

## Why not a live-kernel trigger / no uid0 chain
`radeon` (`/boot/kernel/radeon.ko`) attaches only to AMD/ATI Radeon PCIe GPUs
(none on the guest → never loaded). The power table is parsed from the GPU's
VBIOS (`atom_parse_data_header`) at probe — not reachable from any unprivileged
syscall. A malicious VBIOS via VFIO passthrough or a malicious physical GPU is
the realistic threat. Hardware/firmware-attacker class; no local-privesc chain.

## Fix (`fix.diff`)
Make allocation and writes use the **same** index (`state_index`): allocate
`power_state[state_index].clock_info` (instead of `[i]`), and when a state
yields zero valid modes, free its allocation so the `state_index` slot is reused
by the next iteration (no leak, no divergence). This preserves the compaction
semantics while closing the overflow. Supersedes the finding's "use i instead of
state_index" proposal — that alternative would break the `state_index`-based
compaction the rest of the function relies on (post-loop iteration at
`radeon_atombios.c:2744` and the returned `state_index`).

## Fix validation
`radeon.ko` rebuilt from patched source (DF-1198/1199 applied) compiled with
`-Werror` and linked (`/usr/obj/usr/src/sys/dev/drm/radeon/radeon.ko`, 2030904 B;
`radeon_atombios.o` rebuilt, 48448 B). `fix_status: not_testable` (no AMD GPU for
a live trigger; validated applies + compiles + closes the path).
