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

Heap OOB writes into kzalloc'd struct ci_smumgr via unclamped VBIOS MCLK DPM count vs SMU7_MAX_LEVELS_MEMORY=6: MemoryLevel[6..7], unguarded MemoryLevel[count-1], mc_regs->data[6..7], mclk_levels[MemoryDpmLevelCount] (DF-3909 twin)

Summary

The VBIOS-controlled mclk DPM count (up to 8, unclamped - same provenance) indexes arrays dimensioned 6 for CI: full ~40B MemoryLevel structs written past MemoryLevel[6] clobbering LinkLevel; the unguarded count-1 DisplayWatermark store; 64B MCRegisterSet structs written past mc_regs->data[6] into the trailing ci_mc_reg_table of the kzalloc'd ci_smumgr; and the runtime profile/OD path reads/writes mclk_levels[i] up to the unclamped count. In-object heap corruption with VBIOS-chosen contents, then byte-copied into SMC - kernel heap corruption primitive + firmware confusion. Clamp-at-sinks fix; root fix (trim MCLK in smu7_setup_dpm_tables) flagged for smu7_hwmgr.c.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3940 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.5 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
fix.diff β€” 2.3 KB view raw
env.txt β€” 1004 B view raw
manifest.json β€” 1.2 KB view raw
verdict.json β€” 3.7 KB view raw

DF-3940 β€” Heap OOB writes into kzalloc'd struct ci_smumgr via unclamped VBIOS MCLK DPM count (SMU7_MAX_LEVELS_MEMORY=6 vs counts up to 8)

Finding (tonga DF-3909 twin, CI)

Unclamped data->dpm_table.mclk_table.count (VBIOS-controlled, up to MAX_REGULAR_DPM_NUMBER=8) is used as the write index into arrays dimensioned SMU7_MAX_LEVELS_MEMORY = SMU__NUM_MCLK_DPM_LEVELS = 6 for CI (sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.h:27):

  1. sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c:1314-1321 β€” ci_populate_all_memory_levels: ci_populate_single_memory_level(..., &MemoryLevel[i]) writes full SMU7_Discrete_MemoryLevel (~40B each, smu7_discrete.h:168-207) structs at MemoryLevel[6]/MemoryLevel[7] when count ∈ {7,8} β€” past MemoryLevel[SMU7_MAX_LEVELS_MEMORY] (smu7_discrete.h:325), clobbering the adjacent LinkLevel[0..2] inside smc_state_table of the kzalloc'd struct ci_smumgr (ci_smumgr.h:69-78, alloc at ci_smumgr.c:2731-2743).
  2. ci_smumgr.c:1339 β€” MemoryLevel[dpm_table->mclk_table.count-1].DisplayWatermark = ... β€” the unguarded [count-1] store (tonga DF-3909 called this out too): for count 7/8 writes MemoryLevel[6]/[7] OOB (count==0 is impossible: smu7_setup_dpm_tables_v0/v1 assert dep count >= 1, smu7_hwmgr.c:683-685/779-785).
  3. ci_smumgr.c:1794-1798 β€” ci_convert_mc_reg_table_to_smc: ci_convert_mc_reg_table_entry_to_smc(..., &mc_regs->data[i]) for i < mclk count, but SMU7_Discrete_MCRegisters.data[] is sized SMU7_DISCRETE_MC_REGISTER_ARRAY_SET_COUNT = SMU7_MAX_LEVELS_MEMORY = 6 (smu7_discrete.h:408) β€” data[6]/[7] are 64-byte SMU7_Discrete_MCRegisterSet writes (value[16] u32s, VBIOS MC register data) past the array into the trailing struct ci_mc_reg_table member of the kzalloc'd ci_smumgr (in-object, tail of allocation). Also ci_update_and_upload_mc_reg_table (:1828-1831) then DMAs sizeof(Set)*count bytes to the SMC.
  4. ci_smumgr.c:2819-2846 β€” ci_update_dpm_settings (runtime profile/OD path): for (i = 0; i < smc_state_table.MemoryDpmLevelCount; i++) mclk_levels[i].ActivityLevel/UpH/DownH read/write β€” MemoryDpmLevelCount was set to the unclamped count at :1337, so counts 7/8 give further OOB 2-byte/1-byte writes past MemoryLevel[6].
  • processpptables.c:391: dep table count = VBIOS ucNumEntries (u8), table entries dynamically allocated (hwmgr.h:120-123).
  • smu7_hwmgr.c:704-710 (smu7_setup_dpm_tables_v0, selected for CI via hwmgr.c:92-99 PP_TABLE_V0): dedups into dpm_levels[8] β€” counts 7-8 are fully in-bounds there. No min(count, SMU7_MAX_LEVELS_MEMORY) anywhere.
  • The MCLK family is the only level family without an upstream trim: VDDC/VDDCI/MVDD voltage tables are trimmed at smu7_hwmgr.c:324-346, PCIE capped at :557-569. SCLK needs no trim by luck (8 == GraphicsLevel[8]).

