# DF-4388 VERDICT (verify run, 2026-10-14)

**Status: REPRODUCED (verbatim-rig evidence class). Every defect family the
finding enumerates was demonstrated in byte-identical production code —
including the heap out-of-bounds WRITE at `get_vddc_lookup_table()` — and
the pack's fix.diff was A/B-validated in the same rig: fixed. In-kernel
execution is AMD-ASIC-gated on this guest (no AMD GPU — proven,
`env.txt` / `hw_probe.log`).**

## 1. Method

The guest cannot attach amdgpu (no 1002: PCI device, no `/dev/dri`).
Following the DF-4165/4247/4396 evidence class for crafted-firmware
parsers, the **actual production parser** was compiled into a guest-side
harness:

- `rig/gen_rig.sh` extracts **process_pptables_v1_0.c:34-1337 — the entire
  parser body** (every static helper, `pp_tables_v1_0_initialize`/
  `uninitialize`, the exported `get_number_of_powerplay_table_entries_v1_0`
  / `get_powerplay_table_entry_v1_0`, the `pptable_v1_0_funcs` vtable) plus
  24 header slices: pptable_v1_0.h 24-30 + 33-436 (every ATOM_Tonga_*
  firmware struct, packed layout preserved — sliced around its single
  `#include "hwmgr.h"` at :32), hwmgr_ppt.h 31-113 (phm_ppt_v1_* dest
  types), hwmgr.h 88-186/335-345/375-409/454-465/485-493/497-524/595-624/
  626-631/633-677/679-685, hardwaremanager.h caps enum + helpers +
  PP_Clocks + platform descriptor, power_state.h 26-187, kgd_pp_interface
  amd_vce_state + AMD_MAX_VCE_LEVELS, atombios.h ATOM_COMMON_TABLE_HEADER,
  atom-types.h, and the verbatim PP_ASSERT_WITH_CODE /
  GET_FLEXIBLE_ARRAY_MEMBER_ADDR macros from pp_debug.h.
- `rig/check_verbatim.sh` re-verified all 24 blocks **byte-identical — run
  on the guest against `/usr/src/sys`, the tree its running kernel was
  compiled from** (`build.log`: `ALL VERBATIM CHECKS PASSED`; guest↔host
  MD5 equality of all ten sliced sources, `env.txt`).
- The fixed build is the same slice **+ exactly this pack's fix.diff**
  (applied to a copy of the pristine full file with `patch --fuzz=0`;
  proven bidirectionally: forward apply succeeds with no rejects, reverse
  apply restores the pristine file byte-exactly; the packed reference
  `process_pptables_v1_0_fixed.c` is byte-equal to pristine+fix.diff).
- Harness shims (`rig/rig_shims.h`) touch no defect path: identity
  `le16_to_cpu`/`le32_to_cpu` (LE host, like the target), a minimized
  `struct pp_hwmgr` carrying production names/types for every field the
  parser and the fix consult (`soft_pp_table`, `soft_pp_table_size` as
  hwmgr.h:718-719; `vce_states[AMD_MAX_VCE_LEVELS]`, `num_vce_state_tables`
  as hwmgr.h:722-723), and `kzalloc`/`kfree` mapped **end-anchored at a
  PROT_NONE guard page** so the first byte past any allocation faults —
  the destination-OOB-write detector. The allocator fidelity tradeoff
  (kmalloc slab vs mmap) touches no defect path: the defect is the
  count-vs-allocation mismatch (255 firmware entries vs a 16-record
  allocation), not the allocator.

Crafted tables mimic real Tonga-format PowerPlay tables (revision 7 =
ATOM_Tonga_TABLE_REVISION_TONGA, verbatim packed structs, little-endian
host so struct member writes produce exactly the firmware bytes). Images
end exactly at a PROT_NONE guard page (source OOB reads fault at a
computable offset), at a patterned victim region (leak mode, no fault so
the captured bytes can be compared), or start right after a PROT_NONE page
(negative-index underflow mode).

## 2. Observed (run.log; byte-identical in run.2.log / run.3.log, md5-equal)

Exact layouts from the verbatim structs (layout mode): POWERPLAYTABLE=77B,
Tonga state=20B, SCLK rec=11B (Polaris 15B), MCLK rec=13B, MM rec=23B,
lookup rec=8B → `get_vddc_lookup_table` allocates 4+10*16=**164B** while
its copy loop is bounded by firmware `ucNumEntries` (≤255).

**Baseline — production code faults on every crafted input (13 SIGSEGVs),
each classified by the fault handler, plus the leak leg and two behavioral
proofs:**

