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

vega10 ODN tables are fixed entries[8] but filled/iterated with unbounded VBIOS dep-table counts β€” heap overflow of struct vega10_hwmgr

Summary

odn_dpm_table subtables are fixed entries[MAX_REGULAR_DPM_NUMBER=8] embedded in the kzalloc'd struct vega10_hwmgr, but: vega10_odn_initial_default_setting copies vddc_lookup_table->count records into entries[8]; smu_get_voltage_dependency_table_ppt_v1 copies sclk/mclk/socclk dep records (counts raw ucNumEntries<=255) into entries[8]; vega10_check_dpm_table_updated reads odn entries[i] for i < dep count; vega10_odn_update_soc_table writes podn_vdd_dep_on_socclk->entries[i] and dpm_table->dpm_levels[i] for i < socclk dep count. Up to ~247x44-byte attacker-record writes run through live driver state and past the allocation. Crafted-VBIOS Vega10 with OD enabled at driver load, or root sysfs pp_od_clock_voltage read/restore: attacker-controlled heap corruption (clk/voltage/indices per record).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3703 Β· 10 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
trigger_notes.c β€” 1.1 KB view raw
build.sh β€” 81 B view raw
run.sh β€” 169 B view raw
run.log β€” 1.3 KB view raw
env.txt β€” 848 B view raw
fix.diff β€” 2.3 KB view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.8 KB view raw

DF-3703 β€” vega10 ODN tables are fixed entries[8] but copied/iterated with unbounded VBIOS dep-table counts (heap overflow)

File: sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c (pass 2).

Fixed-size destinations (vega10_hwmgr.h:285-290, MAX_REGULAR_DPM_NUMBER=8):

struct vega10_odn_clock_voltage_dependency_table { count; entries[8]; }
struct vega10_odn_vddc_lookup_table            { count; entries[8]; }

embedded in struct vega10_odn_dpm_table odn_dpm_table inside the kzalloc'd struct vega10_hwmgr backend (vega10_hwmgr.h:314+), followed by odn_fan_table, flags and the SMC state tables β€” so overflow runs forward through live driver state and, with count up to 255 (records are ~44 bytes), past the end of the allocation.

Sinks (all bounded only by attacker-controlled VBIOS counts):

  1. vega10_odn_initial_default_setting - :310-313 copies vddc_lookup_table->count records into od_lookup_table->entries[8] (count>8 requires the get_vddc_lookup_table anomaly β€” secondary amplifier). - :322-323 smu_get_voltage_dependency_table_ppt_v1(dep_table[i], od_table[i]) for sclk/mclk/socclk: smu_helper.c:684-695 copies allowed_dep_table->count records into the fixed entries[8] with no bound. The dep counts are standalone unbounded: they are raw ATOM ucNumEntries (uint8 <= 255, vega10_processpptables.c:540/:574/...) with allocations sized to match, so counts > 8 arrive intact. Attacker controls every copied field (clk/vddc/vddInd/...).
  2. vega10_check_dpm_table_updated :2432-2437 / :2441-2446 β€” reads odn_dep_table->entries[i] for i < dep_table->count -> heap OOB read (255 vs 8).
  3. vega10_odn_update_soc_table :4821-4828 β€” writes podn_vdd_dep_on_socclk->entries[i].clk (entries[8]) and dpm_table->dpm_levels[i] (entries[8]) for i < dep_table->count (vdd_dep_on_socclk, <=255), and reads podn_vdd_dep->entries[dep_table->count-1] cross-table; :4829-4839 more of the same.

Reachability

  • init: vega10_init_smc_table :2478-2484 calls vega10_odn_initial_default_setting when hwmgr->od_enabled && !odn_table->max_vddc (OD enabled in VBIOS);
  • root sysfs pp_od_clock_voltage writes: 'r' (PP_OD_RESTORE_DEFAULT_TABLE, :4874) re-runs the copy; 'c' (COMMIT, :4877) runs vega10_check_dpm_table_updated; OD edits run vega10_odn_update_soc_table (:4900).

Trigger (requires AMD Vega10 hardware)

VBIOS powerplay table with ucNumEntries>8 in GFXCLK/MCLK/SOCCLK dependency tables (+ OD enabled), then driver load β€” or root echo r > pp_od_clock_voltage on such a machine.

PoC status

Guest virtio-only β€” status inconclusive, source-certain (see VERDICT.md).

VERDICT.md
↓ download raw

DF-3703 VERDICT β€” ODN entries[8] overflows from unbounded dep-table counts

