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

**Status: REPRODUCED (verbatim-rig evidence class). All defect families the
finding enumerates were demonstrated in byte-identical production code;
in-kernel execution is AMD-ASIC-gated on this guest (no AMD GPU — proven,
env.txt / hw_probe.log). Fix A/B-validated in the same rig: fixed.**

## 1. Method

The guest cannot attach amdgpu (Bochs SVGA 0x1234:1111, no 1002: device,
`/boot/kernel/amdgpu.ko` unloadable — env.txt). Following the DF-4165
evidence class for crafted-firmware parsers, the **actual production parser**
was compiled into a guest-side harness:

- `rig/gen_rig.sh` extracts **processpptables.c:37-1706 — the entire parser
  body** (all `get_*_offset`/size helpers, all copy helpers,
  `pp_tables_get_num_of_entries`/`get_entry`, the VCE-state getters,
  `pp_tables_initialize`, the `pptable_funcs` vtable) plus 22 header slices
  (pptable.h 26-751 = every ATOM_PPLIB struct; processpptables.h 28-49;
  power_state.h 26-187; hwmgr.h 88-186/335-345/347-362/364-373/375-409/
  454-465/485-493/595-624; hardwaremanager.h 55-252/254-258/283-300/326-333/
  344-370; atombios.h 207-214/2941-2945/3055-3131; atom-types.h; amd_asic_type.h;
  kgd_pp_interface.h 29-39; the PP_ASSERT_WITH_CODE macro).
- `rig/check_verbatim.sh` re-verified all 23 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`; SHA256 equality of the
  three key files host↔guest in 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).
- Harness shims (rig_shims.h) touch no defect path: identity `le16_to_cpu`/
  `le32_to_cpu` (LE host, like the target), calloc-backed `kzalloc`/`kfree`,
  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`
  exactly as hwmgr.h:718-719), and a `smu_atom_get_data_table` shim that
  returns the harness FirmwareInfo blob (or NULL, to exercise the DF-1471
  missing-NULL-check). The only live caller of that shim in the slice is
  `init_overdrive_limits` (processpptables.c:1062-1064).

Crafted tables mimic real ATOM PowerPlay tables: POWERPLAYTABLE5 header
(usTableSize gates the PT3/PT4/PT5 branches at :155/:1300/:1434), extended
header for VCE/UVD/SAMU/ACP/PowerTune offsets, StateArray/ClockInfoArray/
NonClockInfoArray for the state paths. Images end exactly at a PROT_NONE
guard page (making the first OOB source read fault at a computable offset) or
at a 256-byte patterned victim region (leak mode, no guard so the parse
completes and the captured bytes can be compared).

## 2. Observed (run.log; identical in run.2.log / run.3.log)

Exact layouts from the verbatim structs: PT=37, PT3=46, PT4=66, PT5=88;
`usVddcDependencyOnSCLKOffset`@54; `usExtendendedHeaderOffset`@44; dep
record=5 B, table hdr=6 B; `ATOM_PPLIB_NONCLOCK_INFO`=24; VCE/UVD ClockInfo=6.

**Baseline — production code faults on every crafted input (10/10), each at
exactly the predicted offset; classified by the SIGSEGV handler:**

| mode | fault offset vs table size | verbatim site |
|---|---|---|
| dep-offset-oob | 65280 vs 128 | `table->ucNumEntries` read at base+0xFF00 — :1304-1309 → :385 |
| dep-count-255 | 146 vs 144 | entries[3] read past 3-record table — :393-399 |
| statearray-offset-oob | 65280 vs 128 | `pstate_arrays->ucNumEntries` — :875-878 |
| entry-v2-offbyone | 142 vs 141 | `pstate_entry_v2->nonClockInfoIndex` at &states[N] — :913-924 (guard `>` accepts one-past; `get_state_entry_v2:784` `<=` walks it) |
| entry-v1-offbyone | 136 vs 136 | `pstate_entry->ucNonClockStateIndex` — :937-947 (same off-by-one) |
| nonclock-index-255 | 65227 vs 242 | `nonClockInfo + 0xFF*0xFF` deref in `init_non_clock_fields` — :923-926 → :685 |
| clockinfo-index-255 | 65187 vs 242 | `clockInfo + 0xFF*0xFF` handed to the clock-info callback — :929-932 (deref in the smu7/smu8 callback, mimicked by the harness cb) |
| uvd-array-index | 1662 vs 134 | `array->entries[0xFF].ucVClkHigh` at init time — :1099-1107 |
| vce-clockinfo-index | 1662 vs 134 | `entries[0xFF].ucEVClkHigh` via exported VCE getter — :1571-1585 |
| fwinfo-null | **NULL+2** | `fw_info->ucTableFormatRevision` with fw_info==NULL — :1062-1066 (DF-1471 half) |

