# DF-4171 VERDICT (verify run, 2026-10-15)

**Status: REPRODUCED with a MECHANISM CORRECTION (verbatim-rig evidence
class). The count==0 out-of-bounds read in both
`smu8_dpm_update_vce_dpm` and `smu8_dpm_update_uvd_dpm` was demonstrated
in byte-identical production code at the exact computed address — but it
is an `entries[-1]` NEGATIVE-INDEX read 8 bytes BEFORE the table object
(uint8_t count promotes to int −1), NOT the filed "index 0xFFFFFFFF /
~16 GB wild read / panic". The load-time guard passthrough (0 ≤ 8) and
both user-reachable triggers (StablePState cap; `en_umd_pstate` via the
`power_dpm_force_performance_level` profile_* sysfs) are confirmed
in-tree. The pack's fix.diff was A/B-validated: fixed. In-kernel
execution is AMD-ASIC-gated on this guest (no AMD GPU — proven,
`env.txt`).**

## 1. Method

Same rig as DF-4169 (same run, one guest): the cited production functions
extracted byte-identical from smu8_hwmgr.c
(**:1251-1278 `smu8_dpm_update_vce_dpm`**, **:1846-1874
`smu8_dpm_update_uvd_dpm`** + helpers :49-97/:99-158/:248-252 and the
DF-4169 cast/level sites), 26 blocks checker-verified on the guest against
`/usr/src/sys` (`build.log`: `ALL VERBATIM CHECKS PASSED`). The VCE/UVD
tables are built exactly as `get_vce_clock_voltage_limit_table` /
`get_uvd_clock_voltage_limit_table` build them (processpptables.c:1114-1141:
`table_size = sizeof(unsigned long) + sizeof(table) * numEntries`, count =
raw numEntries u8) and allocated **page-aligned right after a PROT_NONE
page**, so the first byte BEFORE the object faults: the underflow
detector.

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

| mode | observation | verbatim site |
|---|---|---|
| 4171-vce-count0 | count=0 table (0 passes the only load-time guard `count <= SMU8_MAX_HARDWARE_POWERLEVELS`, :473), StablePState cap set → `smu8_dpm_update_vce_dpm` → **SIGSEGV READ at table−8** — predicted address printed beforehand and matched exactly (entries at +4, record 12 B ⇒ `entries[-1].ecclk` at base−8) | :1260-1261 |
| 4171-uvd-count0-en-umd | count=0 table, **no** StablePState cap, `hwmgr->en_umd_pstate = true` (the user sysfs route: `power_dpm_force_performance_level` → `pp_dpm_en_umd_pstate`, amd_powerplay.c:310-335) → `smu8_dpm_update_uvd_dpm(hwmgr, false)` → **SIGSEGV READ at table−8** | :1856-1857 |
| clean-valid | count=2 tables, stable+umd paths: `hard_min_clk = entries[1].ecclk/vclk` exactly, SMU msgs [0x20,0x1]/[0x1c,0x1], no fault | :1260/:1856 |

**Fixed (this pack's fix.diff, identical inputs):** both modes return
**rc=−EINVAL without faulting** (`if (!ptable || ptable->count == 0)
return -EINVAL;` before both entries reads); clean-valid byte-identical;
the DF-4169 modes still fault under this fix (orthogonality — see
DF-4169's pack, whose FIXED section conversely leaves these two modes
faulting). Fix A/B validated.

## 3. Mechanism correction (why "0xFFFFFFFF" is wrong here)

`ptable->count` is **uint8_t** (`struct phm_vce_clock_voltage_dependency_
table`, hwmgr.h:183-186; same for uvd, hwmgr.h:137-141). In
`ptable->entries[ptable->count - 1]` the usual arithmetic promotions apply:
`(int)count − 1` with count==0 is **int −1**, so the access is
`entries[-1]` — 8 bytes BEFORE the allocation (entries at offset 4, record
size 12). The ~16 GB/0xFFFFFFFF arithmetic would require a 32-bit count
type (that is the smu10_hwmgr.c:851 twin's shape). The rig proves the
actual address: predicted table−8 == faulting address, both modes.

Consequences of the correction:

- **No panic in-kernel.** The read lands inside the preceding slab page
  (kmalloc'd objects are surrounded by mapped kernel heap), so the
  realistic in-kernel behavior is a **silent 8-byte heap under-read**, not
  a fault. The finding's CVSS availability impact is overstated.
- **What the bytes feed:** the read value becomes
  `data->vce_dpm.hard_min_clk` / `data->uvd_dpm.hard_min_clk` and is then
  passed to the SMU (SetEclkHardMin/SetUvdHardMin via the level helpers)
  — wrong-clock programming from heap garbage, i.e. an integrity/robust-
  ness defect in the DPM engine. No path returning the raw value to
  userland was found (the sysfs exposes level indices computed by the
  level helpers, not the clock), so a confidentiality leak is NOT
  demonstrated.

The security-relevant core of the finding — **an unvalidated firmware u8
count that passes the only load-time check and indexes before the
allocation at runtime, on a user-reachable trigger** — is confirmed
verbatim. The bucket classification (kernleak/CWE-125 OOB read) stands;
the impact narrative (wild read/panic) is corrected to bounded
negative-index OOB read.

## 4. Fix validation

`rig_fixed` = same slice + this pack's fix.diff (header-normalized apply,
bidirectional patch proof, hunk-body cmp-identity — see DF-4169 pack;
both packs' fixed builds were built on the guest in the same session).
Both 4171 modes guarded (rc=−22, no fault), clean-valid byte-identical,
4169 modes unaffected (orthogonal). Rebuilt-kernel validation not
performed (AMD-ASIC-gated on this guest); `fix.diff` never applied to the
read-only `sys/` tree; guest `/usr/src` pristine before and after (md5
a0cb59b723434a78e88bf0580d5a5508).

## 5. Honest limitations

- Demonstrated in the rig (guard page before the object makes the
  underflow fault); in-kernel it would not fault — no in-kernel
  manifestation was observable on this guest (no AMD GPU), so the
  in-kernel impact statement rests on the promotion analysis + exact
  address match, not on a guest panic.
- The count==0 table is constructed directly (production table-builder's
  output for numEntries==0), not by parsing a full crafted VBIOS.
- The :454 hunk of this pack's fix.diff (upload-path NULL-table guard,
  DF-4170 residual) is outside the sliced functions and was not exercised
  by this rig.

## 6. Bottom line

The OOB read is real, reproduced in byte-identical production code at the
exact computed address, on both cited sites, under both cited triggers,
and the pack's zero-count guard eliminates it on identical input without
changing valid-table behavior. The filed mechanism (0xFFFFFFFF index,
~16 GB, panic) is corrected to `entries[-1]` (uint8_t→int promotion);
realistic impact is a bounded kernel-heap under-read feeding SMU clock
parameters, not a panic.

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