# DF-3585 Verification Verdict

**Severity:** High — kernel heap OOB write (crafted-VBIOS gated)
**Verification method:** source-line proof + deterministic logic-replica harness run
on the DragonFly QEMU guest (guest has **no AMD GPU** — virtio-only; the kernel code
path cannot be driven by real hardware on this guest, so this is `inconclusive` at
the kernel level per the honest-status mapping, while the memory-safety defect itself
is certain at the source level and demonstrated at the logic level).

## Source-level proof chain

1. **Attacker-controlled count, no clamp:**
   `sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:285` — `ucNumEntries` (raw VBIOS u8) sizes the
   dependency `entries` (kcalloc) and `:302` sets `count = ucNumEntries` (0..255).
   No upper clamp anywhere between parse and use.

2. **Sink 1 (write, controlled data):**
   `sys/dev/drm/amd/amdgpu/si_dpm.c:4410` —
   `voltage_table->count = voltage_dependency_table->count;` then
   `:4411-4414` writes `voltage_table->entries[i]` for `i < count`.
   `struct atom_voltage_table.entries` is fixed `[MAX_VOLTAGE_ENTRIES=32]`
   (`sys/dev/drm/amd/amdgpu/amdgpu_atombios.h:119-133`), embedded in
   `struct evergreen_power_info` (`si_dpm.h:668`) inside the single
   `kzalloc(sizeof(struct si_power_info))` (`si_dpm.c:7311`).
   => up to 1784 bytes OOB *write*, each 8-byte slot carrying a BIOS u16
   (`entries[i].v` of the dependency table) plus zeros.

3. **Reachability gating (all VBIOS-controlled):**
   * `si_dpm_init` `si_dpm.c:7389-7399`: `voltage_control` (GPIO LUT) false,
     `voltage_control_svi2` (SVID2 object present) true;
   * `si_dpm_enable` `si_dpm.c:6788-6798` -> `si_construct_voltage_tables()`;
   * `si_dpm.c:4436-4441` (VDDC SVI2) or `:4457-4463` (VDDCI SVI2).
   Fired at boot / module load / resume — before any power-state sanity check.

4. **Sink 2 (R-M-W):** `si_dpm.c:4505-4506` `table->lowSMIO[i] |= ...` for
   `i < count`, `lowSMIO[32]` fixed (`sislands_smc.h:207,215`); with a mixed
   GPIO-VDDC/SVI2-VDDCI config (`si_dpm.c:4538-4543`) the vddci table count
   (up to 255) drives OOB accesses across `SISLANDS_SMC_STATETABLE` and past the
   `si_power_info` allocation end for `i >= ~154` (statetable sits at the tail of
   the allocation, `si_dpm.h:1002-1012`).

5. **First-hop corruption detail (harness-verified):** the first OOB slot
   (`entries[32]`) lands exactly on `eg_pi->vddci_voltage_table.count`, replacing it
   with the BIOS-chosen u16 (demo: 0x1020). Every later consumer of the vddci table
   (`si_populate_voltage_value` si_dpm.c:4578-4584, `btc_find_voltage`
   si_dpm.c:3210-3214, `si_populate_smc_voltage_table` 4505) then walks an
   attacker-sized table — compounding OOB reads/writes. `current_rps.ps_priv` /
   `requested_rps.ps_priv` pointer slots (`si_dpm.h:675-678`) sit ~540 bytes past
   the overflow origin and are partially overwritten (2 controlled bytes + 4 zeroed)
   -> subsequent `si_get_ps()` returns a corrupted pointer.

## Harness result (DragonFly 6.5-DEVELOPMENT guest, base gcc)

```
[A] ... vddci_voltage_table.count -> now 4128 (BIOS-chosen u16 0x1020)
[A] bytes of the heap object rewritten past entries[32] = 446 ... OOB WRITE CONFIRMED
[B] child killed by SIGSEGV => the i>=32 stores leave the object mapping
[C] child killed by SIGSEGV at lowSMIO[32] => si_dpm.c:4505-4506 sink is OOB
[D] with fix.diff clamp: ret=-1 ... FIX VALIDATED at harness level (no OOB writes)
summary: A=1 B=1 C=1 (1=confirmed) D_fix_ok=1
```
Deterministic across 2+ runs (`run.log`, `run.2.log`). The guest serial log
(`env.txt`) independently shows the harness children dying on signal 11.

## Why not a kernel-level PoC on this guest

`pciconf -l`: only QEMU/virtio devices; the sole VGA is `vgapci0 chip=0x11111234`
(Bochs/virtio). `amdgpu.ko` exists in `/boot/kernel` and loads
(`[drm] amdgpu kernel modesetting enabled`), but no SI device exists, so
`si_dpm_*` never executes. Driving the real path requires an SI card whose VBIOS
PowerPlayInfo tables are attacker-crafted (malicious/flashed VBIOS or a passed-
through device with a custom ROM image).

## Exploitability assessment

* Write primitive: length fully BIOS-controlled (33..255 entries), content = 16-bit
  BIOS values at 8-byte stride with 4-byte zeroing — enough to corrupt adjacent
  heap-object fields (demonstrated: vddci.count, pointer slots).
* Placement: corruption is intra-`si_power_info` for the primary sink; sink 2 and
  the count-cascade can reach past the allocation.
* Ceiling on realism: requires crafted VBIOS (physical / malicious-card / flashed
  image), same threat model as the pass-1 family (DF-1127/1128/1130). No
  unprivileged-local software-only trigger exists.

## Fix validation

`fix.diff` clamps both the construction (`si_get_svi2_voltage_table`) and the
population (`si_populate_smc_voltage_table`) to `MAX_VOLTAGE_ENTRIES` /
`SISLANDS_MAX_NO_VREG_STEPS`. Harness part D: with the clamp, the oversized BIOS
table is rejected (`-EINVAL`) and zero corruption occurs. A full kernel build was
not repeated for this fix (guest kernel build budget); the patch is 2 two-line
guards with no type/control-flow changes, and mirrors the existing bound checks at
`si_dpm.c:4432-4435` (trim path) and `amdgpu_atombios.c:1539-1540`.

## Verdict

**inconclusive** at kernel level (no SI hardware on the verification guest);
**certain** at source level — the unclamped BIOS-controlled count flows into two
fixed-size kernel heap arrays and the OOB write arithmetic is deterministically
reproduced by the logic replica. Recommended fix as in `fix.diff`.

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