# DF-4396 VERDICT — central Vega10 PowerPlay validation layer

**Status: REPRODUCED (verbatim-rig evidence class, DF-4165/DF-4247) |
fix A/B-validated: FIXED | confidence: certain**

## Verification method

The **entire production parser** — `vega10_processpptables.c:34-1326`, every
offset/count/index site — plus 22 struct/type header slices were
machine-extracted from the kernel tree (`rig/gen_rig.sh`) and compiled into a
harness on the DragonFly guest. `rig/check_verbatim.sh` re-proved all **23
VERBATIM blocks byte-identical against `/usr/src/sys` on the guest — the tree
its running kernel (Jul 2 2026 build) was compiled from** (`build.log`: ALL
VERBATIM CHECKS PASSED; host/guest parser SHA256 equal 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 + slice equality +
reverse-apply restores the pristine file byte-exactly).

The rig's only non-production code (`rig_shims.h`) touches no defect path:
identity endian helpers, a 3-mode kzalloc (plain / page-guarded destination /
0xAA canary), minimized `pp_hwmgr`/`amdgpu_device`/`pci_dev` with production
names and types for every field the parser and the fix consult, and the
verbatim `PP_ASSERT_WITH_CODE`. Crafted images end either at a 22-page
PROT_NONE tail (deep-offset reads fault deterministically — a small guard let
the +0xFF00 modes land in adjacent heap mappings in the first rig iteration,
which is why the guard region is 22 pages) or at a deterministic victim
pattern (leak provenance).

## Every cited defect family proven (baseline = verbatim production code)

All numbers from `run.log` (fault-offset fingerprints identical across
`run.2.log`/`run.3.log` — 3 runs, 16 SIGSEGVs each):

**A. USHORT offsets dereferenced unvalidated (deep OOB reads + NULL deref)**
- `check_powerplay_tables:72-82` — `usStateArrayOffset=0xFF00` on a 128-byte
  image: `state_arrays->ucNumEntries` read at image+65281 → SIGSEGV
  (+65153 past image end).
- `vega10_get_number_of_powerplay_table_entries:1246-1249` — the same offset,
  zero validation of any kind: SIGSEGV at image+65281.
- `init_thermal_controller:127-134` — `usThermalControllerOffset=0xFF00`:
  `ucType` read at image+65281 → SIGSEGV.
- `get_pcie_table:781` — PCIE table present + `usGfxclkDependencyTableOffset
  == 0` → `table_info->vdd_dep_on_sclk->count` on NULL → SIGSEGV at 0x0
  (DF-1587).

**B. u8 counts size alloc AND copy loop (source OOB reads → kernel-heap leak)**
- socclk/mclk/pixclk (shared getter)/mm and the GFXCLK V1 walk: `ucNumEntries
  =255` with 1-2 records in image → SIGSEGV at image_end+0..4 in the copy
  loop. Leak legs (victim-pattern images): `vdd_dep_on_socclk` 35/35 OOB
  records and the GFXCLK V2 13-byte-stride walk 38/38 OOB records
  **byte-exact with the bytes past the image** — the cloned values surface to
  userland via powerplay sysfs (`pp_dpm_*`), exactly the DF-4247 leak class.
- `init_over_drive_limits:269-273` — rev-1 GFXCLK table, `ucNumEntries=255`,
  1 record: `patom_record_v2[254].ucACGEnable` read 3298 bytes past image end
  → SIGSEGV (the DF-1589 unclamped `[count-1]`). Leak leg: with the OOB byte
  readable, `overdriveLimit.engineClock` (sysfs-visible) is decided by that
  **OOB byte** while the last in-image record carries the opposite bit —
  byte-exact 1-bit leak provenance, A/B-decisive against the fixed build.

**C. Rev dispatch over-reads larger structs from shorter subtables**
- fan table: header ucRevId=11 (V2) with only the header byte in image → V2
  field read at image_end+31 → SIGSEGV (`:210-254`).
- PowerTune: rev 7 (→V3) with a V2-sized 38-byte table → `usBoostStartTemperature`
  read at image_end+0 → SIGSEGV (`:472-513`).
- hard limits: header claims 1 entry, no record bytes → `entries[0]` read at
  image_end+0 → SIGSEGV (`:810-819`).

**D. Heap OOB WRITES past kzalloc'd destinations (attacker-controlled)**
- `get_vddc_lookup_table:1026-1038` (DF-1586): alloc `4+8*10=84`
  (`kmalloc-128` class), loop copies 255 → dest-guard SIGSEGV at **obj+86**;
  canary run: 494 bytes changed in [+2,+2463], **247/247 OOB us_vdd writes
  byte-exact equal to the crafted image bytes** (strided 2-byte writes, +2
  every 10 bytes — fully attacker-controlled linear overflow of the adjacent
  slab object).
