Unbounded VCE clk_idx causes heap OOB read in kv_parse_power_table
Summary
kv_parse_power_table VCE loop at kv_dpm.c:2705-2707: clock_array_index=vce_states[i].clk_idx (6-bit 0-63 from VBIOS). clock_info=&clockInfo[clock_array_index*ucEntrySize]. NO check vs ucNumEntries. OOB read up to 16065 bytes past clockInfo[0]. Sibling of DF-1269 VCE clk_idx. Fix: check clock_array_index<ucNumEntries.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1307 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | replica of ClockInfoArray+VCE byte-index access; clk_idx=63*ucEntrySize=255 faults off 5-page guard | 6.0 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 107 B | view raw |
| run.sh | run-script | ./harness | 60 B | view raw |
| build.log | build-log | final successful build | 65 B | view raw |
| run.log | run-log | decisive run: FAULT/CONFIRMED | 671 B | view raw |
| fix.diff | suggested-fix | bounds-check VCE clk_idx vs ucNumEntries (mirror main clock loop) | 1013 B | view raw |
| env.txt | environment | uname, cc version | 555 B | view raw |
| VERDICT.md | verdict | full narrative: VCE clk_idx provenance, asymmetry vs clock loop, mechanism, harness, fix | 3.9 KB | β raw |
| README.md | readme | build/run/expected | 992 B | β raw |
| fix_module_proof.txt | fix-build-proof | radeon.ko built with fix applied under -Werror, 0 errors, kv_dpm.o produced | 169 B | view raw |
| fix_module_build.log | fix-build-log | radeon module build excerpt: kv_dpm.o compiled, radeon.ko linked | 63.0 KB | view 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-1307 β Unbounded VCE clk_idx heap OOB read in kv_parse_power_table
Severity: Medium Β· CWE: CWE-125 (Out-of-bounds Read)
File: sys/dev/drm/radeon/kv_dpm.c:2705-2707
Build & run (radeon-DRM latent bug β no AMD GPU on guest, harness proof)
./build.sh # cc -O2 -Wall -o harness harness.c ./run.sh # ./harness
Expected output (bug present)
VBIOS VCE clk_idx = 63 (NO check vs ucNumEntries=2) access byte offset = 63 * 255 = 16065 (past the 2 valid entries) FAULT (signal 11): OOB read at clockInfo[16065] off the bios buffer RESULT: OOB read CONFIRMED at kv_dpm.c:2707
Preconditions (kernel path)
radeon driver attach on Kabini/Kaveri/Mullins/Trinity APUs (VCE power-state
fill from powerplay tables). Attacker controls the VBIOS image (clk_idx is a
6-bit field in the VCE state record, r600_dpm.c:1128). See VERDICT.md. The
main clock loop at line 2683 checks bounds; the VCE loop (2705) does not β
that asymmetry is the bug.
DF-1307 β Unbounded VCE clk_idx heap OOB read in kv_parse_power_table
Verdict: REPRODUCED (source-level + harness) β latent radeon-DRM bug, heap OOB read
The radeon DPM code (kv_dpm.c) is part of the radeon DRM module, which is
not in X86_64_GENERIC and no AMD GPU is present on the audit guest.
It is a real latent bug confirmed by source trace and reproduced at the
access-pattern level with a userspace harness.
The bug
sys/dev/drm/radeon/kv_dpm.c, kv_parse_power_table, VCE power-state fill
loop, lines 2705-2707:
for (i = 0; i < RADEON_MAX_VCE_LEVELS; i++) {
u32 sclk;
clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx; /* 6-bit 0..63, VBIOS */
clock_info = (union pplib_clock_info *)
&clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize]; /* NO bounds check */
...
}
clk_idx originates from the VBIOS VCE state record:
/* r600_dpm.c:1127-1128 */
rdev->pm.dpm.vce_states[i].clk_idx = state_entry->ucClockInfoIndex & 0x3f; /* 6-bit, 0..63 */
It is used to byte-index clockInfo[] (a UCHAR flex[1] array,
pptable.h:446-454) at offset clk_idx * ucEntrySize, with no bounds check
against clock_info_array->ucNumEntries. With clk_idx up to 63 and
ucEntrySize up to 255 (UCHAR), the byte offset reaches 63 * 255 = 16065
past clockInfo[0] β heap OOB read off the kmalloc'd bios buffer.
Contrast β the main clock loop at the same function DOES check (line 2683):
clock_array_index = idx[j];
if (clock_array_index >= clock_info_array->ucNumEntries) /* <-- bounds check */
continue;
The VCE loop at 2705 was never given the equivalent guard. Sibling of DF-1269 and DF-1306 (the non-clock path, same function, same pattern of missing check).
Reachability / threat model
kv_parse_power_table runs at radeon driver attach on Kabini/Kaveri/
Mullins/Trinity (Southern/Sea Islands APUs). Threat model: malicious/faulty
VBIOS, VFIO PCI passthrough of a card with a hacked ROM, supply-chain VBIOS
tampering. Effect: kernel heap OOB read (info leak of adjacent slab/heap
contents, up to ~16 KB) and/or DoS (read past mapped bios buffer β panic).
Local, requires attacker control of the VBIOS image β the same trust boundary
the driver already assumes.
Harness proof
harness.c replicates ClockInfoArray + the VCE loop's byte-index access,
places clockInfo[0] at the tail of a data page with a 5-page PROT_NONE
guard region after it (the 16065-byte OOB would skip a single guard page), sets
clk_idx=63 + ucEntrySize=255 (the crafted-VBIOS amplification), and shows
the read at byte offset 16065 faults. Decisive run:
DF-1307 kv_parse_power_table VCE clk_idx OOB read harness sizeof(ClockInfoArray) overhead = 3 (clockInfo is UCHAR flex[1]) ClockInfoArray @ 0x8004a0fee (clockInfo[0] @ 0x8004a0ff0), ucNumEntries=2, ucEntrySize=255 VBIOS VCE clk_idx = 63 (NO check vs ucNumEntries=2) access byte offset = 63 * 255 = 16065 (past the 2 valid entries) FAULT (signal 11): OOB read at clockInfo[16065] off the bios buffer -> in-kernel equivalent: heap OOB read up to 16065 bytes past clockInfo[0] RESULT: OOB read CONFIRMED at kv_dpm.c:2707 (VCE clk_idx byte-index with NO bounds check vs ucNumEntries)
noinline + volatile inputs prevent gcc -O2 from constant-folding the
array index and eliding the OOB load as UB.
Build & run
./build.sh # cc -O2 -Wall -o harness harness.c ./run.sh # ./harness
Fix
fix.diff adds the bounds check mirroring the clock path at line 2683, right
before the unbounded byte-index access, and zeroes the VCE state on the error
path (matching the loop's existing sclk=0; mclk=0; epilogue at line 2710-2711):
if (clock_array_index >= clock_info_array->ucNumEntries) {
rdev->pm.dpm.vce_states[i].sclk = 0;
rdev->pm.dpm.vce_states[i].mclk = 0;
continue;
}
This matches the finding proposal ("check clock_array_index < ucNumEntries").
Fix verification
fixedvalidated
module build rc=0 + harness
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. kv VCE clk_idx no bounds vs ucNumEntries -> 16KB OOB read. radeon not in GENERIC.
No comments yet.