**Leak leg (leak-canary):** ucNumEntries=40 with only 5 records in image,
image followed by patterned victim bytes: the parse completes and
**70/70 field values (clk+v 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 `hwmgr->dyn_state.vddc_dependency_on_sclk`,
which userland reads back via powerplay sysfs (pp_dpm_sclk / OD tables).
Stable across 3 runs.

**Regression control (clean-valid):** a fully valid table parses identically
in both builds (rc=0, count=3, one clock-info callback).

## 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 10 fault modes: **no fault** —
oversized offsets skipped (rc=0), counts clamped to entries-that-fit
(255→3, 40→5), off-by-one indices rejected (`>=` guards, rc=-1), indices
clamped/bounds-checked (rc=-EINVAL / entry 0), fw_info NULL-checked (rc=0);
leak-canary: **0 out-of-image values**; clean-valid: identical behavior.

Baseline corrupts, patched slice is clean on the same input → **fix
validated** (fix_validation.log). 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 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.

**fix.diff corrections (found by this verification — the pre-verification
draft did not compile):**
1. `get_clock_voltage_limit` empty-table guard used `table->numEntries`; the
   member is `ucNumEntries` (pptable.h:531). Fixed.
2. Two `func(...)` calls in `pp_tables_get_entry` passed an integer as the
   `const void *clock_info` argument (int-conversion; error on modern
   compilers). Cast added. Fixed.
Both corrections are in this pack's fix.diff and are what the rig validated.

## 4. Primitive and impact

Primitive: **attacker-directed source OOB reads out of the kernel-heap
PowerPlay image** — u16 offsets reach +65535, u8 count loops read linearly
past the image (fully value- and length-controlled: every count byte and
every index byte is firmware-chosen), u8 indices × u8 entry sizes reach
+65025 — with the leaked bytes converted 1:1 into dyn_state clock/voltage
tables exposed to userland (kernel-heap info leak, proven byte-exact), plus
a NULL-deref kernel panic when the ROM lacks a FirmwareInfo table. Write
sinks are NOT in this file (destination allocations over-reserve,
`8+12n ≥ 4+8n`, layout mode) — the downstream fixed-array overflows
(DF-1166, DF-4000..4006, DF-4036..4041, DF-1416/1609) remain separate
sink-side fixes, as the finding's coverage assessment states.

Threat model (the family's established one): crafted/corrupted GPU VBIOS
(flashed ROM, vfio-pci romfile override, malicious discrete device) parsed
at driver attach, 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` (amd_powerplay.c:672-706). No
unprivileged userland trigger on stock hardware. Demonstrated out-of-kernel
context (rig); impact recorded as `leak` (the proven, realistic ceiling of
this file's defects — info leak + oops-capable NULL deref), not `panic`
(the guest kernel never executed the path).

## 5. Honest limitations

- Demonstrated with production instructions and production struct layouts,
  but in a userland mapping, not the kernel's kmalloc heap. The leak leg
  proves the source bytes are captured; the slab neighborhood contents in
  kernel context are the allocator's, not a controlled victim.
- The off-by-one modes (entry-v2/v1) require a caller passing
  `entry_index == ucNumEntries`; the stock caller loop
  (pp_tables_get_num_of_entries → 0..N-1) stays in range, so those two are
  latent wrong-guard defects (defense-in-depth), demonstrated by direct call.
- clockinfo-index-255's fault lands in the clock-info callback (harness
  mimics the smu7/smu8 deref); the pointer computation itself is verbatim.
- 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.
- Cross-file lead unchanged (out of scope for this file):
  `pp_dpm_set_pp_table` memcpy heap overflow at amd_powerplay.c:684-689.

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