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

NULL pointer dereference of tdp_table from VBIOS PowerPlay table missing usPowerTuneTableOffset

Summary

vega10_initialize_power_tune_defaults() and vega10_enable_power_containment() unconditionally dereference table_info->tdp_table, but the pointer is only populated by the VBIOS parser when powerplay_table->usPowerTuneTableOffset is non-zero.

A Vega10 VBIOS PowerPlay table that passes check_powerplay_tables() yet has usPowerTuneTableOffset == 0 leaves tdp_table == NULL, triggering a kernel page-fault panic in amdgpu attach (boot or hotplug).

Root cause

vega10_initialize_power_tune_defaults() (sys/dev/drm/amd/powerplay/hwmgr/vega10_powertune.c:1289-1326) fetches

struct phm_tdp_table *tdp_table = table_info->tdp_table;

(line 1294) and immediately dereferences it at line 1297 (tdp_table->usMaximumPowerDeliveryLimit) with no NULL guard.

The same pattern repeats in vega10_enable_power_containment() at lines 1344 (struct phm_tdp_table *tdp_table = table_info->tdp_table;) and 1348 (tdp_table->usMaximumPowerDeliveryLimit).

The pointer originates in sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c:921-924:

if (!result && powerplay_table->usPowerTuneTableOffset)
    result = get_tdp_table(hwmgr, &pp_table_info->tdp_table, power_tune_table);

The parent struct phm_ppt_v2_information is kzalloc'd at vega10_processpptables.c:1139 so tdp_table defaults to NULL; the conditional means it stays NULL when usPowerTuneTableOffset == 0.

check_powerplay_tables() (vega10_processpptables.c:66-86) validates only sHeader.format_revision, usStateArrayOffset, sHeader.structuresize, and state_arrays->ucNumEntries β€” never usPowerTuneTableOffset.

init_dpm_2_parameters() therefore returns 0 (success), vega10_pp_tables_initialize() returns 0, and hwmgr_hw_init() proceeds to call hwmgr_func->backend_init (sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:229), which at vega10_hwmgr.c:864 calls vega10_init_dpm_defaults β†’ vega10_initialize_power_tune_defaults β†’ NULL deref.

The first faulting instruction is the le16-to-cpu read at vega10_powertune.c:1297.

Threat

Attacker must control the Vega10-class GPU VBIOS image (Radeon RX Vega 56/64, Vega Frontier Edition, MI25 Instinct).

Realistic vectors:

  • (a) PCI passthrough / VFIO in a cloud or local-VM setup where the host or guest kernel parses a romfile supplied by the attacker;
  • (b) a supply-chain-modified or re-flashed physical card (flashrom -w requires root, but no kernel priv is needed to trigger the panic once the modified ROM is presented);
  • (c) a separate write-privilege exploit used first to reflash the ROM.

No code execution on the target is required beyond presenting the tampered VBIOS β€” the panic fires in amdgpu attach at boot or hotplug.

Impact is local kernel panic / denial of service (system hang or spontaneous reboot).

No memory-safety primitive beyond the NULL-page fault is exposed (page 0 is unmapped on DragonFlyBSD/amd64), so no escalation path was identified.

Exploit / PoC

Reproduce on a host with a Vega10 GPU, or under QEMU+vfio-pci with a romfile.

  1. Dump a known-good Vega10 VBIOS:

cd /sys/bus/pci/devices/<bdf> echo 1 > rom cat rom > /tmp/vbios.rom echo 0 > rom

  1. Locate the ATOM_Vega10_POWERPLAYTABLE inside the ROM (PowerPlay signature 'ATPPL'). Within it, zero out the 16-bit little-endian field usPowerTuneTableOffset. Leave usStateArrayOffset, sHeader.format_revision (>= ATOM_Vega10_TABLE_REVISION_VEGA10), sHeader.structuresize (>0), and the state-array ucNumEntries (>0) intact so check_powerplay_tables still returns 0.

  2. For VFIO: place the patched ROM at /etc/qemu/vga-vega10.rom and add romfile=/etc/qemu/vga-vega10.rom to the host -device vfio-pci,host=... line, then boot a DragonFlyBSD guest that loads amdgpu.

For bare metal: flashrom -w vbios_patched.rom and reboot.

  1. Success criterion: kernel panics during amdgpu attach with a NULL-page fault trace pinning vega10_initialize_power_tune_defaults (line 1297) or, if backend_init somehow skips init_dpm_defaults, vega10_enable_power_containment (line 1348).

