β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1586

Heap buffer overflow in get_vddc_lookup_table: VBIOS ucNumEntries > caller max_levels writes ~2.5KB past slab object

Summary

vega10_processpptables.c:1026-1027 table_size = sizeof(uint32)+sizeof(record)*max_levels (caller passes 4 for vddmem/vddci at 1118/1127). 1034 table->count = ucNumEntries (UCHAR 0..255). 1036-1038 loop i<ucNumEntries writes entries[i].us_vdd. For vddmem max_levels=4: alloc 44B, ucNumEntries=255 -> writes 2510B past slab into kmalloc-64 slab. Trigger: root writes /sys/class/drm/cardN/device/pp_table with crafted usVddmemLookupTableOffset ucNumEntries=255; OR malicious VFIO VBIOS. Sibling of DF-1574 (process_pptables_v1_0). Fix: clamp to min(ucNumEntries, max_levels).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1586 Β· 8 files
FileTypeDescriptionSize
VERDICT.md verdict full narrative: bug, reachability, fix, threat model 4.7 KB ↓ raw
README.md readme summary + reproduce 1.6 KB ↓ raw
fix.diff suggested-fix clamp table->count and loop to min(ucNumEntries, max_levels) 927 B view raw
fix_build.log build-log patched vega10_processpptables.c compiles -Werror clean 1.4 KB view raw
env.txt environment guest uname, pciconf, kldstat β€” no AMD hardware 1.5 KB view raw
manifest.json manifest this catalog 2.7 KB view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme summary + reproduce
↓ download raw

DF-1586 β€” vega10 get_vddc_lookup_table heap OOB write (amdgpu powerplay)

At a glance

Field Value
Finding DF-1586 (sibling of DF-1574)
Severity High
Class CWE-787 Out-of-bounds Write
File sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c:1014-1043
Verdict NOT REPRODUCED ON GUEST (real bug, unreachable runtime path)
Impact none (on this guest); latent memory-corruption
Confidence certain (source-traced line-by-line)

The bug, in one sentence

vega10's get_vddc_lookup_table() allocates 4 + 10*max_levels bytes (callers pass 4 or 8) but loops ucNumEntries times walking 10 bytes per iteration; with a hostile VBIOS ucNumEntries=255 it writes ~2.5 KB past the kmalloc-64 slab object.

Reproduce on this guest

Cannot run. Part of the amdgpu KLD module β€” not in X86_64_GENERIC, not loaded, no AMD GPU present. See VERDICT.md for full reachability analysis and fix_build.log for the standalone patched-source compile.

Apply the fix

cd /usr/src
patch -p1 < findings/poc/DF-1586/fix.diff
# In sys/dev/drm/amd/amdgpu:
make   # rebuild amdgpu.ko

Files in this folder

File Purpose
VERDICT.md Full analysis: bug, reachability, fix, threat model
fix.diff git-apply-able unified diff (clamps count + loop)
fix_build.log Translation-unit compile log (patched source, -Werror clean)
env.txt Guest environment + PCI/module state
manifest.json Machine-readable catalog
VERDICT.md verdict full narrative: bug, reachability, fix, threat model
↓ download raw

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

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

Fix verification

not_testable

compile validated -Werror

module/translation-unit build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. vega10 get_vddc_lookup_table alloc by max_levels=4/8 but loop by ucNumEntries -> ~2.5KB OOB. amdgpu not in GENERIC, no AMD GPU. Sibling of DF-1574.