β¬’ 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 732 B ↓ raw
build.sh build-script N/A (source-only) 61 B view raw
run.sh run-script N/A (source-only) 87 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 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)