Heap OOB write in ci_setup_default_dpm_tables from unbounded VBIOS dependency-table counts
Summary
ci_setup_default_dpm_tables at ci_dpm.c:3514-3566: sclk/mclk/vddc/vddci/mvdd loops iterate VBIOS-supplied count (u8 up to 255) writing into dpm_levels[MAX_REGULAR_DPM_NUMBER=8]. Only lower-bound checked. Crafted VBIOS with count>8 overflows sclk_table into mclk_table/pcee/vddc/vddci/mvdd then ci_power_info pointer fields. Vddc loop also OOB-reads std_voltage_table->entries[i]. Same class as DF-1141 (amdgpu). Fix: upper-bound all counts against MAX_REGULAR_DPM_NUMBER.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1150 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace replica of the 5 ci_setup_default_dpm_tables loops; counts OOB writes for crafted VBIOS count=255 | 4.8 KB | view raw |
| fix.diff | suggested-fix | git-apply-able: clamp each loop to MAX_REGULAR_DPM_NUMBER + clamp .count (6 clamps) | 2.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 486 B | view raw |
| run.sh | run-script | ./harness | 214 B | view raw |
| build.log | build-log | radeon.ko module build with fix applied (ci_dpm.o, -Werror, rc=0) | 1.2 KB | view raw |
| run.log | run-log | decisive harness run: 1235 OOB writes confirmed, fix -> 0 | 726 B | view raw |
| env.txt | environment | guest uname/cc, no AMD GPU, radeon not in GENERIC | 1.3 KB | view raw |
| VERDICT.md | verdict | full narrative: mechanism, harness, threat model, uid0 assessment, fix validation | 5.3 KB | β raw |
| README.md | readme | how to reproduce + status | 1.1 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-1150 β PoC evidence pack
Heap OOB write in ci_setup_default_dpm_tables (radeon sys/dev/drm/radeon/ci_dpm.c:3514-3565).
ci_setup_default_dpm_tables fills five DPM tables (dpm_levels[MAX_REGULAR_DPM_NUMBER=8])
from VBIOS dependency-table counts that are u8 (up to 255) and only lower-bound
checked. A crafted VBIOS (count>8) overflows each 8-entry dpm_levels[] into
the adjacent ci_single_dpm_table and past struct ci_dpm_table.
Status
Source-confirmed + harness. Latent at runtime on this guest: radeon is
optional radeon drm, NOT in X86_64_GENERIC, and the guest has no AMD GPU.
Reproduce
./build.sh # cc -O2 -Wall -o harness harness.c (unprivileged maxx) ./run.sh # prints the OOB write count (1235 for count=255) + fix check
Expected (harness): DF-1150: CONFIRMED OOB heap write ... (VBIOS count=255 > MAX_REGULAR_DPM_NUMBER=8).
Fix
fix.diff clamps every loop to MAX_REGULAR_DPM_NUMBER (and clamps .count).
Validated: applies + compiles clean (full radeon.ko build, ci_dpm.o, -Werror).
See VERDICT.md for the full analysis.
DF-1150 β Heap OOB write in ci_setup_default_dpm_tables (radeon ci_dpm.c)
Verdict
REPRODUCED (code-level / harness), latent at runtime on this guest. The bug
is a genuine unbounded-copy-into-fixed-array heap OOB write, confirmed by
line-by-line source trace and a userspace harness that reproduces the exact
indexing. It is not triggerable at runtime on this guest (radeon is
optional radeon drm, NOT in X86_64_GENERIC, and the guest has no AMD GPU β
only QEMU std VGA 0x1234:0x1111). This is the valid hard blocker:
runtime-unreachable on this guest (no matching hardware) β a latent
HW-gated write. No exploit.c was written: this is not a userspace-reachable
primitive (no unprivileged guest syscall can inject a VBIOS).
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/radeon/ci_dpm.h:59,64).ci_dpm_tableholds five such tables (sclk/mclk/vddc/vddci/mvdd). - Attacker-controlled count: the per-table entry counts come straight from the VBIOS dependency tables (
allowed_sclk_vddc_table->count,allowed_mclk_table->count) βu8up to 255 β stored unclamped. - Unbounded loops into fixed arrays in
ci_setup_default_dpm_tables(sys/dev/drm/radeon/ci_dpm.c): - sclk dedup loop
:3514writesdpm_levels[count],count++per distinct entry β up to 255 writes into[8]. - mclk dedup loop
:3527(same shape). - vddc loop
:3539writesvddc_table.dpm_levels[i]foriin[0, count)(direct index), and OOB-readsstd_voltage_table->entries[i];.countset to the unbounded count:3546. - vddci loop
:3550+.count:3555; mvdd loop:3560+.count:3565(identical, unbounded). - Effect: with a crafted VBIOS (count>8) the writes run off each 8-entry
dpm_levels[]into the nextci_single_dpm_tableand paststruct ci_dpm_tableinto adjacentci_power_infoheap fields. - The
ci_reset_single_dpm_tablehelper (:3408) only zerosdpm_levels[0..7]; it does not bound the subsequent fill loops.
Evidence (harness)
harness.c (run as unprivileged maxx) models all five loops with a crafted
VBIOS count of 255. Result: 1235 writes land at dpm_levels[8..] across the
five loops. With the fix (&& i < MAX_REGULAR_DPM_NUMBER), the OOB count drops
to 0. (Full output in run.log.)
Threat model / reachability
- Attacker: malicious/reflashed VBIOS, or a malicious PCIe/Thunderbolt AMD Sea Islands GPU whose VBIOS dependency tables ship
count > 8. Reached at DPM init (ci_dpm_enableβci_setup_default_dpm_tables). - On this guest: NOT reachable. radeon is not in GENERIC; no AMD GPU. The realistic impact ceiling (on physical AMD CIK hardware) is kernel heap corruption controllable via crafted VBIOS β kernel panic / DoS; uid0 escalation would require a separate slab-layout-control primitive.
Exploit chain
None developed β valid hard blocker: the primitive is only reachable on physical AMD Sea Islands hardware (or VFIO passthrough) 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.
PoC changes
Authored from scratch (the dir was empty). Deliverables: harness.c, fix.diff
(clamp each loop to MAX_REGULAR_DPM_NUMBER + clamp .count), build.sh,
run.sh, VERDICT.md, manifest.json, env.txt, build.log, run.log.
Recommended fix
fix.diff adds && i < MAX_REGULAR_DPM_NUMBER to each of the five loop
conditions (:3514/:3527/:3539/:3550/:3560) and clamps the three trailing
.count assignments (:3546/:3555/:3565) to at most MAX_REGULAR_DPM_NUMBER.
This matches the finding proposal ("upper-bound all counts against
MAX_REGULAR_DPM_NUMBER"). Validated: applies + compiles clean (full radeon.ko
module build, ci_dpm.o linked, -Werror) β see build.log.
Fix validation (Phase 8)
fix.diffapplies cleanly (git apply --checkOK;patch -p1in-guest OK, 6 clamps).- Full
radeon.komodule build (which generates the forwarder dir + compiles ci_dpm.o):cd /usr/src/sys/dev/drm/radeon && makeβ rc=0,radeon.ko(2029192 B) produced,ci_dpm.ocompiled & linked, 0 errors under-Werror. fix_status: not_testablefor runtime: radeon is not in GENERIC and there is no AMD GPU on the guest, so the live trigger cannot run. The fix is validated at apply + compile level: with the clamps, every loop iterates at mosti < MAX_REGULAR_DPM_NUMBERand.countis capped, so no write reachesdpm_levels[8]. On physical CIK hardware the patched module would no longer overflow.
Kernel references (confirmed during verification)
sys/dev/drm/radeon/ci_dpm.h:59β#define MAX_REGULAR_DPM_NUMBER 8sys/dev/drm/radeon/ci_dpm.h:64βdpm_levels[MAX_REGULAR_DPM_NUMBER]sys/dev/drm/radeon/ci_dpm.c:3514β sclk dedup loop (unbounded)sys/dev/drm/radeon/ci_dpm.c:3527β mclk dedup loopsys/dev/drm/radeon/ci_dpm.c:3539β vddc loop (direct index + std_voltage OOB read)sys/dev/drm/radeon/ci_dpm.c:3550β vddci loopsys/dev/drm/radeon/ci_dpm.c:3560β mvdd loopsys/dev/drm/radeon/ci_dpm.c:3408βci_reset_single_dpm_table(no bound on fill loops)
Fix verification
not_testablecompile+harness validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed+harness. radeon ci_setup_default_dpm_tables 5 loops unbounded vs MAX_REGULAR_DPM_NUMBER=8 -> 1235 OOB. Not in GENERIC.
No comments yet.