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

vega20_processpptables: VBIOS powerplay table size not validated against structure footprint -> OOB read

Field Value
ID DF-1689
File sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c
Lines 636, 643, 644, 804, 815, 857, 880, 884, 886
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L
CWE CWE-125 Out-of-bounds Read
Confidence likely
Status new
CVE match variant (VBIOS PowerPlay parser count/offset family β€” DF-1468/1574/1586)
Created 2026-07-18

Summary

check_powerplay_tables() only asserts sHeader.structuresize > 0 and never validates that the VBIOS-reported size covers the full ATOM_Vega20_POWERPLAYTABLE layout (including the multi-KB inline PPTable_t smcPPTable).

The parser then unconditionally dereferences fields far past the reported size β€” most importantly memcpy-ing sizeof(PPTable_t) bytes from &powerplay_table->smcPPTable at line 884. A malicious/corrupted VBIOS that positions the powerplayinfo data table near the end of the BIOS heap allocation (or simply reports a small structuresize) causes the parser to read past the end of adev->bios (allocated via kmalloc in amdgpu_bios.c).

This is the same defect class as the sibling parsers DF-1468 (processpptables.c), DF-1574 (process_pptables_v1_0.c), and DF-1586 (vega10_processpptables.c); vega20 confirms the pattern persists.

Root cause

get_powerplay_table() (lines 44-62) calls smu_atom_get_data_table() which returns (uint8_t *)atom_context->bios + data_start (smu_helper.c:669-670) where data_start is a 16-bit offset pulled verbatim from the VBIOS data_table by amdgpu_atom_parse_data_header (atom.c:1384-1403) with NO validation against bios_size. The reported size is stored into hwmgr->soft_pp_table_size but never compared to sizeof(ATOM_Vega20_POWERPLAYTABLE).

check_powerplay_tables() (lines 636-657) only checks:

  • format_revision >= 11
  • structuresize > 0
  • smcPPTable.Version == PPTABLE_V20_SMU_VERSION

β€” all of which can be satisfied by a 16-byte table.

init_powerplay_table_information() then reads fields at fixed offsets throughout the entire ATOM_Vega20_POWERPLAYTABLE layout: hwmgr->thermal_controller.fanInfo.ulMaxRPM = powerplay_table->smcPPTable.FanMaximumRpm (line 815), the OverDrive8Table.ODSettingsMax[] access (line 857), and crucially the memcpy(pptable_information->smc_pptable, &(powerplay_table->smcPPTable), sizeof(PPTable_t)) at lines 884-886 which copies a multi-KB inline structure whose offset+size routinely exceeds any small structuresize.

Since ATOM_Vega20_POWERPLAYTABLE embeds PPTable_t smcPPTable inline (vega20_pptable.h:136) rather than via an offset, the parser cannot avoid reading the full structure footprint regardless of the VBIOS-reported size.

Threat model

Attacker position: anyone who controls the GPU's VBIOS image β€” a malicious card in the supply chain, a re-flashed EEPROM (some cards expose VBIOS flash via userspace tools / MMIO), or a virtualization setup passing through a crafted PCI option ROM.

Precondition: amdgpu driver loads and calls vega20_pp_tables_initialize() (line 894) via vega20_hwmgr_init path.

Impact:

  1. kernel heap OOB read past the adev->bios kmalloc allocation when data_start + sizeof(ATOM_Vega20_POWERPLAYTABLE) exceeds bios_size; the memcpy at line 884 is the cleanest OOB primitive since it copies a known large size
  2. The OOB-read bytes (adjacent kernel heap contents) are then exposed to userspace indirectly via sysfs attributes derived from pptable_information (clock limits, OD limits, TDPODLimit at line 857) and are also sent to the SMU microcontroller as configuration, which can cause GPU hardware misbehavior (over-volt/over-clock)

No direct path to kernel code execution was demonstrated from this file alone β€” impact is bounded info leak of adjacent heap + hardware-config corruption.

PoC

PoC is a VBIOS image patch, not a userspace program, because the attack surface is firmware-supplied data consumed at driver init. Steps:

  1. Obtain a Vega20 VBIOS image (e.g. dump from a Radeon VII / MI50 via amdgpu.gpu_recovery=0 + MMIO strap, or extract the PCI option ROM with pci-tools).
  2. Locate the ATOM master data_table; find the index for powerplayinfo (via GetIndexIntoMasterDataTable(powerplayinfo) β€” same lookup the driver uses at line 46).
  3. Patch that data table entry's data_start to a 16-bit value near bios_size (e.g. bios_size - 32), OR set the structuresize field at the data_start offset to 1, while keeping format_revision=11 (ATOM_VEGA20_TABLE_REVISION_VEGA20), content_revision to pass, and the byte at smcPPTable.Version offset = PPTABLE_V20_SMU_VERSION (so check_powerplay_tables lines 640-652 still pass).
  4. Reprogram the SPI EEPROM with the modified image, OR feed it to a VM via pci-passthrough of an emulated option ROM.
  5. Trigger driver (re-)bind: echo > /sys/bus/pci/drivers/amdgpu/bind or boot.

Expected outcome: amdgpu calls vega20_pp_tables_initialize β†’ init_powerplay_table_information β†’ the memcpy at line 884 reads sizeof(PPTable_t) (~2 KB) bytes starting near the end of the BIOS allocation, going past bios_size into adjacent kernel heap.

