# DF-4190 — VERDICT

**Status: REPRODUCED (logic-level harness; kernel-level repro hardware-gated)**
**Impact: kernel heap OOB read (crash when far out; silent kernel-heap disclosure into power-state fields when the neighbouring pages are mapped)**
**Confidence: certain**

## Root cause

`sumo_parse_power_table()` (`sys/dev/drm/radeon/sumo_dpm.c:1455-1528`) trusts
three u8 indices from the VBIOS PowerPlayInfo image without any bounds check:

```c
non_clock_array_index = power_state->v2.nonClockInfoIndex;          /* :1495 u8 0..255 */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
        &non_clock_info_array->nonClockInfo[non_clock_array_index]; /* :1496-1497 OOB */
...
        for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) {
                clock_array_index = idx[j];                          /* :1509 u8 */
                ...
                clock_info = (union pplib_clock_info *)
                        ((u8 *)&clock_info_array->clockInfo[0] +
                         (clock_array_index * clock_info_array->ucEntrySize)); /* :1513-1515 OOB */
```

- `nonClockInfo[idx]` strides 24 bytes (`sizeof(ATOM_PPLIB_NONCLOCK_INFO)`,
  pptable.h:298-309): idx 255 reads up to ~6.1 KB past the array.
- `clockInfo[idx]` strides `ucEntrySize` — **itself a u8 from the image** (up
  to 255), so a single index reaches up to ~65 KB past the clock array.
- the state walk itself (`power_state_offset += 2 + ucNumDPMLevels`, `:1524`)
  advances by up to 257 bytes per state with no end-of-image bound, and
  `idx[j]` (`:1509`) reads past the state entry whenever `ucNumDPMLevels`
  exceeds the real array.

The arrays live inside `atom_context->bios`, a kmalloc'd copy of the GPU ROM
image; the reads run past the end of that heap allocation. Values fetched OOB
flow into `rps->class`/`class2`/`caps`/`vclk`/`dclk` (`:1412-1418`) — which
drive control flow (BOOT/UVD/THERMAL classification → which ps becomes
`boot_ps`, thermal-state patching) and hardware programming
(`radeon_set_uvd_clocks`). With a far index the read hits an unmapped page and
panics; with a near index it silently copies adjacent kernel-heap bytes into
state fields (kernleak class, same scoping as the DF-1401/1306/1140 twins).

## Trigger / threat model

Crafted VBIOS ROM on a Sumo/Ontario/Palm APU (flashed card, replaced OPROM,
VM GPU ROM passthrough). Parse happens at driver load; no privilege needed
beyond the attacker controlling the ROM.

## Harness proof (mode 4190)

16 KB crafted BIOS image, NonClockInfoArray placed near its end, state with
`nonClockInfoIndex = 250`:

```
bios image: 16384 bytes; nonClockInfo[250] lands at bios+21874 (5490 bytes PAST the end)
*** SIGNAL 11 at address 0x8004c55b2 ***   == the predicted OOB address
```

The fault address equals the computed `&nonClockInfo[250]` exactly — the
verbatim kernel index math dereferenced past the image buffer. (In the kernel
the same read at the same relative offset faults identically or, when the
adjacent heap is mapped, silently ingests neighbour bytes.)

## Fix validation

1. `harness_fixed 4190` (`-DFIX_DF4190`: reject
   `nonClockInfoIndex >= ucNumEntries`): "invalid nonClockInfoIndex 250",
   parse returns -22, exit 0, no OOB.
2. `fix.diff` (both nonClockInfoIndex and clockInfoIndex bounds checks, with
   clean teardown) applied with the other three sumo fixes on guest `/usr/src`
   (`radeon_fix_apply.log` hunks 4 & 5): `radeon.ko` builds under `-Werror`,
   `MAKE_RC=0`.

## Kernel references

- sys/dev/drm/radeon/sumo_dpm.c:1495-1497 (nonClockInfoIndex, unchecked)
- sys/dev/drm/radeon/sumo_dpm.c:1509, :1513-1515 (clockInfoIndex × ucEntrySize, unchecked)
- sys/dev/drm/radeon/sumo_dpm.c:1524 (unbounded state-array walk)
- sys/dev/drm/radeon/sumo_dpm.c:1412-1418 (OOB bytes ingested into rps fields)
- sys/dev/drm/radeon/pptable.h:298-309, :424-464 (u8 indices / entry sizes)

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
