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)
PoC verification
Evidence pack
findings/poc/DF-3703 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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):
- vega10_odn_initial_default_setting
- :310-313 copies
vddc_lookup_table->countrecords into od_lookup_table->entries[8] (count>8 requires the get_vddc_lookup_table anomaly β secondary amplifier). - :322-323smu_get_voltage_dependency_table_ppt_v1(dep_table[i], od_table[i])for sclk/mclk/socclk: smu_helper.c:684-695 copiesallowed_dep_table->countrecords 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/...). - 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). - vega10_odn_update_soc_table :4821-4828 β writes
podn_vdd_dep_on_socclk->entries[i].clk(entries[8]) anddpm_table->dpm_levels[i](entries[8]) for i < dep_table->count (vdd_dep_on_socclk, <=255), and readspodn_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_voltagewrites: '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).
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)
- 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. - 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). - Copies ignore the mismatch:
- smu_helper.c:684-695
smu_get_voltage_dependency_table_ppt_v1loopfor (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). - 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_testablefix.diff authored against read-only sys/ tree; no AMD hw on guest so no patched-kernel validation possible
fix.diff; run.log hardware gate
Confirmed kernel references
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:310
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:322
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:2432
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c:4821
- sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.h:285
- sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:684
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.
No comments yet.