A KASAN-enabled kernel reports a slab-out-of-bounds read; on a production kernel the read silently returns adjacent heap bytes that then appear in sysfs (e.g. /sys/class/drm/card0/device/... pp_dpm_sclk / OD settings) and are pushed to the SMU.

findings/poc/DF-1689/: drop the patched VBIOS as vega20_poisoned_vbios.bin plus a Python script patch_vbios.py that takes a stock Vega20 VBIOS dump and writes the patched image with the data_start/structuresize fields set as described; include the binding trigger script trigger.sh.

Validate that the VBIOS-reported size actually covers the full ATOM_Vega20_POWERPLAYTABLE structure before any field is read, and additionally guard against data_start running past bios_size.

Minimal in-file fix:

--- a/sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c
@@ -636,11 +636,18 @@ static int check_powerplay_tables(
        struct pp_hwmgr *hwmgr,
        const ATOM_Vega20_POWERPLAYTABLE *powerplay_table)
 {
+   u16 struct_size = le16_to_cpu(powerplay_table->sHeader.structuresize);
+
    PP_ASSERT_WITH_CODE((powerplay_table->sHeader.format_revision >=
        ATOM_VEGA20_TABLE_REVISION_VEGA20),
        "Unsupported PPTable format!", return -1);
    PP_ASSERT_WITH_CODE(powerplay_table->sHeader.structuresize > 0,
        "Invalid PowerPlay Table!", return -1);
+   PP_ASSERT_WITH_CODE(struct_size >= sizeof(ATOM_Vega20_POWERPLAYTABLE),
+       "PowerPlay Table size too small for vega20 layout!",
+       return -EINVAL);
+   PP_ASSERT_WITH_CODE(hwmgr->soft_pp_table_size == 0 ||
+           hwmgr->soft_pp_table_size >= sizeof(ATOM_Vega20_POWERPLAYTABLE),
+       "PowerPlay Table allocation too small!", return -EINVAL);

    if (powerplay_table->smcPPTable.Version != PPTABLE_V20_SMU_VERSION) {
        pr_info("Unmatch PPTable version: "

Defense-in-depth: amdgpu_atom_parse_data_header() in sys/dev/drm/amd/amdgpu/atom.c:1384 should also reject data_start values where data_start + size > ctx->bios_size, but that fix belongs to atom.c, not this file.

VBIOS PowerPlay parser count/offset family:

  • DF-1468 (processpptables.c)
  • DF-1574 (process_pptables_v1_0.c)
  • DF-1586 (vega10_processpptables.c)
  • DF-1560 (amd_powerplay.c)
  • DF-1674 (amdgpu_dpm.c)
  • DF-1690 (smc_dpm_info table β€” same file, separate OOB)

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1689 Β· 4 files
FileTypeDescriptionSize
VERDICT.md verdict source-only confirmation + mechanism + fix 1.7 KB ↓ raw
fix.diff suggested-fix Add 'structuresize >= sizeof(ATOM_Vega20_POWERPLAYTABLE)' assertion. 714 B 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
VERDICT.md verdict source-only confirmation + mechanism + fix
↓ download raw

DF-1689 β€” PoC Verification Verdict

Category: amdgpu powerplay (module, HW-gated) Source: sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:636-657 Guest: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (X86_64_GENERIC, INVARIANTS ON, no SMAP/SMEP/KASLR) Date verified: 2026-07-21

Verdict: REPRODUCED (source-only confirmation; HW/module-gated)

Mechanism

check_powerplay_tables only asserts structuresize>0 (643) and smcPPTable.Version==PPTABLE_V20_SMU_VERSION (646). NO check that structuresize covers the full ATOM_Vega20_POWERPLAYTABLE layout (which includes multi-KB inline PPTable_t smcPPTable). A VBIOS with a tiny structuresize but matching version passes validation; subsequent field accesses read OOB.

In GENERIC kernel build: NO (module / not compiled into X86_64_GENERIC)

Reproduction status

This finding is hardware/module gated: the vulnerable code path requires specific hardware (AMD GPU / radeon / Atheros NIC / RAID controller / AGP chipset) or a loadable module not present on the audit QEMU guest. The QEMU guest has no GPU passthrough, no physical NIC/RAID HW, and these modules are not in the GENERIC kernel. The bug is therefore confirmed by source-level trace of the cited path:line data flow rather than by a runtime PoC. The cited code, guards (or lack thereof), and types were verified against the audited sys/ tree.

Fix

Add 'structuresize >= sizeof(ATOM_Vega20_POWERPLAYTABLE)' assertion.

See fix.diff for the standalone git-apply-able unified diff. Validated by applying all 35 batch diffs and building a single X86_64_GENERIC kernel (rc=0, -Werror clean) β€” see fix_apply.log and the combined build log.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.

VALIDATED: fix.diff applies cleanly + batch kernel build rc=0 -Werror; bug HW/module/runtime-gated, no runtime PoC re-test possible on guest.
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

REPRODUCED (source-only): check_powerplay_tables only asserts structuresize>0 and version match; NO check that structuresize covers full ATOM_Vega20_POWERPLAYTABLE layout; multi-KB images used unbound

Verified recommended fix

REPRODUCED (source-only): check_powerplay_tables only asserts structuresize>0 and version match; NO check that structuresize covers full ATOM_Vega20_POWERPLAYTABLE layout; multi-KB images used unbounded.

Verdict

REPRODUCED (source-only): check_powerplay_tables only asserts structuresize>0 and version match; NO check that structuresize covers full ATOM_Vega20_POWERPLAYTABLE layout; multi-KB images used unbounded.