# DF-3887 — verdict narrative

## Finding

`sumo_construct_vid_mapping_table()` (sys/dev/drm/amd/amdgpu/kv_dpm.c:159-191)
fills `pi->sys_info.vid_mapping_table.entries[]` directly from the VBIOS:

```c
for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) {          /* :165 */
        if (table[i].ulSupportedSCLK != 0) {                    /* :166 */
                vid_mapping_table->entries[table[i].usVoltageIndex].vid_7bit =   /* :167 */
                        table[i].usVoltageID;
                vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit =   /* :169 */
                        table[i].usVoltageIndex;
        }
}
```

`entries[]` is `struct sumo_vid_mapping_entry entries[SUMO_MAX_NUMBER_VOLTAGES]`
(kv_dpm.h) — **4 slots × 4 bytes = 16 bytes**. `usVoltageIndex` is a **USHORT
(0..65535)** from `ATOM_AVAILABLE_SCLK_LIST` (sys/dev/drm/amd/include/atombios.h:5705-5710),
copied verbatim out of the card's VBIOS `ATOM_INTEGRATED_SYSTEM_INFO_V1_8.sAvail_SCLK[5]`
(atombios.h:6172). There is **no bounds check anywhere** between the VBIOS image and
the array store. A VBIOS (flash-modified, supply-chain, or VM firmware-passthrough)
with `usVoltageIndex >= 4` executes up to 5 chosen-offset 16-bit-pair writes:

- write 1 (value): `usVoltageID` — fully attacker-chosen 16-bit
- write 2 (value): `usVoltageIndex` itself
- offsets: `entries + 4*usVoltageIndex` and `+2`, i.e. any 4-byte-aligned slot in
  `entries + [4 .. 262142]`

`vid_mapping_table` lives ~160 bytes into the heap `kv_power_info`
(`kzalloc` at kv_dpm.c:2797, ~1.3 KB: `graphics_level[8]`, SMC offsets
`dpm_table_start`/`sram_end`, level counts, boot levels...). The write window
therefore covers both intra-object corruption (e.g. `pi->dpm_table_start`, which
later becomes the destination offset of `amdgpu_kv_copy_bytes_to_smc` writes, and
`graphics_dpm_level_count`) and, beyond ~1.3 KB, adjacent kernel heap objects — a
genuine arbitrary-offset heap OOB write with (two-u16) attacker-controlled data,
executed at every Kaveri/Kabini/Mullins boot (`kv_dpm_init` → `kv_parse_sys_info_table`
:2613).

This is distinct from pass-1 DF-1268 (unbounded *counts* overflowing the fixed
`graphics_level`/`uvd_level`/... arrays via `kv_init_graphics_levels`/`kv_populate_*`)
and DF-1269 (unvalidated *indices/offsets* in `kv_parse_power_table` causing OOB
*reads*): the victim here is `sys_info.vid_mapping_table.entries[]`, the input is the
IGP table's `sAvail_SCLK` (not the powerplay tables), and the primitive is a write.

## Root cause (kernel references)

- `sys/dev/drm/amd/amdgpu/kv_dpm.c:167-170` — unchecked `usVoltageIndex` store
- `sys/dev/drm/amd/amdgpu/kv_dpm.h` — `entries[SUMO_MAX_NUMBER_VOLTAGES /* 4 */]`
- `sys/dev/drm/amd/include/atombios.h:5705-5710` — `USHORT usVoltageIndex`
- `sys/dev/drm/amd/amdgpu/kv_dpm.c:2613` — call site (init path)

## What was verified, and how

1. **Source-level certainty.** Full-file pass-2 audit; struct geometry verified in
   kv_dpm.h and atombios.h (array=4×u16-pairs, index=USHORT); call path
   `kv_dpm_init:2848 → kv_parse_sys_info_table:2613` runs unconditionally for
   crev==8 IGP tables (the only supported revision, :2574-2577).
2. **Harness on the guest** (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3):
   `vid_mapping_oob_write.c` replicates the verbatim loop with 1:1 layouts.
   Accounting mode shows the exact OOB write set (entries+1200..+1210 for indices
   300-302, payload 0xBEEF/0xCAFE/0x1234); crash mode executes the verbatim store
   with `usVoltageIndex=65535` against a single page mapping → write at +262140 →
   SIGSEGV exit=139 (kernel equivalent: page fault in kernel mode → panic; in the
   kernel the same store into mapped adjacent heap silently corrupts it instead).
3. **Hardware absence.** The guest is virtio-only (pciconf: no 1002:xxxx device;
   amdgpu.ko present but unloadable/useless). The in-kernel trigger needs a
   Kaveri/Kabini/Mullins APU with a crafted VBIOS (physical flash / supply chain /
   VM GPU firmware). Per pass-2 spawn instructions this is recorded as
   **inconclusive**, impact none *on this guest* (source + harness: certain).

## Exploit chain (up to the hardware gate)

flash crafted VBIOS (physical/supply-chain) → boot → `kv_dpm_init` → OOB write
window `[entries+4 .. entries+262142]` inside/after the `kv_power_info` heap object →
corrupt `dpm_table_start`/`sram_end` (SMC SRAM write offsets used by every
`amdgpu_kv_copy_bytes_to_smc`), `graphics_dpm_level_count`, or adjacent slab objects →
second-order controlled writes to SMU SRAM / kernel heap. Not exercisable on this
guest.

## Fix validation

`fix.diff` authored post-verification against the read-only `sys/` tree (clamp
`usVoltageIndex < SUMO_MAX_NUMBER_VOLTAGES` before both stores, mirroring the
`clock_array_index >= ucNumEntries` check Linux already has at :2758). Not built:
no AMD hardware on the guest → `fix_status: not_testable`; the diff is minimal and
mechanically checkable against :165-171.

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