| mode | fault offset vs table | verbatim site |
|---|---|---|
| vddc-lookup-255-write | **kzalloc(164)+0 — DESTINATION HEAP OUT-OF-BOUNDS WRITE** (source records all in-image; write loop bound 255 vs 16-record allocation; up to ~2390B of firmware-chosen `usVdd/usCAC*` values past the object) | `get_vddc_lookup_table` :176-190 |
| statearray-offset-oob | 65281 vs 256 | `state_arrays->ucNumEntries` at base+0xFF00 — :1042-1053 |
| thermal-offset-oob | 65281 vs 256 | `thermal_controller->ucType` — :891-897 |
| fan-offset-oob | 65280 vs 256 | `fan_table->ucRevId` — :922-929 |
| sclkdep-offset-oob | 65280 vs 256 | `sclk_dep_table->ucRevId` — :781-783 → :420 |
| ppm-offset-oob | 65281 vs 256 | `atom_ppm_table->ucPpmDesign` — :301-305 → :214-215 |
| vce-offset-oob | 65281 vs 256 | `vce_state_table->ucNumEntries` — :1219-1222 |
| sclk-count-255 | 131 vs 131 | entries[3] read past a 3-record table — :438-451 |
| vce-sclk-index-255 | 2914 vs 256 | `entries[ucSCLKIndex=0xFF].ulSclk` at sclk_table+2+255*11 — :1249-1252,1261 |
| vce-mclk-underflow | **image−2** | `entries[ucNumEntries−1]` = `entries[-1]` with count 0 — :1263-1267 |
| entry-offbyone | 178 vs 170 | `entry_index <= ucNumEntries` accepts one-past; `state_entry->usClassification` at image end +10 — :1310-1321 |
| pcie-null-vdddepsclk | **NULL+0** | `vdd_dep_on_sclk->count` with sclk offset 0 — :522 (DF-1575) |
| gpio-null-vdddepsclk | **NULL+0** | same in `get_gpio_table` — :745 (DF-1575) |

**Leak leg (leak-canary):** MM dep `ucNumEntries=40` with only 5 records in
image, image followed by patterned victim bytes: the parse completes and
**175/175 field values (dclk+vclk+eclk+aclk+samclock of entries[5..39])
are byte-exact with the victim bytes at the exact OOB source positions** —
kernel-heap data past `soft_pp_table` flows 1:1 into
`pp_table_information->mm_dep_table`, which userland reads back through
the powerplay sysfs/DPM interfaces. Stable across 3 runs.

**Behavioral proofs:** `gpio-gate-zero-offset` — with
`usGPIOTableOffset==0` the :843 gate tests `powerplay_table+0` (never
NULL), so `get_gpio_table` runs and reads `ucVRHotTriggeredSclkDpmIndex`
from PowerPlay header byte 1 (the "SCLK DPM index for VRHot cannot exceed"
assert fires on header bytes); `vce-count-unclamped` —
`hwmgr->num_vce_state_tables` stored = 200 against a 6-entry
`vce_states[]` (the fill loop is gated, but every consumer walking the
stored count reads past the array — DF-4389's root, :1328).

**Regression control (clean-valid):** a fully valid table (2 states, fan,
thermal, MM 2, PowerTune, SCLK 3, MCLK 2, PCIE 1, hard limits, VCE 2
states, PPM, vddc lookup 4, vddgfx lookup 2, GPIO) parses **identically in
both builds** — rc=0, all counts and limits byte-equal, TDP=250,
vce_states[0]={evclk=100000, sclk=30000}.

## 3. Fix validation (A/B, identical crafted inputs)

`rig_fixed` = same verbatim slice + this pack's fix.diff (checker-proven
identity, bidirectional patch proof). All 13 fault modes: **no fault** —
oversized offsets rejected (rc=-1/-22), counts clamped to the entries that
fit AND to destination constants (255→16 lookup, 255→3 sclk, 40→5 mm),
VCE indices clamped to 0, `entries[-1]` gone, one-past entry rejected
(rc=-1), NULL guards on PCIE/GPIO (rc=-22); leak-canary: **0 out-of-image
values**; gpio-gate: GPIO parse skipped when the offset is 0; vce count
stored clamped to 6. clean-valid: identical behavior to baseline.

