# DF-4247 — Missing central bounds-validation layer for VBIOS PowerPlay table parsing (processpptables.c)

## Status
**REPRODUCED (verbatim-rig evidence class, same as DF-4165).** All defect
families claimed by the finding were demonstrated in **byte-identical
production parser code** compiled into a guest-side harness and driven with
crafted PowerPlay tables; the pack's `fix.diff` was A/B-validated in the same
rig (fixed). In-kernel execution remains AMD-ASIC-gated on this guest (no AMD
GPU — proven, `env.txt`/`hw_probe.log`).

## What was verified and how
The rig (`rig/`) sed-extracts the **entire parser body** —
`processpptables.c` lines 37-1706, every offset/count/index site — plus 22
verbatim struct/type header slices (pptable.h 26-751, power_state.h,
hwmgr/hardwaremanager slices, atombios FW-info tables, processpptables.h
interface), and compiles them into a harness on the DragonFly guest.

- `rig/check_verbatim.sh` re-proved all 23 blocks **byte-identical**, run on
  the guest against **`/usr/src/sys` — the tree its running kernel (Jul 2
  2026 build) was compiled from** (`build.log`: `ALL VERBATIM CHECKS PASSED`;
  host/guest file SHA256 equality in `env.txt`).
- The fixed build is the same slice with this pack's `fix.diff` applied via
  `patch --fuzz=0` (bidirectionally proven: forward-apply + reverse-apply
  restores the pristine file byte-exactly).
- Shims (`rig/rig_shims.h`) touch no defect path: identity `le*_to_cpu`,
  calloc kzalloc, minimized `struct pp_hwmgr` with production names/types for
  every field the fix consults, and a `smu_atom_get_data_table` shim standing
  in for the ROM FirmwareInfo lookup (NULL-able, to prove DF-1471's missing
  NULL check). The crafted images mimic ATOM PowerPlay tables a crafted VBIOS
  (or root-written `pp_table` sysfs blob) feeds the driver on smu7/smu8
  (PP_TABLE_V0); each image ends at a PROT_NONE guard page or at a patterned
  victim region, making OOB source reads decisive.

## Observed (run.log / run.2.log / run.3.log — stable across 3 runs)
Layout: PT=37/46/66/88 bytes, `usVddcDependencyOnSCLKOffset`@54,
`usExtendendedHeaderOffset`@44, dep record=5B, NONCLOCK=24B, VCE/UVD info=6B.

| mode (crafted input) | baseline (verbatim) | fixed (+fix.diff) |
|---|---|---|
| clean-valid | rc=0, count=3, cb×1 (PASS) | identical (no regression) |
| dep-offset-oob (`usVddcDependencyOnSCLKOffset=0xFF00`, 128B image) | SIGSEGV, offset 65280 » 128 (`:1304-1309,385`) | rc=0, skipped |
| dep-count-255 (255 claimed, 3 records) | SIGSEGV, offset 146 ≥ 144 (`:393-399`) | count clamped to 3, no fault |
| leak-canary (40 claimed, 5 records + victim) | **70/70 field values byte-exact with bytes past the image** → dyn_state | clamped to 5, 0 leaked |
| statearray-offset-oob (`usStateArrayOffset=0xFF00`) | SIGSEGV, offset 65280 (`:875-878`) | rc=-1 |
| entry-v2-offbyone (`entry_index==ucNumEntries`) | SIGSEGV, offset 142 vs 141 (`:913-924`, `>` should be `>=`; also `get_state_entry_v2:784` `<=`) | rc=-1 |
| entry-v1-offbyone (fmt<6, `entry_index==ucNumStates`) | SIGSEGV, offset 136 vs 136 (`:937-947`) | rc=-1 |
| nonclock-index-255 (`nonClockInfoIndex=0xFF × ucEntrySize=0xFF`) | SIGSEGV, offset 65227 vs 242 (`:923-926,685`) | rc=-1 |
| clockinfo-index-255 (`clockInfoIndex[0]=0xFF × 0xFF`) | SIGSEGV, offset 65187 (`:929-932`; deref in the clock-info callback, as in smu7/smu8) | rc=-EINVAL, 0 callbacks |
| uvd-array-index (`ucUVDClockInfoIndex=0xFF`, init-time) | SIGSEGV, offset 1662 vs 134 (`:1099-1107`) | rc=0, index clamped |
| vce-clockinfo-index (`ucVCEClockInfoIndex=0xFF`) | SIGSEGV, offset 1662 (`:1571-1585`) | rc=-EINVAL |
| fwinfo-null (FirmwareInfo table absent) | **SIGSEGV at NULL+2** (`:1062-1066`, DF-1471 half) | rc=0 |

`fix_validation.log` = the full FIXED half of the decisive run.

## Primitive and impact
Source-side OOB **read** family: firmware offsets (u16), counts (u8) and
indices (u8) index straight out of the `soft_pp_table` heap image (typically
2-4 KB) — up to 65280 bytes past it — with the read bytes flowing 1:1 into
`hwmgr->dyn_state.*` tables (leak-canary: 70/70 byte-exact), which userland
reads back through powerplay sysfs/`pp_dpm_*` (kernel-heap info leak), plus a
NULL-deref panic path when the ROM lacks FirmwareInfo. Write sinks are not in
this file (dest allocations over-reserve: `8+12n ≥ 4+8n` — layout mode).
Gating: an AMD ASIC (smu7/smu8/smu10-shortcircuited) whose VBIOS carries a
crafted/corrupt PowerPlay table, or root re-parsing via
`/sys/class/drm/card0/device/pp_table` (amd_powerplay.c:672-706). No
unprivileged trigger on stock hardware.

## Build / run (on the audit guest, or any DF box with the kernel tree)
```
scp -r rig fix.diff root@guest:/root/df4247/
cd /root/df4247/rig && sh ./build.sh /usr/src   # verbatim checks + 2 binaries
sh ./run.sh                                      # baseline vs fixed, 13 modes
```

## fix.diff corrections found during verification (this run)
The pre-verification `fix.diff` did not compile: (1) `get_clock_voltage_limit`
guard referenced `table->numEntries` — the struct member is `ucNumEntries`
(pptable.h:531); (2) two `func(...)` calls passed an integer as the
`const void *clock_info` argument (int-conversion). Both corrected in this
pack's `fix.diff`; the corrected diff applies cleanly (fuzz=0) and is what the
rig validated.
