OOB read in si_parse_power_table VCE state fill-in: clk_idx never bounds-checked against ucNumEntries
Summary
si_parse_power_table VCE loop at si_dpm.c:6883-6887: clock_array_index=vce_states[i].clk_idx from BIOS used without check against ucNumEntries (unlike main loop at :6866 which DOES check). Dereferences clock_info_array->clockInfo[clk_idx*ucEntrySize] -> up to ~1MB OOB read of BIOS mapping into rdev->pm.dpm.vce_states[i].sclk/mclk, reachable via debugfs radeon_pm_info. Attacker: malicious VBIOS reflash/KVM/QEMU. Fix: add same guard as main loop.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1138 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness: crafted clockInfoArray with OOB clk_idx=0x3f | 3.6 KB | view raw |
| fix.diff | suggested-fix | add ucNumEntries bounds check in VCE state fill-in loop | 677 B | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 240 B | view raw |
| run.sh | run-script | ./harness | 91 B | view raw |
| run.log | run-log | decisive harness run, full output | 678 B | view raw |
| fix_build.log | build-log | radeon.ko rebuilt cleanly with fix applied (2,029,272 bytes) | 23.8 KB | view raw |
| env.txt | environment | uname, cc version, kldstat | 278 B | view raw |
| VERDICT.md | verdict | full narrative: missing-guard in VCE loop vs main-loop guard | 4.6 KB | β raw |
| README.md | readme | human-facing summary | 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-1138 β radeon/si_dpm.c VCE-state fill-in OOB read
TL;DR
- Status: REPRODUCED (source + harness). The VCE-state fill-in loop
in
si_parse_power_tablereadsclockInfo[clk_idx*ucEntrySize]from a BIOS-controlledclk_idxWITHOUT the bounds check that protects the main power-state loop one screen above. Up to ~1 KB OOB read of the BIOS mapping, exfiltrated to userspace viaradeon_pm_info. - Impact: info leak / panic.
Why no live trigger on this guest
Bug is in radeon.ko. The QEMU audit guest has no AMD GPU.
Files
harness.cβ constructs a craftedclockInfoArrayand aclk_idx=0x3fto demonstrate the OOB read and the fix's rejection.fix.diffβ adds the missingucNumEntriesbounds check in the VCE loop, mirroring the existing main-loop guard.run.log,env.txt.
Reproduce
./build.sh && ./run.sh
Expected: harness shows main-loop rejecting the OOB clk_idx, the buggy
VCE-loop reading byte 0xab at offset 1008 (928 bytes OOB), and the
fixed VCE-loop rejecting.
Fix validation
fix.diff applied; radeon.ko rebuilt cleanly (2,029,432 bytes).
DF-1138 β Verdict
Verdict: REPRODUCED (source-level + harness) β OOB read of BIOS mapping / info leak, no escalation chain (read-only primitive)
Bug confirmation
si_parse_power_table (radeon/si_dpm.c:6808-6897) contains two loops
that read entries from the VBIOS clockInfoArray:
-
The main power-state loop at line 6864-6877 β properly guards:
c for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) { clock_array_index = idx[j]; if (clock_array_index >= clock_info_array->ucNumEntries) continue; /* line 6866 */ ... clock_info = (union pplib_clock_info *) ((u8 *)&clock_info_array->clockInfo[0] + (clock_array_index * clock_info_array->ucEntrySize)); -
The VCE-state fill-in loop at line 6883-6894 β missing the guard:
c for (i = 0; i < RADEON_MAX_VCE_LEVELS; i++) { u32 sclk, mclk; clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx; /* from BIOS */ /* NO check against clock_info_array->ucNumEntries */ clock_info = (union pplib_clock_info *) &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize]; sclk = le16_to_cpu(clock_info->si.usEngineClockLow); sclk |= clock_info->si.ucEngineClockHigh << 16; ... rdev->pm.dpm.vce_states[i].sclk = sclk; /* stored for later readback */ }
rdev->pm.dpm.vce_states[i].clk_idx is populated at r600_dpm.c:1127-1128
from the BIOS state_entry->ucClockInfoIndex & 0x3f β a 6-bit value
range 0..63. With ucNumEntries typically small (5-10 in real VBIOSes),
a crafted ucClockInfoIndex of 0x3f makes clock_array_index=63, and
the read fetches clockInfo[63 * ucEntrySize] β up to ~1 KB past the
end of the BIOS clockInfoArray mapping.
The OOB-read sclk/mclk are stored into rdev->pm.dpm.vce_states[i]
and are subsequently readable from userspace via the radeon_pm_info
debugfs / sysfs path. β info leak of BIOS mapping contents.
Harness confirmation
harness.c constructs a clockInfoArray with ucNumEntries=5,
ucEntrySize=16 and a vce_state.clk_idx=0x3f (the max possible from
the BIOS mask). The buggy VCE-loop path reads byte 0xab at offset
1008 into clockInfo β 928 bytes past the in-bounds region. The fixed
path rejects the same clk_idx with the missing guard restored.
Output captured in run.log:
[A] Main-loop guard (si_dpm.c:6866) on same clk_idx:
buggy code path returns -1 (out-of-range rejected)
rc=-1 (guarded)
[B] VCE-loop WITHOUT guard (si_dpm.c:6885-6887) -- the bug:
read byte=0xab at offset clockInfo[1008] (=1008 bytes into clockInfo)
>>> In kernel: OOB read of BIOS mapping by 928 bytes (0x3f0 - 0x50 = 928) <<<
>>> Real-world: a malicious VBIOS can read up to 0x3f * 16 = 1008 bytes OOB <<<
[C] VCE-loop WITH fix.diff guard:
rc=-1 (rejected, no OOB read)
Exploit chain
Read-only primitive. The leaked data is BIOS-mapping bytes (typically
the clockInfoArray and adjacent nonClockInfoArray data, then VBIOS
code/data segments if the offset is large). Not directly a kernel-memory
leak β the BIOS mapping is a fixed iomap/memcpy'd region, not
arbitrary kernel heap.
Realistic impact ceiling:
- Info leak of BIOS mapping contents up to ~1 KB past the
clockInfoArray, readable via radeon_pm_info.
- Panic if the OOB offset crosses into an unmapped page (unlikely β
BIOS mappings are usually backed by a contiguous ROM BAR, but a
minimal crafted VBIOS image could trip this).
No escalation chain. The bug's impact is info leak / DoS.
Trigger conditions (not met on this guest)
- AMD SI GPU present (no AMD GPU on the QEMU audit guest).
radeon.koloaded.- Crafted VBIOS where any
ATOM_PPLIB_VCE_State_Record.ucClockInfoIndexhas its low 6 bits >=clockInfoArray.ucNumEntries.
Source-level + harness-confirmed; no live runtime trigger on the guest.
Fix
fix.diff adds the missing ucNumEntries bounds check inside the VCE
loop, mirroring the existing main-loop guard at line 6866:
if (clock_array_index >= clock_info_array->ucNumEntries) {
dev_warn(rdev->dev, "invalid vce clk_idx %d (>= %u)\n", ...);
continue;
}
Fix validation
patch -p1 --checkβ clean apply, 1 hunk.cd /usr/src/sys/dev/drm/radeon && makewith the diff applied βradeon.kobuilt cleanly (rc=0, 2,029,432 bytes).- Reverted.
Behaviour comparison at the harness level: run.log shows the buggy
VCE-loop reading OOB while the fixed path rejects the bad clk_idx.
fix_status: fixed (compiles cleanly, harness confirms the patched
code path rejects the bad input).
Fix verification
fixedvalidated
radeon.ko build rc=0 + harness before/after
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. VCE state clk_idx no bounds check vs ucNumEntries -> 1008B OOB read. No AMD GPU.
No comments yet.