# DF-4395 VERDICT — vega10 dcefclk pioneer-path heap OOB write

**Status: reproduced (harness) | impact: corruption | confidence: certain**

## Root cause (path:line)

`get_dcefclk_voltage_dependency_table()` in
`sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c`:

- Line 709 asserts only `ucNumEntries != 0` — a u8 the VBIOS image fully
  controls (up to 255); nothing validates it against the image size
  (that systemic gap is DF-4396).
- Lines 718-726 — the pioneer workaround: when
  `adev->pdev->device == 0x6863 && rev_id == 0` and
  `entries[ucNumEntries-1].ulClk < 90000`,
  `num_entries = min(ucNumEntries + 1, NUM_DSPCLK_LEVELS=8)`.
- Lines 729-733 — the destination `kzalloc` is sized by **num_entries**
  (`4 + 28 * num_entries`; `phm_ppt_v1_clock_voltage_dependency_record`
  is 28 bytes — hwmgr_ppt.h:31-45).
- Lines 740-745 — the copy loop runs
  `for (i = 0; i < clk_dep_table->ucNumEntries; i++)` — **the raw
  firmware count**, not the allocation count.

=> For `ucNumEntries >= 9` on the pioneer SKU the loop writes
`(ucNumEntries - 8) * 28` bytes past the object. Worst case
`ucNumEntries = 255`: allocation 228 bytes, writes through byte 7144,
**6916 bytes past** — a linear heap overflow repeating a 28-byte record
whose first 4 bytes are an arbitrary VBIOS `u32` (`ulClk`) and whose 5th
byte is an arbitrary `ucVddInd` (only those two fields are written; the
rest of each record keeps the kzalloc zeros, i.e. mostly-zero spray with
a controlled dword every 28 bytes — classic heap-corruption primitive
into the DragonFly kernel heap, `kmalloc`-backed `kzalloc`).

## Trigger / threat model

Parse runs at `vega10_pp_tables_initialize()` (powerplay init, probe and
every `pp_table` sysfs reload). Attacker controls the table bytes via:
1. a crafted/flashed VBIOS ROM image (supply chain / re-flashed card /
   passthrough ROM), or
2. root writing `/sys/class/drm/card0/device/pp_table`
   (`amdgpu_pm.c:1007` DEVICE_ATTR pp_table S_IWUSR).

Device gate: PCI device id 0x6863, revision 0 ("pioneer" Vega10 SKU).
The parse-side count itself is unconstrained on that SKU.

## Reproduction (this pack)

Lifted-copy harness `dcefclk_oflow.c` (same approach the audit used for
DF-1179 in the same driver family): the allocation decision and copy
loop are replicated byte-for-byte with the real struct layouts
(`ATOM_Vega10_CLK_Dependency_Record` = 8 bytes,
`phm_ppt_v1_clock_voltage_dependency_record` = 28 bytes), the kzalloc is
emulated by a canary-guarded calloc. Run on the audit guest
(cc 8.3, DragonFly 6.5-DEVELOPMENT):

- `ucNumEntries=6` → guard intact (workaround allocates 7, copy 6+1)
- `ucNumEntries=9` → **28 bytes past the object** (guard smashed)
- `ucNumEntries=255` → writes through byte 7144 of the 228-byte object
  (**6916 bytes past**), first OOB record shows `clk=0x3f0` (the
  VBIOS-chosen u32 landing in kernel heap memory)
- negative control A (device 0x687f) and B (revision 1): guards intact —
  proves the overflow is the pioneer-cap interaction, not the copy loop
  in general (non-pioneer paths alloc == copy count).
- PATCHED loop (fix.diff bound): guard intact with `ucNumEntries=255`.

The in-kernel path cannot run on the guest (no AMD GPU — run.log
hardware gate), so this is a harness-level reproduction of the exact
overflow arithmetic of the kernel code, per the project's DF-1179
precedent.

## Fix

`fix.diff` (git apply --check OK): bound the copy loop by `num_entries`
— identical to upstream's current code:

```diff
-	for (i = 0; i < clk_dep_table->ucNumEntries; i++) {
+	for (i = 0; i < num_entries; i++) {
```

Excess entries are dropped (same semantics upstream). The image-side
count validation (reading 255 entries from an image that doesn't hold
them) is covered by the DF-4396 central layer, whose unit test includes
this path (`dcefclk: pioneer fw=255 -> alloc 8, copy 8`).

Fix validation: kernel-level not_testable (no ASIC on guest); harness
patched-control proves the fixed bound stops the overflow; the DF-4396
patched parser (which subsumes this fix) syntax-validates with 0 errors
on the guest kernel toolchain.

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