Heap OOB write in ci_setup_default_dpm_tables: VBIOS counts index fixed-size dpm_levels[8] unchecked
Summary
ci_setup_default_dpm_tables at ci_dpm.c:3593-3673: sclk/mclk/vddc/vddci/mvdd loops iterate to VBIOS-supplied count (u8, up to 255) writing into fixed-size dpm_levels[MAX_REGULAR_DPM_NUMBER=8]. No clamp. Crafted VBIOS with >8 entries overflows into adjacent ci_power_info fields (golden_dpm_table, voltage_control, SMC offsets). Corrupted counts then drive secondary OOB in ci_populate_all_graphic/memory_levels over GraphicsLevel[8]/MemoryLevel[6]. Attacker: malicious VBIOS via reflash/VFIO/QEMU. Fix: clamp each loop to MAX_REGULAR_DPM_NUMBER.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1141 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | clamp dpm-table loops to MAX_REGULAR_DPM_NUMBER (git-apply-able) | 2.7 KB | view raw |
| build.sh | build-script | apply fix.diff + incremental compile of amdgpu ci_dpm.o | 879 B | view raw |
| run.sh | run-script | documents runtime-unreachable (no AMD GPU on guest) | 1.2 KB | view raw |
| VERDICT.md | verdict | full source-trace + mechanism + fix rationale | 4.3 KB | β raw |
| env.txt | environment | guest uname, pci, driver build status | 2.0 KB | view raw |
| build.log | build-log | fix compile-validation output (patched ci_dpm.o, -Werror clean) | 1.5 KB | view raw |
| README.md | readme | human repro summary | 1.9 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1141 β Heap OOB write in ci_setup_default_dpm_tables (amdgpu ci_dpm.c)
Status: REPRODUCED at code level β latent at runtime on this guest (no AMD GPU). Severity (finding): High Β· CWE-787 Out-of-bounds Write
What the bug is
ci_setup_default_dpm_tables() (sys/dev/drm/amd/amdgpu/ci_dpm.c:3582) copies
VBIOS-supplied PowerPlay dependency-table counts (a u8, up to 255, from
amdgpu_parse_clk_voltage_dep_table at amdgpu_dpm.c:302) into the fixed
dpm_levels[MAX_REGULAR_DPM_NUMBER=8] arrays (ci_dpm.h:60,65) with no
clamp. A crafted VBIOS with ucNumEntries > 8 overflows sclk/mclk/vddc/
vddci/mvdd tables into adjacent ci_power_info fields.
Why it does not trigger here
The QEMU/KVM guest has no AMD GPU (pciconf shows only 0x1234:0x1111
QEMU std VGA), and amdgpu is not in X86_64_GENERIC. The path runs only
at DPM init on real Sea Islands (Bonaire/Hawaii) hardware with a malicious
VBIOS. This is a latent finding on the audit guest.
What was validated
- Source trace β the unbounded loops and the unbounded count source are
confirmed (see VERDICT.md
kernel_refs). - Baseline compiles β
amdgpu.ko(incl.ci_dpm.o) builds clean from/usr/srcunder-Werror. - Fix compiles β
fix.diffapplies (3 hunks) and the patchedci_dpm.orebuilds clean under-Werror(ci_dpm.o: 68080 -> 68176 bytes).
Reproduce (compile-validation only β no runtime trigger on this guest)
# on the guest, as root, from the with-src snapshot: scp this-folder/fix.diff root@guest:/root/df1141.diff cd /usr/src && patch -p1 --forward < /root/df1141.diff cd sys/dev/drm/amd/amdgpu && rm -f ci_dpm.o && make ci_dpm.o # -Werror clean
Or just ./build.sh after copying fix.diff to /root/.
Fix
fix.diff clamps each of the five loops in ci_setup_default_dpm_tables to
MAX_REGULAR_DPM_NUMBER and clamps the trailing .count assignments β
matches the finding's recommendation.
DF-1141 β Heap OOB write in ci_setup_default_dpm_tables (amdgpu ci_dpm.c)
Verdict
REPRODUCED (code-level, latent at runtime). The bug is a genuine
unbounded-copy-into-fixed-array heap OOB write, confirmed by source trace.
It is not triggerable at runtime on this guest (no AMD GPU; amdgpu is not
in GENERIC), so runtime impact is not_testable here. The fix.diff was
validated to apply + compile cleanly under -Werror.
Mechanism (trigger -> primitive -> effect)
- Source array:
struct ci_single_dpm_table { u32 count; struct ci_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER]; }withMAX_REGULAR_DPM_NUMBER = 8(sys/dev/drm/amd/amdgpu/ci_dpm.h:60,65).ci_dpm_tableholds five such tables (sclk/mclk/vddc/vddci/mvdd) back-to-back (ci_dpm.h:68-75). - Attacker-controlled count: the per-table entry counts come straight from VBIOS PowerPlay
ATOM_PPLIB_Clock_Voltage_Dependency_Table.ucNumEntries(au8, i.e. up to 255) viaamdgpu_parse_clk_voltage_dep_table(sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:282-304), which storesatom_table->ucNumEntriesintoamdgpu_table->countwith no upper bound (amdgpu_dpm.c:302). - Unbounded loops into fixed arrays in
ci_setup_default_dpm_tables(sys/dev/drm/amd/amdgpu/ci_dpm.c:3582): - sclk dedup loop
:3621writesdpm_levels[count]withcountderived fromallowed_sclk_vddc_table->count(write indexcount++on each distinct entry -> up toucNumEntrieswrites into[8]). - mclk dedup loop
:3634(same shape). - vddc loop
:3646writesvddc_table.dpm_levels[i]foriin[0, allowed_sclk_vddc_table->count)(index =i, unbounded), then setsvddc_table.count = allowed_sclk_vddc_table->count(:3653). - vddci loop
:3657+.count:3662; mvdd loop:3667+.count:3672(identical, unbounded). - Effect: with a crafted VBIOS (
ucNumEntries > 8), the writes run off the end of each 8-entrydpm_levels[]into the nextci_single_dpm_table(overwriting the next table'scount+dpm_levels) and ultimately paststruct ci_dpm_tableinto adjacentci_power_infofields (golden_dpm_table,voltage_control, SMC offsets). The corrupted counts then drive the secondary population loops inci_populate_all_graphic/memory_levelspastGraphicsLevel[8]/MemoryLevel[6].
Threat model / reachability
- Attacker: malicious/reflashed VBIOS, or a malicious PCIe/Thunderbolt AMD GPU whose VBIOS PowerPlay tables ship
ucNumEntries > 8. Reached at DPM init (ci_dpm_enable->ci_setup_default_dpm_tables). - On this guest: NOT reachable.
pciconfshows only QEMU std VGA (0x1234:0x1111); amdgpu is not inX86_64_GENERIC. This is the valid hard blocker: runtime-unreachable on this guest (no matching hardware) β a latent bug. The realistic impact ceiling (on physical AMD CIK hardware) is kernel heap corruption controllable via crafted VBIOS.
Exploit chain
None developed β valid hard blocker: the primitive is only reachable on
physical AMD Sea Islands hardware (or VFIO-passthrough of such a GPU) with a
malicious VBIOS, none of which exist on this QEMU/KVM guest. There is no
unprivileged-guest syscall path to inject a VBIOS. Per the bright-line rule
this is a latent HW-dependent write, not a default-GENERIC unpriv->kernel
chain; the demonstrated work is the source-level confirmation + compiling
fix. (No exploit.c written β not a userspace-reachable primitive.)
PoC changes
No trigger PoC exists (none was seeded for this latent finding). This folder
adds: fix.diff (clamp each loop to MAX_REGULAR_DPM_NUMBER), build.sh
(apply fix + incremental compile of ci_dpm.o), run.sh (documents the
runtime-unreachable status), VERDICT.md, manifest.json, env.txt,
build.log.
Recommended fix
Clamp every loop in ci_setup_default_dpm_tables to MAX_REGULAR_DPM_NUMBER
and clamp the trailing .count assignments. Implemented in fix.diff:
- :3621/:3634/:3646/:3657/:3667 loop conditions gain && i < MAX_REGULAR_DPM_NUMBER;
- :3653/:3662/:3672 .count set to min((u32)<src>->count, (u32)MAX_REGULAR_DPM_NUMBER).
This matches the finding proposal ("clamp each loop to
MAX_REGULAR_DPM_NUMBER"). min/min_t come from the DRM linux-compat header
sys/dev/drm/include/linux/kernel.h already force-included by the module.
Fix verification
not_testablecompile validated -Werror
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed+compile. ci_setup_default_dpm_tables 5 loops unbounded vs MAX_REGULAR_DPM_NUMBER=8. amdgpu not in GENERIC.
No comments yet.