Trigger

Crafted VBIOS with vdd_dep_on_mclk = 7-8 distinct MCLK entries on a Bonaire/Hawaii board; amdgpu DPM enable β†’ ci_init_smc_table β†’ ci_populate_all_memory_levels (ci_smumgr.c:1980), ci_populate_initial_mc_reg_table (:2110), and later OD/profile updates β†’ ci_update_dpm_settings.

Why inconclusive on this guest

No AMD GPU present (env.txt β€” zero vendor-0x1002 PCI functions on the audit guest). Source math is exact; this is the CI twin of verified tonga finding DF-3909 (heap OOB into GraphicsLevel[8]/MemoryLevel[4] via unclamped counts).

VERDICT.md
↓ download raw

DF-3940 β€” VERDICT

Status: inconclusive (hardware-gated) β€” source-level certainty: certain. Impact class: kernel heap out-of-bounds write (CWE-787), crafted-VBIOS vector.

What overflows, exactly

struct ci_smumgr is kzalloc'd at ci_smumgr.c:2731-2743; it embeds SMU7_Discrete_DpmTable smc_state_table (with MemoryLevel[6] at smu7_discrete.h:325) and, near its tail, SMU7_Discrete_MCRegisters mc_regs (with data[6] since SMU7_DISCRETE_MC_REGISTER_ARRAY_SET_COUNT == SMU7_MAX_LEVELS_MEMORY, smu7_discrete.h:408). For CI, SMU7_MAX_LEVELS_MEMORY == SMU__NUM_MCLK_DPM_LEVELS == 6 (ci_smumgr.h:27) while the hwmgr-side DPM machinery holds up to MAX_REGULAR_DPM_NUMBER == 8 levels (smu7_hwmgr.h:95,100).

Sinks (all keyed on dpm_table->mclk_table.count, never clamped):

# site write OOB when count
1 ci_smumgr.c:1314-1318 full SMU7_Discrete_MemoryLevel (~40B, ~30 VBIOS-derived fields) per level via ci_populate_single_memory_level 7 or 8
2 ci_smumgr.c:1339 [count-1].DisplayWatermark u8 7 or 8
3 ci_smumgr.c:1794-1798 SMU7_Discrete_MCRegisterSet (16 x u32 VBIOS MC data) per level into mc_regs->data[i] 7 or 8
4 ci_smumgr.c:2819-2846 ActivityLevel (u16), UpH/DownH (u8) via MemoryDpmLevelCount (set at :1337) 7 or 8

