# DF-1586 — Heap buffer overflow in get_vddc_lookup_table (vega10_processpptables.c)

## Verdict

**NOT REPRODUCED ON THIS GUEST** — bug is real in source (sibling of
DF-1574 in the vega10 powerplay parser), but the affected TU is part of
the `amdgpu` KLD module, not in `X86_64_GENERIC`, not loaded on this
guest, and no AMD GPU is present.  Latent memory-corruption defect.
Fix authored, applies cleanly, compiles cleanly.

## The bug (confirmed in source)

`sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c`, function
`get_vddc_lookup_table` (lines 1014-1043):

* Line 1026-1027 — allocation uses the *caller's* `max_levels`:
  `table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_voltage_lookup_record) * max_levels;`
* Three callers in `init_dpm_2_parameters` pass different `max_levels`:
  - line 1109 — vddc:        `max_levels=8`   → table_size = 4 + 80 = 84  (kmalloc-128)
  - line 1118 — vddmem:      `max_levels=4`   → table_size = 4 + 40 = 44  (kmalloc-64)
  - line 1127 — vddci:       `max_levels=4`   → table_size = 4 + 40 = 44  (kmalloc-64)
* Line 1034 — `table->count = vddc_lookup_pp_tables->ucNumEntries;`
  (`ucNumEntries` is UCHAR 0..255 — `vega10_pptable.h:237`).
* Line 1036-1038 — loop is bounded by `ucNumEntries`, **not** `max_levels`:
  `for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++)`
  `   table->entries[i].us_vdd = le16_to_cpu(...usVdd);`
* `entries` is of type `phm_ppt_v1_voltage_lookup_record` (5x uint16 = 10
  bytes — `hwmgr_ppt.h:78-84`), so each iteration walks 10 bytes forward
  even though only the first 2 (`us_vdd`) are written.
* For vddmem (max_levels=4): worst case `ucNumEntries=255` writes at
  offset `4 + 254*10 = 2544` into a 44-byte kmalloc-64 allocation →
  **~2500 bytes of (partly) controlled OOB write** into the kmalloc-64
  bucket.

### Sibling functions in this same file are correct

`get_mm_clock_voltage_dependency_table` (lines 302-330),
`get_gpio_table` (no — that's right),
`get_clock_voltage_dependency_table` (lines 528-557, 603-...,
669-..., 709-...), all use `clk_dep_table->ucNumEntries` for BOTH
allocation and loop (e.g. line 531-533 / 540 / 542).  Only the vega10
`get_vddc_lookup_table` helper splits the bounds — clear inconsistency.

## Reachability on this guest

* Same as DF-1574 — `amdgpu` not in `X86_64_GENERIC`, not in `LINT64`
  except as a loadable KLD; no AMD GPU hardware on the QEMU guest.
* `get_vddc_lookup_table` is only reachable via
  `vega10_pp_tables_initialize` → `init_dpm_2_parameters`, called during
  amdgpu driver attach to a Vega 10-class AMD GPU.

### Realistic threat model (when reachable)

* An attacker who controls the VBIOS PowerPlay table supplied to the
  amdgpu driver on Vega 10 hardware can trigger the OOB.
* Real-world scenarios:
  1. **VFIO PCI passthrough** of an AMD Vega 10 GPU to a DragonFly
     guest, with a crafted `vfio-pci.romfile=`.
  2. **Malicious FPGA GPU / PCIe device** presenting a Vega 10
     vendor/device ID.
* On a host where root can also write to
  `/sys/class/drm/cardN/device/pp_table` (if exposed by the driver),
  root-controlled replacement of the parsed PowerPlay table can also
  trigger it — again, root→kernel.
* All triggers are root-controlled or physical-PCIe-level.  No
  unprivileged-user path exists.  On the default GENERIC kernel
  (INVARIANTS ON), the slab INVARIANTS would catch the cross-object
  overwrite and panic the kernel — realistic impact is **panic/DoS**.

## Exploit chain

Not applicable — same hard blocker as DF-1574.  The primitive is real
(~2.5 KB partly-attacker-shaped heap write into kmalloc-64 bucket) but
its only trigger paths are root-controlled (kldload / VFIO romfile) on
the default kernel, and the guest has no AMD GPU hardware.  Documented
as a latent memory-corruption defect with a verified fix.

## The fix

`fix.diff` — same shape as DF-1574's fix: clamp both `table->count` and
the loop bound to `min(ucNumEntries, max_levels)`.

```diff
-table->count = vddc_lookup_pp_tables->ucNumEntries;
-
-for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++)
+/* ucNumEntries comes from VBIOS (UCHAR, 0..255) but the buffer was
+ * sized for max_levels entries.  Clamp the count and loop bound to
+ * max_levels to prevent an out-of-bounds write past the slab object. */
+if (vddc_lookup_pp_tables->ucNumEntries > max_levels)
+	table->count = max_levels;
+else
+	table->count = vddc_lookup_pp_tables->ucNumEntries;
+
+for (i = 0; i < table->count; i++)
         table->entries[i].us_vdd =
                 le16_to_cpu(vddc_lookup_pp_tables->entries[i].usVdd);
```

## PoC changes

No userspace PoC can exercise this path.  Same situation as DF-1574.

## Reproduce

Not runnable on this guest.  See `fix_build.log` for the standalone
patched-source compile.