- `get_dcefclk_voltage_dependency_table:721-750` (pioneer, dev 0x6863 rev 0):
  alloc `4+8*28=228` (`kmalloc-256`), loop runs 255 → dest-guard SIGSEGV at
  **obj+232**; canary run: 1232 bytes changed reaching **+6892** past the
  object (~6.6 KB into adjacent slab objects; values = crafted-image clocks).

**E. Masked parse failure (the DF-3702 NULL-table feeder)**
- `init_dpm_2_parameters:1103-1128` — failing VDDC lookup (returns 1, leaves
  `vddc_lookup_table==NULL`) overwritten by succeeding VDDMEM/VDDCI lookups:
  parse returns **rc=0 with a NULL table** — reproduced exactly; consumers
  NULL-deref (vega10_hwmgr.c:293-313).

**F. Off-by-one state entry (DF-1588)**
- `vega10_get_powerplay_table_entry:1308` `entry_index <= ucNumEntries` lets
  `entry_index==ucNumEntries` pass → `states[N].usClassification` read at
  image_end+12 → SIGSEGV.

**G. No regression**: `clean-valid` — a complete, well-formed table parses
identically in baseline and fixed builds (23/23 structural checks OK,
including the state-entry callback path).

## Fix A/B (same rig, same inputs, fix.diff applied to the same slice)

| family | baseline | fixed |
|---|---|---|
| deep offsets (state/thermal/number) | SIGSEGV +65281 | rc=-1/-22, range-checked |
| fan V2 / PowerTune V3 / hard-limit tail | SIGSEGV | rc=-EINVAL/-1, full-struct range checks |
| count-255 copy loops | SIGSEGV / byte-exact leak | counts clamped to image (1/2/5), 0 leaked |
| od ACG `[count-1]` | SIGSEGV +3298 / OOB decision bit | clamped, decision bit in-image |
| pcie NULL sclk | SIGSEGV addr 0 | rc=-EINVAL (NULL guard) |
| vddc lookup write | SIGSEGV obj+86 / 247 strided writes | canary intact, count=8 |
| dcefclk pioneer write | SIGSEGV obj+232 / +6892 overwrite | canary intact, loop=8 |
| result clobber | rc=0, NULL vddc table | rc=1 propagated |
| entry off-by-one | SIGSEGV image_end+12 | rc=-1 (`<` + per-entry range check) |
| clean-valid | OK | identical |

**fix_status: fixed** (baseline reproduced on the verbatim slice; patched
slice clean on every mode; valid tables parse identically).

## Hardware gating (unchanged from the pre-verification assessment)

In-kernel execution requires an AMD Vega10 ASIC: the parse runs at driver
attach (ROM-provided table) or on root-written `pp_table` sysfs re-parse
(amd_powerplay.c:671-706 — `kmemdup` + `soft_pp_table` swap + reset). This
guest has no 0x1002 PCI device, no /dev/dri, no amdgpu module (env.txt), so
the guest kernel never executes the path; reproduction is in the verbatim rig
per the established evidence class for crafted-firmware parsers. There is no
unprivileged userland trigger on stock hardware; the realistic vectors are a
crafted/corrupt VBIOS (flashed ROM, vfio romfile, malicious device) or the
root-gated sysfs blob.

## Impact ceiling

Strongest proven primitive: **attacker-controlled heap OOB write** up to
~6.6 KB past a `kmalloc-256` object (dcefclk pioneer) and a 2463-byte strided
overwrite past a `kmalloc-128` object (vddc lookup), both with byte-exact
content control from the firmware image; plus a byte-exact kernel-heap info
leak into sysfs-visible tables and several panic paths. Recorded impact
"dos" follows the DF-4165 precedent for the identical evidence class and
gating (heap OOB write proven in verbatim code, in-kernel execution
ASIC-gated, no unprivileged trigger).

## Corrections to the pre-verification pack (this run)

- The verbatim rig supersedes the syntax-check + clamp-unit-test approach
  (`vega10_clamp_test.c`, `vega10_processpptables_fixed.c` removed).
- The finding text calls the GFXCLK V2 record "12-byte"; the packed struct is
  13 bytes (`ulClk 4 + ucVddInd 1 + usCKSVOffsetandDisable 2 + usAVFSOffset
  2 + ucACGEnable 1 + ucReserved[3] 3`, vega10_pptable.h:147-154) — the rig
  measures and uses sizeof() throughout; no impact on any defect or fix.
- fix.diff required NO corrections this run (applies fuzz=0, compiles clean,
  all modes fixed; contrast DF-4247 where the pre-verification fix had two
  compile bugs).

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