Baseline corrupts, patched slice is clean on the same input → **fix
validated** (`fix_validation` = the FIXED half of `run.log`; three
identical runs). A rebuilt-kernel validation was not performed: the fix
sites execute in-kernel only on AMD ASICs absent from this guest; per the
verbatim-rig protocol (DF-4165/4247/4396) the fixed slice was A/B-validated
in the same rig. `fix.diff` is authored against the pristine read-only
`sys/` tree and was never applied there (guest `/usr/src` verified pristine
by MD5 before and after).

## 4. Primitive and impact

Primitives, all in verbatim production code:

1. **Heap OOB WRITE** (`get_vddc_lookup_table` :176-190): a firmware UCHAR
   (`ucNumEntries` ≤ 255) bounds a copy loop into a fixed 164-byte
   allocation → up to ~2390 bytes past the object with firmware-chosen
   content (`usVdd`, `usCACLow/Mid/High` — 16-bit values), via both
   `usVddcLookupTableOffset` and `usVddgfxLookupTableOffset`. In-kernel
   this is kmalloc-heap corruption; the realistic floor is a kernel
   panic/oops (impact recorded as `dos`), with content-controlled heap
   overwrite potential.
2. **Attacker-directed source OOB reads** out of the kernel-heap PowerPlay
   image: USHORT offsets reach +65535, UCHAR count loops read linearly
   past the image (fully value- and length-controlled), UCHAR indices ×
   record sizes reach +2807 (VCE sclk index), `entries[-1]` underflow
   reads *before* the image — with the leaked bytes converted 1:1 into
   parsed DPM tables exposed to userland (175/175 byte-exact, proven).
3. **NULL-deref kernel panics** (`vdd_dep_on_sclk` in `get_pcie_table` /
   `get_gpio_table`) whenever the ROM omits the SCLK dependency table but
   ships a PCIE or GPIO table; plus the never-NULL gate defect that reads
   the PowerPlay header as a GPIO table when the offset is 0.
4. **Unclamped count store** (`num_vce_state_tables` ≤ 255 vs
   `vce_states[6]`) — DF-4389's provenance root.

Threat model (the family's established one): crafted/corrupted GPU VBIOS
(flashed ROM, vfio-pci romfile override, malicious discrete device) parsed
at driver attach (`pptable_v1_0_initialize`), or root re-parsing a crafted
blob through `/sys/class/drm/card0/device/pp_table` → `pp_dpm_set_pp_table`
→ `amd_powerplay_reset` → `pptable_init`. No unprivileged userland trigger
on stock hardware — same class as DF-4247 (V0 twin). Demonstrated
out-of-kernel context (rig); the write primitive's in-kernel consequence
(panic at minimum) follows from the verbatim instruction stream and the
production allocation sizes.

## 5. Honest limitations

- Demonstrated with production instructions and production struct layouts,
  but in a userland mapping, not the kernel's kmalloc heap. The kzalloc
  shim's guard pages *detect* the destination overflow; they do not model
  slab freelist interaction (whether the ~2390-byte overwrite is
  weaponizable in-kernel was not investigated — the VBIOS-gated threat
  model gives the attacker kernel-equivalent control already at ROM-flash
  time in the realistic scenarios).
- The leak leg proves source bytes are captured into parsed tables; the
  slab neighborhood contents in kernel context are the allocator's, not a
  controlled victim.
- `entry-offbyone` requires a caller passing `entry_index ==
  ucNumEntries` (the stock caller loops 0..N-1), so it is a latent
  wrong-guard defect (defense-in-depth), demonstrated by direct call.
- The crafted images are synthetic minimal tables; a real crafted ROM must
  also satisfy earlier ATOM ROM parsing stages, which precede and do not
  constrain these fields.
- Offset modes demonstrate 6 of the 16 USHORT consumers concretely; the
  remaining ten (MM/PowerTune/MCLK/HardLimit/PCIE/VddcLookup/VddgfxLookup/
  GPIO/Fan/VCE-in-entry-getter) are the same unvalidated
  `powerplay_table + offset` pattern at :772-792/:281-304/:1219-1240 and
  are all covered by the fix's range checks (validated where exercised:
  MM count clamp in leak-canary, vddc/vddgfx lookup range checks and
  count clamps in the write mode, VCE table checks in the index modes).

## 6. Bottom line

The central-fix claim is verified end to end: the V1 parser consumes every
firmware offset/count/index unvalidated (heap OOB write, OOB reads with
byte-exact leak, NULL derefs, negative-index underflow, off-by-one,
unclamped count store — 16 defect modes demonstrated in byte-identical
production code), and the pack's fix.diff neutralizes every demonstrated
mode on identical input while leaving valid-table parsing byte-identical.

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