Writes stay inside the ci_smumgr allocation for sinks 1-2 (they land on LinkLevel[0..2]) and reach the trailing ci_mc_reg_table for sink 3 β€” in-object corruption of the heap object that is then byte-copied wholesale into SMC RAM (ci_copy_bytes_to_smc, bounded to SMC_RAM_END, so no kernel side-effect there). This is precisely the DF-3909 (tonga) result shape: heap object corruption with attacker-influenced contents, gateway to confused SMC firmware state and, with the right heap layout, past-object corruption is not reachable from these sinks alone (struct is large and the sinks are mid/tail members; sink 3's data[7] ends 64B into ci_mc_reg_table).

Count provenance (path:line)

  • processpptables.c:391 β€” count = VBIOS ucNumEntries (u8, ≀255).
  • hwmgr.h:120-123 β€” dep table entries[1] dynamic alloc β†’ any count fits the source table.
  • smu7_hwmgr.c:704-710 β€” v0 setup (CI selects PP_TABLE_V0 at hwmgr.c:92-99) dedups into dpm_levels[8]; count 7-8 in-bounds; no trim to 6.
  • Contrast (proves the omission is the bug, not policy):
  • voltage tables trimmed: smu7_hwmgr.c:324-346 (phm_trim_voltage_table_to_fit_state_table + smum_get_mac_definition(SMU_MAX_LEVELS_{VDDC,VDDGFX,VDDCI,MVDD})) β€” hence VddcLevel[8]/VddciLevel[4]/MvddLevel[4] writes at ci_smumgr.c:839-922 are SAFE (negative result).
  • PCIE table capped: smu7_hwmgr.c:557-569 (max_entry = min(SMU7_MAX_LEVELS_LINK, count), count = max_entry - 1 ≀ 7) β€” hence the i <= count loop at ci_smumgr.c:1003 writing LinkLevel[count] stays ≀ LinkLevel[7], in-bounds (negative result β€” looks like an off-by-one, is not reachable for CI).

Why not reproduced

No AMD GPU on the audit guest (env.txt). Requires Bonaire/Hawaii hardware plus a modified VBIOS image. Classified inconclusive per batch convention (DF-3934/DF-3935, tonga DF-3908..3910). No uid=0 chain possible without the device; the write contents are largely VBIOS-chosen (clocks, voltages, MC register values) making this a strong corruption primitive on-target.

Fix validation

fix.diff clamps the MCLK count at the populate sinks (sinks 1, 2, 4 via the clamped count, sink 3 with its own clamp). fix_status: not_testable (no hardware); bounds-only diff. Root fix belongs one layer up β€” trim MCLK (and SCLK for symmetry) in smu7_setup_dpm_tables_v0/v1 exactly like the voltage/PCIE tables; that also kills DF-3939's trigger at the source.

Out-of-file note (for the orchestrator, not re-reported here)

Dep-table counts > 8 overflow dpm_levels[8] inside struct smu7_hwmgr itself (smu7_hwmgr.c:704-710, :783-799) β€” a separate file/finding.

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 not validated on the guest: requires an AMD Sea Islands GPU + crafted VBIOS (no 0x1002 function on the guest, env.txt). fix.diff clamps the MCLK count to SMU7_MAX_LEVELS_MEMORY at the ci_smumgr.c sinks (populate loop, MemoryDpmLevelCount, [count-1] store, mc_regs->data[] loop) and is authored post-verification against the cited lines; min_t is already used in this TU (ci_smumgr.c:391).

findings/poc/DF-3940/fix.diff + VERDICT.md
↓ fix.diffper-fix-DF-3940

Confirmed kernel references

Detail

Exploit chain

craft VBIOS with vdd_dep_on_mclk = 7-8 distinct MCLK entries -> amdgpu DPM enable -> ci_init_smc_table -> ci_populate_all_memory_levels/ci_populate_initial_mc_reg_table write past MemoryLevel[6]/mc_regs.data[6] -> corrupt LinkLevel + ci_mc_reg_table in the ci_smumgr heap object -> confused SMC state / OOD writes on every profile update (ci_update_dpm_settings)

Evidence (decisive lines)

findings/poc/DF-3940/VERDICT.md (sink table + provenance), fix.diff, env.txt

PoC changes

No seed PoC; hardware-gated (AMD CI GPU + crafted VBIOS).

Verified recommended fix

Clamp mclk count to SMU7_MAX_LEVELS_MEMORY at the ci_smumgr.c sinks (fix.diff), or trim MCLK levels once in smu7_setup_dpm_tables_v0/v1.

Verdict

CI twin of DF-3909: the VBIOS-derived mclk DPM count (up to MAX_REGULAR_DPM_NUMBER=8, unclamped from processpptables.c:391 ucNumEntries through smu7_hwmgr.c:704-710 dpm_levels[8]; the MCLK family is the only one without an upstream trim - voltages are trimmed at smu7_hwmgr.c:324-346, PCIE capped at :557-569) is used to index arrays dimensioned SMU7_MAX_LEVELS_MEMORY=6 for CI (ci_smumgr.h:27): MemoryLevel[i] full ~40B structs at ci_smumgr.c:1314-1318, the unguarded MemoryLevel[count-1] store at :1339, SMU7_Discrete_MCRegisterSet (16x u32 VBIOS MC data) into mc_regs->data[i] at :1794-1798 (SET_COUNT=6, smu7_discrete.h:408), and the runtime mclk_levels[MemoryDpmLevelCount] writes at :2819-2846. All land in the kzalloc'd struct ci_smumgr (ci_smumgr.c:2731-2743): sinks 1-2 clobber LinkLevel[0..2], sink 3 clobbers the trailing ci_mc_reg_table - in-object heap corruption with VBIOS-chosen contents, then byte-copied into SMC. Crafted-VBIOS vector on Bonaire/Hawaii; hardware absent from the audit guest => inconclusive, source-certain, like tonga DF-3909.