# DF-4492 — VERDICT

**Finding**: rs780_parse_power_table() trusts unvalidated VBIOS pplib
offsets/indices/strides → wild OOB kernel reads past the kmalloc'd BIOS
image (kernleak class).

**Status: REPRODUCED at logic level (verbatim replica), 2 attempts.**
Kernel-level trigger hardware-gated: the QEMU audit guest has no
RS780/RS880-class ATI IGP, so the radeon driver never probes DPM there
(family precedent DF-4190/DF-4146/DF-4124 were verified the same way).

## How it was reproduced

`harness.c` carries the VERBATIM code of `rs780_parse_power_table()`
(rs780_dpm.c:789-844), `rs780_parse_pplib_non_clock_info()` (:717-745) and
`rs780_parse_pplib_clock_info()` (:747-787) with only MMIO stubbed; the
kernel heap is emulated by a canary allocator (64-byte 0xA5 redzones both
sides of every allocation). The VBIOS image is a crafted 16 KB buffer
using the packed ATOM structure definitions from pptable.h (offsets
faithful: ucNumStates@+5, entry sizes +6..+8, array offsets +9/+11/+13).

### mode 4492 (near OOB → poison ingestion, leak flavor)

Crafted table claims `usNonClockInfoArrayOffset = 10336` with
`ucNonClockStateIndex = 250`, `ucNonClockSize = 24` → the 24-byte
`_ATOM_PPLIB_NONCLOCK_INFO` load lands at bios+16336, **16 bytes past the
16 KB image**, inside the rear redzone. Observed (run.log):

```
*** CANARY: nonClockInfo[250] ... at 0x8004c4050 (len 24) is OUTSIDE every live allocation -> heap OOB access ***
*** CANARY: clockInfo[250] ... OUTSIDE every live allocation -> heap OOB access ***       (same landing via 250*20)
*** CANARY: state[7] of the ucStateEntrySize=254 walk ... OUTSIDE every live allocation *** (16318+7*254, ~1.7 KB past)
rs780_parse_power_table() = 0 num_ps=1
rps[0].class = 0xa5a5 (== canary poison: OOB READ CONFIRMED)
rps[0].caps  = 0xa5a5a5a5 (== canary poison)
rps[0].vclk  = 0xa5a5a5a5 (== canary poison)
```

In the kernel the same loads read whatever follows the `kzalloc`'d BIOS
image in the kernel heap; class/caps/vclk/sclk flow to
`rs780_dpm_print_power_state()` (:944-948, console/debugfs) — info leak
class; far variants fault (DoS). Note the poisoned `class = 0xa5a5` even
carries bit0 (ATOM_PPLIB_CLASSIFICATION_BOOT), showing the OOB data also
selects which state becomes `boot_ps`.

### mode 4492g (far OOB → deterministic fault)

BIOS image mapped as 16 KB writable + PROT_NONE guard page;
`ucClockStateIndices[0] = 66`, `ucClockInfoSize = 255` → load at
bios+17022 = 638 bytes past the image, inside the guard page. Observed:

```
clockInfo[66] (x255) lands at 0x8004c427e = 638 bytes PAST the image end (inside the guard page)
*** SIGNAL 11 at address 0x8004c4280 ***   exit=99
```

Maximum reach with u8 fields: 255×255 = 65025 bytes past the table on the
index walks, 254×255 ≈ 64.8 KB on the state walk, plus the u16 array
offsets — comfortably past a 64 KB BIOS allocation.

## Why this is real in the kernel

* `atom_parse_data_header()` (sys/dev/drm/radeon/atom.c:1366-1385) returns
  the raw u16 master-table offset with **no bounds validation** of
  `idx`/table extent vs `ctx->bios`.
* `radeon_read_bios()` (radeon_bios.c:78-110) allocates `rdev->bios` with
  exactly the ROM size; `atom_context->bios` points at it (atom.c:1283).
* The rs780 parse (pass-1 sibling audits confirmed the same shape in
  sumo/trinity/kv/si) reads via those raw pointers with no table-extent
  math anywhere.

## Fix validation

`fix.diff` requests the table size (`&data_size`) from
`atom_parse_data_header()` and bounds every walk by it via
`rs780_pplib_walk_ok()` (rejects with consistent cleanup). Applied to the
guest's pristine `/usr/src` copy:

* `radeon_fix_apply.log`: all 7 hunks applied (Plan A).
* `radeon_module_build.log`: `radeon.ko` links under **-Werror**, MAKE_RC=0.
* `harness_fixed` modes 4492/4492g: `DRM_ERROR: ... walks past the table`,
  exit=0, no canary violations.

Guest source reverted (md5 verified) and guest reset (`vm.sh reset
with-src`) after the run.

## Attempts

1. First run: crafted landings fell *inside* the 16 KB image (intra-image
   overread only) — re-crafted offsets/strides to land in the redzone /
   guard page.
2. Decisive run captured in run.log.

## 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.