status: inconclusive (hardware-gated) β€” source-certain. reproduced=0 on audit guest.

Proof chain (lines verified in guest /usr/src, see run.log)

  1. Destination capacity is hard-fixed at 8: vega10_hwmgr.h:285/:290 entries[MAX_REGULAR_DPM_NUMBER], MAX_REGULAR_DPM_NUMBER=8 (vega10_hwmgr.h:122); odn_dpm_table embedded in struct vega10_hwmgr (:314+) β€” one kzalloc.
  2. Source counts are untrusted and unbounded: vega10_processpptables.c get_{socclk,mclk,gfxclk}_voltage_dependency_table set count = (uint32_t)ucNumEntries (uint8 <= 255) with matching allocations (no SMC-level cap).
  3. Copies ignore the mismatch: - smu_helper.c:684-695 smu_get_voltage_dependency_table_ppt_v1 loop for (i=0;i<dep_table->count;i++) writes dep_table->entries[i] β€” invoked from vega10_hwmgr.c:322-323 with od_table[i] = fixed entries[8] destinations. count=255 => ~247 x 44-byte records written over odn_dpm_table's siblings and beyond within/past the vega10_hwmgr allocation. - vega10_hwmgr.c:310-311 (vddc lookup copy, amplifier when lookup count>8), :2432/:2441 (OOB reads in check_dpm_table_updated), :4821-4828 (OOB writes in odn_update_soc_table: podn_vdd_dep_on_socclk->entries[i] and dpm_table->dpm_levels[i] for i < socclk dep count).
  4. Reachability: vega10_init_smc_table:2478-2484 (od_enabled at init) and root sysfs pp_od_clock_voltage 'r'/:4874, 'c'/:4877, edits/:4900 (amdgpu_pm.c:543-602 parser feeds long parameter[] straight into odn_edit_dpm_table).

Impact: kernel heap overflow with attacker-controlled record contents (clk in 10kHz units, voltage, indices) => corruption of adjacent vega10_hwmgr state and heap neighbors; on a hardened box a crash, on default deployments a potential privesc primitive (no KASLR/SMEP/SMAP by default). Severity High for the crafted-VBIOS threat model.

Guest result

0 AMD PCI devices; amdgpu.ko never binds; the path cannot execute. Cited lines confirmed in guest /usr/src (run.log). Verdict: inconclusive, source-certain.

Fix

fix.diff bounds every copy/read by the destination capacity (min(count, MAX_REGULAR_DPM_NUMBER)), and caps vega10_odn_update_soc_table's soc loop at MAX_REGULAR_DPM_NUMBER.

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

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored against read-only sys/ tree; no AMD hw on guest so no patched-kernel validation possible

fix.diff; run.log hardware gate
↓ fix.diffper-fix-DF-3703

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS dep table ucNumEntries>8 + OD enabled -> driver load or root 'echo r > pp_od_clock_voltage' -> entries[8] overflow with attacker-controlled records -> corrupt adjacent vega10_hwmgr state/heap -> crash or privesc primitive on unhardened configs (chain hardware-gated, not demonstrated)

Evidence (decisive lines)

VERDICT.md proof chain; run.log (guest-src excerpts :310-323, entries[8] defs, :2432, :4821-4826); trigger_notes.c; fix.diff

PoC changes

no runnable PoC possible (hardware-gated); documented VBIOS + sysfs triggers; verified cited lines in guest kernel source

Verified recommended fix

Bound all odn copies/loops by MAX_REGULAR_DPM_NUMBER (min with source count) β€” see fix.diff

Verdict

Source-certain kernel heap overflow: vega10 ODN tables are fixed entries[MAX_REGULAR_DPM_NUMBER=8] (vega10_hwmgr.h:285,290) embedded in the kzalloc'd struct vega10_hwmgr, but vega10_odn_initial_default_setting (:310-313, :322-323 via smu_get_voltage_dependency_table_ppt_v1 smu_helper.c:684-695), vega10_check_dpm_table_updated (:2432/:2441) and vega10_odn_update_soc_table (:4821-4828) bound their loops by VBIOS dep-table ucNumEntries (<=255, uncapped by the parser) β€” up to ~247 x 44-byte attacker-record writes/reads past entries[8] into live driver state and heap neighbors. Reachable at init (:2478-2484, od_enabled) and via root sysfs pp_od_clock_voltage 'r'/'c' (:4874/:4877). Not reproducible on the virtio-only audit guest (0 AMD PCI devices); cited lines verified in guest /usr/src.