Capture dmesg -a and the panic backtrace as evidence.

Defensive guard at the consumer site (preferred minimal local fix), plus a parser-level guard so other consumers of tdp_table are protected too.

Local fix in sys/dev/drm/amd/powerplay/hwmgr/vega10_powertune.c:

--- a/sys/dev/drm/amd/powerplay/hwmgr/vega10_powertune.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/vega10_powertune.c
@@ -1291,9 +1291,15 @@ void vega10_initialize_power_tune_defaults(struct pp_hwmgr *hwmgr)
    struct phm_ppt_v2_information *table_info =
            (struct phm_ppt_v2_information *)(hwmgr->pptable);
    struct phm_tdp_table *tdp_table = table_info->tdp_table;
    PPTable_t *table = &(data->smc_state_table.pp_table);

+   if (tdp_table == NULL) {
+       pr_err("amdgpu: %s: missing PowerTune table "
+              "(usPowerTuneTableOffset == 0 in VBIOS)\n", __func__);
+       return;
+   }
+
    table->SocketPowerLimit = cpu_to_le16(
            tdp_table->usMaximumPowerDeliveryLimit);
@@ -1342,6 +1348,12 @@ int vega10_enable_power_containment(struct pp_hwmgr *hwmgr)
    struct phm_tdp_table *tdp_table = table_info->tdp_table;
    int result = 0;

+   if (tdp_table == NULL) {
+       pr_err("amdgpu: %s: missing PowerTune table "
+              "(usPowerTuneTableOffset == 0 in VBIOS)\n", __func__);
+       return -EINVAL;
+   }
+
    hwmgr->default_power_limit = hwmgr->power_limit =
            (uint32_t)(tdp_table->usMaximumPowerDeliveryLimit);

Root-cause fix in sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c β€” reject VBIOS images that omit the mandatory PowerTune table up front so every consumer is protected:

--- a/sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/vega10_processpptables.c
@@ -78,6 +78,9 @@ static int check_powerplay_tables(
    PP_ASSERT_WITH_CODE(powerplay_table->usStateArrayOffset,
        "State table is not set!", return -1);
+   PP_ASSERT_WITH_CODE(powerplay_table->usPowerTuneTableOffset,
+       "PowerTune table offset is not set!", return -1);
    PP_ASSERT_WITH_CODE(powerplay_table->sHeader.structuresize > 0,
        "Invalid PowerPlay Table!", return -1);
  • DF-1471 (sibling, processpptables.c): NULL deref DoS in init_overdrive_limits and cac_dtp_table allocation path β€” same VBIOS-omits-mandatory-table family.
  • DF-1468/DF-1469/DF-1470 (siblings, processpptables.c): unbounded indices / counts in the same VBIOS PowerPlay parser family.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1550 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited bug 548 B view raw
VERDICT.md verdict source-confirmation analysis 1.1 KB ↓ raw
build.sh build-script N/A (source-only) 65 B view raw
run.sh run-script N/A (source-only) 89 B view raw
VERDICT.md verdict source-confirmation analysis
↓ download raw

DF-1550 VERDICT

Verdict: REPRODUCED (source-confirmed)

Impact: Low (driver-level NULL deref / OOB / leak / DoS β€” hardware-gated)

Mechanism: vega10_powertune.c:1294 vega10_initialize_power_tune_defaults fetches tdp_table=table_info->tdp_table then derefs at 1297 (tdp_table->usMaximumPowerDeliveryLimit). Same at 1344 vega10_enable_power_con

Citation: sys/dev/drm/amd/powerplay/hwmgr/vega10_powertune.c:1294-1348

Fix: Applied fix.diff β€” compiles in batch kernel build (rc=0, -Werror).

Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff compiled in batch kernel build rc=0 -Werror

fix.diff compiled in batch kernel build rc=0 -Werror
↓ fix.diffcombined build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none (Low severity)

Evidence (decisive lines)

Source-confirmed: tdp_table NULL deref when VBIOS omits PowerTuneTableOffset (vega10_powertune.c:1294-1297)

Verified recommended fix

Source-confirmed: tdp_table NULL deref when VBIOS omits PowerTuneTableOffset (vega10_powertune.c:1294-1297)

Verdict

Source-confirmed: tdp_table NULL deref when VBIOS omits PowerTuneTableOffset (vega10_powertune.c:1294-1297)