# DF-4146 — `trinity_parse_power_table` trusts unvalidated VBIOS u16 array offsets, an unbounded per-state stride walk, and `state_array->ucNumEntries` against a `power_state[]` allocation sized by a *different* VBIOS count — heap OOB reads of the BIOS image and the legacy power_state array

- File: `sys/dev/drm/radeon/trinity_dpm.c`
- Class: kernel out-of-bounds reads (CWE-125) driven by crafted VBIOS
- **Verified via the verbatim-code rig** (`rig/`, 68 blocks byte-identical to
  the guest's `/usr/src/sys`), including the verbatim
  `atom_parse_data_header` (atom.c:1366-1385) — which demonstrably performs
  no extent validation.

## What the rig demonstrates (baseline build = production code)

| mode | claim | result |
|---|---|---|
| `4146-offset` | (1) unvalidated u16 array offsets | `usStateArrayOffset=0x1000` with a 0x1000-byte image → `state_array = bios+0x1500` → the `:1767` read of `ucNumEntries` faults **past the BIOS image guard page** (`FAULT addr=bios+0x1500`) |
| `4146-stride` | (2) unbounded stride walk | a state with `ucNumDPMLevels=255` placed at the image end → the `:1788` `idx[j]` reads walk past the image and fault at **exactly `bios+0x2000`** (the first byte past the image) |
| `4146-canary` | (2) data flow | the same 255-byte walk with a readable tail: levels are parsed from bytes **beyond the state entry** — `idx[1..3]` (image bytes past the entry) select clock entries 5/6/7 (sclk 5777/6777/7777), and `idx[253]` reads the **non-clock array's own header byte** (`ucNumEntries=3` at state+254) as a clock index (sclk 3777) — the walk crosses into the adjacent table |
| `4146-powerguard` | (3) `ucNumEntries` vs `ucNumStates` sizing | frev-4/5-shaped image: legacy pre-parse (radeon_atombios.c:2592) sized `power_state[]` for `ucNumStates=1` (48-byte entries); the trinity loop runs to `ucNumEntries=255` → `power_state[1].clock_info` read at **allocation_end+8 faults in the guard page** (`FAULT addr=0x...c008` = arr+56); the claim's read reach is 255×48 ≈ 12.2 KB past a 1-entry allocation (measured sizeof = 48) |
| `4146-nullabort` | (3) NULL-word abort → swallowed-error chain | `ucNumEntries=4` with only entry 0 filled → the `:1777` OOB/NULL `power_state[1].clock_info` read aborts parse with `-EINVAL` → the rig simulates `radeon_pm.c:1448` discarding it → enable → the DF-4143 NULL-`boot_ps` fault at 0x28 |

## Fixed build (this finding's `fix.diff`, A/B on identical images)

`fix.diff` validates all three array offsets against the PowerPlayInfo table
size (captured via a second verbatim `atom_parse_data_header` call), bounds
the per-state walk (`state + 2 + ucNumDPMLevels` within the table) with full
unwind, and stops the loop when `i >= rdev->pm.num_power_states`. Fixed run:
`4146-offset/stride/powerguard/nullabort` all rejected cleanly (`init
rc=-22`, enable guarded, no fault); `4146-canary` (walk within table bounds
by construction) unchanged; `good` unchanged.

## Build / Run (on the DragonFly guest)

```
cd rig && sh ./build.sh /usr/src DF-4146
cd rig && sh ./run.sh
```

## Verdict

Reproduced (verbatim-rig): all three trust violations produce demonstrated
OOB reads at exact, predicted addresses, plus the canary data-flow proof and
the swallowed-abort → DF-4143 panic chain. Note (measurement): with the
measured `sizeof(struct radeon_power_state) == 48`, claim (3)'s reach is
~12.2 KB past a 1-entry allocation (the finding estimated ~25 KB). See
`VERDICT.md`, `verdict.json`.
