# DF-3890 — verdict narrative

## Finding

`kv_convert_vid2_to_vid7` (kv_dpm.c:72-92) falls back to the firmware-built
mapping table when `vddc_dependency_on_sclk.count == 0` (no SCLK dep subtable in
the PowerPlayInfo header — `amdgpu_parse_clk_voltage_dep_table` is only invoked
when `usVddcDependencyOnSCLKOffset != 0`, amdgpu_dpm.c:384-395):

```c
} else {
        for (i = 0; i < vid_mapping_table->num_entries; i++) {
                if (vid_mapping_table->entries[i].vid_2bit == vid_2bit)
                        return vid_mapping_table->entries[i].vid_7bit;
        }
        return vid_mapping_table->entries[vid_mapping_table->num_entries - 1].vid_7bit;  /* :90 */
}
```

`num_entries` is produced by `sumo_construct_vid_mapping_table` (:159-191) as the
index of the first zero `vid_7bit` run after compaction. If every `usVoltageID`
in the VBIOS `sAvail_SCLK[]` is 0 (while `ulSupportedSCLK` values are nonzero and
strictly increasing — needed so `num_max_dpm_entries > 0`), all `vid_7bit` stay 0,
the compaction breaks at `i == 0`, and **`num_entries == 0`**. Then `:90`
evaluates `entries[(u32)0 - 1]` = `entries[0xFFFFFFFF]` =
`entries + 17,179,869,180` bytes (~16 GiB) — a wild read, page fault → panic.

Reach (boot, before any power-state machinery):
`kv_dpm_init` (:2848) → `kv_parse_sys_info_table` (:2617) →
`kv_construct_max_power_limits_table` (:2040-2046: `num_max_dpm_entries > 0` is
checked, `num_entries` is **not**) → `kv_convert_2bit_index_to_voltage` (:690) →
sink. Also later from `kv_init_graphics_levels` else-branch (:2453, `kv_set_vid`).

The identical `entries[num_entries - 1]` pattern exists at `:114`
(`kv_convert_vid7_to_vid2`); its callers currently take the `count != 0` branch
(kv_init_graphics_levels :2433), so :90 is the live instance.

Distinct from DF-3887 (same source table, but that is the *write* with an
out-of-range `usVoltageIndex`; this is the read underflow when the table ends up
empty) and from DF-1268/1269 (count overflow / parse indices).

## Root cause (kernel references)

- `sys/dev/drm/amd/amdgpu/kv_dpm.c:90` — underflowed index read
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:114` — identical pattern (latent)
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:185-190` — num_entries can legitimately be 0
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:2045` / `:690` — reachable call chain
- `sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:384-395` — count==0 is parse-legal

## What was verified, and how

1. **Source-level certainty.** Full-file pass-2 audit; compaction logic walked
   for the all-zero-`usVoltageID` case; the `count == 0` else-branch selection
   verified against `amdgpu_parse_extended_power_table`.
2. **Harness on the guest** (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3):
   `vid_mapping_zero_entries.c` replicates the function verbatim; accounting mode
   prints `entries[4294967295]` / `+17179869180 bytes (~17.2 GB)`; crash mode
   executes the verbatim read against a single-page mapping → SIGSEGV exit=139
   (kernel equivalent: page fault in kernel mode → panic).
3. **Hardware absence.** Guest is virtio-only; trigger needs a KV APU + crafted
   VBIOS. Recorded **inconclusive**, impact none *on this guest*; source +
   harness certain.

## Fix validation

`fix.diff` guards `num_entries != 0` at both :90 and :114 (returning 0 otherwise).
Not built on the guest (no AMD hardware) → `fix_status: not_testable`.

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