Unbounded nonClockInfoIndex causes heap OOB read in kv_parse_power_table
Summary
kv_parse_power_table at kv_dpm.c:2668-2670: non_clock_array_index=power_state->v2.nonClockInfoIndex (u8 0-255 from VBIOS) used directly to index non_clock_info_array->nonClockInfo[non_clock_array_index]. NO bounds check vs ucNumEntries (clock path at :2683 DOES check). nonClockInfo is flex[1], OOB read up to 6120 bytes past nonClockInfo[0]. Sibling of DF-1269. Fix: check non_clock_array_index<ucNumEntries.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1306 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | replica of NonClockInfoArray+ATOM_PPLIB_NONCLOCK_INFO; nonClockInfoIndex=255 faults off buffer | 5.8 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 | 679 B | view raw |
| fix.diff | suggested-fix | bounds-check non_clock_array_index vs ucNumEntries (mirror clock path) | 918 B | view raw |
| env.txt | environment | uname, cc version | 555 B | view raw |
| VERDICT.md | verdict | full narrative: asymmetry vs clock path, mechanism, harness, fix | 3.9 KB | β raw |
| README.md | readme | build/run/expected | 852 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-1306 β Unbounded nonClockInfoIndex 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:2668-2670
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 nonClockInfoIndex = 255 (NO check vs ucNumEntries=1) FAULT (signal 11): OOB read at nonClockInfo[255] off the bios buffer RESULT: OOB read CONFIRMED at kv_dpm.c:2670
Preconditions (kernel path)
radeon driver attach on Kabini/Kaveri/Mullins/Trinity APUs (powerplay table
parse). Attacker controls the VBIOS image. See VERDICT.md. The clock path at
line 2683 checks bounds; the non-clock path (2668) does not β that asymmetry is
the bug.
DF-1306 β Unbounded nonClockInfoIndex 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, lines 2668-2670:
non_clock_array_index = power_state->v2.nonClockInfoIndex; /* u8 0..255, VBIOS */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
&non_clock_info_array->nonClockInfo[non_clock_array_index]; /* NO bounds check */
non_clock_array_index comes straight from the VBIOS (ATOM_PPLIB_STATE_V2.
nonClockInfoIndex, a u8) and is used to index nonClockInfo[] with no
bounds check against non_clock_info_array->ucNumEntries.
nonClockInfo is a flex[1] array of ATOM_PPLIB_NONCLOCK_INFO entries
(pptable.h:298-309, 24 bytes packed; pptable.h:456-464). With
nonClockInfoIndex=255 and ucNumEntries=1, the access reads at byte offset
255 * 24 = 6120 past nonClockInfo[0] β heap OOB read off the kmalloc'd
bios buffer.
Contrast β the clock path 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 non-clock path (2668-2670) was simply never given the equivalent guard β this is the regression/inconsistency that the finding calls out. Sibling of DF-1269 (same pattern in other radeon DPM drivers) and DF-1307 (the VCE loop two lines down, same function).
Reachability / threat model
kv_parse_power_table runs at radeon driver attach on Kabini/Kaveri/
Mullins/Trinity (Southern/Sea Islands APUs), parsing the powerplay tables from
the GPU VBIOS. 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) 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 NonClockInfoArray + ATOM_PPLIB_NONCLOCK_INFO
verbatim, places nonClockInfo[0] at the tail of a page-backed region with the
next page unmapped, sets nonClockInfoIndex=255 (the crafted-VBIOS value),
and shows the read at nonClockInfo[255] faults off the buffer. Decisive run:
DF-1306 kv_parse_power_table nonClockInfoIndex OOB read harness sizeof(ATOM_PPLIB_NONCLOCK_INFO)=28 sizeof(NonClockInfoArray)=32 NonClockInfoArray @ 0x80047cfe0 (nonClockInfo[0] @ 0x80047cfe4), ucNumEntries=1 VBIOS nonClockInfoIndex = 255 (NO check vs ucNumEntries=1) access byte offset = 255 * 28 = 7140 (past the single valid entry) FAULT (signal 11): OOB read at nonClockInfo[255] off the bios buffer -> in-kernel equivalent: heap OOB read up to 7140 bytes past nonClockInfo[0] RESULT: OOB read CONFIRMED at kv_dpm.c:2670 (nonClockInfoIndex used with NO bounds check vs ucNumEntries)
(28 bytes/entry here is the unpacked size; the kernel ATOM struct is packed to 24, giving the finding's 6120-byte ceiling β same bug either way.)
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 index is used, and frees the already-allocated dpm.ps
on the error path (matching the existing kfree(rdev->pm.dpm.ps); return
-ENOMEM; pattern at line 2675):
if (non_clock_array_index >= non_clock_info_array->ucNumEntries) {
kfree(rdev->pm.dpm.ps);
return -EINVAL;
}
This matches the finding proposal ("check non_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 nonClockInfoIndex no bounds vs ucNumEntries -> 6KB OOB read. radeon not in GENERIC.
No comments yet.