Missing bounds check on vce_states[].clk_idx causes OOB read past ClockInfoArray
Summary
trinity_parse_power_table VCE loop at trinity_dpm.c:1809-1813: clock_array_index=vce_states[i].clk_idx (6-bit 0-63 from VBIOS). clockInfo[idx*ucEntrySize] with NO check vs ucNumEntries. idx=63 vs ucNumEntries=1 -> 1008B OOB read. Sibling of DF-1269/1307. Crafted VBIOS. Fix: check clock_array_index<ucNumEntries.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1402 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trinity_vce_oob.c | trigger-source | byte-exact harness replicating VCE clk_idx OOB read (clockInfo byte array) | 4.0 KB | view raw |
| build.sh | build-script | cc -O2 -o trinity_vce_oob trinity_vce_oob.c | 90 B | view raw |
| run.sh | run-script | run harness | 28 B | view raw |
| run.log | run-log | decisive harness output, OOB READ CONFIRMED (1008B OOB) | 760 B | view raw |
| env.txt | environment | uname, cc version | 227 B | view raw |
| fix.diff | suggested-fix | add clk_idx >= ucNumEntries guard -> continue | 591 B | view raw |
| VERDICT.md | verdict | full analysis (read-only primitive, no escalation) | 4.0 KB | β raw |
| README.md | readme | reproduce guide | 1.6 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-1402 β trinity_parse_power_table VCE clk_idx OOB read (PoC)
Summary
The VCE power-state loop in trinity_parse_power_table
(sys/dev/drm/radeon/trinity_dpm.c:1816-1823) indexes the clock info array with
a VBIOS-supplied 6-bit clk_idx (0..63) and no bounds check. With
ucNumEntries=1 and clk_idx=63 that is a ~1008-byte OOB read. Read-only
primitive β no escalation; ceiling = wrong VCE engine clock / OOB info read
(DoS).
Reachability
radeon is a loadable module (radeon.ko), not in GENERIC, attaching only to
AMD/ATI Radeon GPUs. The QEMU guest has no AMD GPU, so the bug is not
live-reachable here. Proven deterministically with a byte-exact harness
replicating struct _ClockInfoArray (pptable.h:446-454), where clockInfo
is a byte array so [idx*ucEntrySize] is a byte offset.
Build / run
./build.sh && ./run.sh
Expected output (bug present)
level 0: clk_idx=63 -> byte offset [63*16=1008] OOB -> sclk=0xdddddd guard present? : NO (clock path at :1789 checks, this VCE loop does not) OOB READ CONFIRMED: clk_idx=63 vs ucNumEntries=1 reads ~1008 bytes OOB per level (idx*ucEntrySize).
On a fixed driver the guard continues on clk_idx >= ucNumEntries.
Fix
Add the missing guard mirroring the clock path: if
clock_array_index >= clock_info_array->ucNumEntries, continue
(fix.diff). Validated to compile into a rebuilt radeon.ko (-Werror,
rc=0; trinity_dpm.o built).
Files
trinity_vce_oob.cβ byte-exact harness.build.sh/run.sh/run.log/env.txt/fix.diff/VERDICT.md/manifest.json.
DF-1402 β trinity_parse_power_table VCE clk_idx OOB read
Verdict: REPRODUCED (primitive proven via source trace + byte-exact harness). Fix compiles.
In the VCE power-state loop of trinity_parse_power_table, a VBIOS-supplied
6-bit clk_idx (0..63) indexes the clock info array with no bounds check
β matching exactly the finding's claim of ~1008-byte OOB read. Read-only
primitive (no write-through), so no escalation chain; ceiling = OOB-info read /
wrong VCE clock (DoS). Confirmed real by source tracing + harness. Not
live-reachable on the QEMU guest (no AMD Trinity GPU); the fix compiles.
Mechanism (trigger β primitive β effect)
sys/dev/drm/radeon/trinity_dpm.c:1816-1823:
/* fill in the vce power states */
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, NO CHECK */
clock_info = (union pplib_clock_info *)
&clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize]; /* OOB */
sclk = le16_to_cpu(clock_info->sumo.usEngineClockLow);
sclk |= clock_info->sumo.ucEngineClockHigh << 16;
rdev->pm.dpm.vce_states[i].sclk = sclk; /* stored OOB-read value */
...
}
The array type (sys/dev/drm/radeon/pptable.h:446-454):
typedef struct _ClockInfoArray {
UCHAR ucNumEntries;
UCHAR ucEntrySize;
UCHAR clockInfo[1]; /* BYTE array (UCHAR) */
} ClockInfoArray;
Because clockInfo is a byte array, [clock_array_index * ucEntrySize] is
a byte offset β with clk_idx=63 and ucEntrySize=16, that is byte offset
1008; against ucNumEntries=1 (16 bytes) it reads ~992 bytes OOB. The read
value is stored into vce_states[i].sclk.
The clock path at :1786-1790 guards clock_array_index >= ucNumEntries, but
this VCE loop does not β an oversight sibling of DF-1269/1307.
Primitive
- Class: out-of-bounds READ, VBIOS-controlled 6-bit index (0..63) vs
ucNumEntries; up to ~1008 bytes OOB per the finding. - READ-ONLY: no write-through β NOT a write primitive. Valid hard blocker for escalation (Phase 6: read-only β no chain). Impact ceiling = wrong VCE engine clock / OOB info read β DoS via bogus clock programming.
Reachability / threat model
radeonis a loadable module (radeon.ko), NOT inX86_64_GENERIC. Attaches to AMD/ATI Radeon GPUs. No AMD GPU on the QEMU guest β not live-reachable here.- Threat: crafted VBIOS parsed at GPU attach (malicious firmware flash / GPU passthrough). Local, already-on-the-box attacker presenting a crafted VBIOS.
Harness proof (run.log)
clockInfoArray.ucNumEntries = 1, ucEntrySize = 16 level 0: clk_idx=63 -> byte offset [63*16=1008] OOB -> sclk=0xdddddd ... guard present? : NO (clock path at :1789 checks, this VCE loop does not) OOB READ CONFIRMED: clk_idx=63 vs ucNumEntries=1 reads ~1008 bytes OOB per level (idx*ucEntrySize).
The harness initially segfaulted because clockInfo was wrongly modelled as an
element array; the kernel pptable.h shows it is a UCHAR[] byte array, so
the index is a byte offset β exactly the 1008-byte OOB the finding describes.
Fix validation
fix.diff adds the missing guard mirroring the clock path: after reading
clk_idx, if clock_array_index >= clock_info_array->ucNumEntries, continue
(skip that VCE level β no allocation in the loop, so continue is clean, and
it matches the :1789 continue style). The fix was applied to in-guest
/usr/src and radeon.ko rebuilt cleanly (cc ... -Werror, RC=0;
trinity_dpm.o built with the fix). Runtime re-test not possible (no AMD GPU),
so fix_status = not_testable for runtime, with the diff verified to apply +
compile and the harness logic confirming the guard rejects the OOB index.
Kernel references
sys/dev/drm/radeon/trinity_dpm.c:1816-1823(unguarded VCE clk_idx loop)sys/dev/drm/radeon/trinity_dpm.c:1786-1790(the guarded clock path it should mirror)sys/dev/drm/radeon/pptable.h:446-454(struct _ClockInfoArray, byte-arrayclockInfo)
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). trinity VCE clk_idx no bounds -> 1008B OOB read. radeon not in GENERIC. Read-only.
No comments yet.