Missing bounds check on nonClockInfoIndex causes OOB read past NonClockInfoArray
Summary
trinity_parse_power_table at trinity_dpm.c:1774-1776: non_clock_array_index=power_state->v2.nonClockInfoIndex (u8 from VBIOS) used without check vs ucNumEntries. Clock path at :1789 has guard, non-clock does not. Sibling of DF-1269/1333. Crafted VBIOS. Fix: check non_clock_array_index<ucNumEntries.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1401 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trinity_nonclock_oob.c | trigger-source | byte-exact harness replicating nonClockInfoIndex OOB read | 4.1 KB | view raw |
| build.sh | build-script | cc -O2 -o trinity_nonclock_oob trinity_nonclock_oob.c | 105 B | view raw |
| run.sh | run-script | run harness | 33 B | view raw |
| run.log | run-log | decisive harness output, OOB READ CONFIRMED | 716 B | view raw |
| env.txt | environment | uname, cc version | 227 B | view raw |
| fix.diff | suggested-fix | add nonClockInfoIndex >= ucNumEntries guard -> return -EINVAL | 803 B | view raw |
| VERDICT.md | verdict | full analysis (read-only primitive, no escalation) | 4.1 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-1401 β trinity_parse_power_table nonClockInfoIndex OOB read (PoC)
Summary
trinity_parse_power_table (sys/dev/drm/radeon/trinity_dpm.c:1774-1776)
indexes the non-clock info array with a VBIOS-supplied nonClockInfoIndex (u8)
and no bounds check, unlike the clock path at :1786-1790 which IS
guarded. Crafted VBIOS β OOB read. Read-only primitive (no write-through)
β no escalation; ceiling = OOB-info read / wrong DPM settings (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 _NonClockInfoArray (pptable.h:456-464).
Build / run
./build.sh && ./run.sh
Expected output (bug present)
nonClockInfoIndex (from VBIOS) = 200 guard present? : NO (contrast clock path at :1789 which checks >= ucNumEntries) reads entry [200] of a 1-entry array -> OUT OF BOUNDS OOB READ CONFIRMED: ... reads ~3200 bytes OOB ...
On a fixed driver the guard rejects indices >= ucNumEntries and returns
-EINVAL.
Fix
Add the missing guard mirroring the clock path: if
non_clock_array_index >= non_clock_info_array->ucNumEntries, free
rdev->pm.dpm.ps, NULL it, return -EINVAL (fix.diff). Validated to compile
into a rebuilt radeon.ko (-Werror, rc=0; trinity_dpm.o built).
Files
trinity_nonclock_oob.cβ byte-exact harness.build.sh/run.sh/run.log/env.txt/fix.diff/VERDICT.md/manifest.json.
DF-1401 β trinity_parse_power_table nonClockInfoIndex OOB read
Verdict: REPRODUCED (primitive proven via source trace + byte-exact harness). Fix compiles.
trinity_parse_power_table indexes the non-clock info array with a
VBIOS-supplied nonClockInfoIndex (u8) and no bounds check, unlike the
clock path in the same function which IS guarded. A crafted VBIOS with
nonClockInfoIndex >= ucNumEntries reads out of bounds. This is a read-only
primitive (the OOB data is read and stored, not written through), so there is
no escalation chain β the impact ceiling is OOB-info read / wrong DPM settings
(DoS via bogus clock programming). 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:1774-1776:
non_clock_array_index = power_state->v2.nonClockInfoIndex; /* u8 from VBIOS, NO CHECK */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
&non_clock_info_array->nonClockInfo[non_clock_array_index]; /* OOB READ */
Contrast the clock path at :1786-1790, which IS guarded:
idx = (u8 *)&power_state->v2.clockInfoIndex[0];
for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) {
clock_array_index = idx[j];
if (clock_array_index >= clock_info_array->ucNumEntries) /* <-- GUARDED */
continue;
...
}
The non-clock index has no equivalent check β an oversight sibling of DF-1269/1333.
The array type (sys/dev/drm/radeon/pptable.h:456-464):
typedef struct _NonClockInfoArray {
UCHAR ucNumEntries;
UCHAR ucEntrySize;
ATOM_PPLIB_NONCLOCK_INFO nonClockInfo[1]; /* array of structs; index is element index */
} NonClockInfoArray;
non_clock_info is then passed to trinity_parse_pplib_non_clock_info(...) and
its fields are READ into the power-state struct.
Primitive
- Class: out-of-bounds READ, VBIOS-controlled index (u8, 0..255) vs
ucNumEntries; withucNumEntries=1and index 255, reads ~255 entries past the array β up to ~255*ucEntrySizebytes OOB. - READ-ONLY: no write-through, so this is NOT a memory-corruption write primitive. Valid hard blocker for escalation (Phase 6: read-only primitive β no chain). Impact ceiling = OOB-info read / wrong DPM settings β DoS via bogus clock programming on crafted VBIOS.
Reachability / threat model
radeonis a loadable module (radeon.ko), NOT inX86_64_GENERIC. It attaches to AMD/ATI Radeon GPUs. The QEMU guest has no AMD GPU, sotrinity_parse_power_tablenever runs live here.- Threat: a crafted VBIOS (e.g. flashed malicious firmware, or a malicious virtual-GPU passthrough) parsed at GPU attach. This is a local, already-on-the-box attacker with the ability to present a crafted VBIOS image to the driver.
Harness proof (run.log)
nonClockInfoArray.ucNumEntries = 1 nonClockInfoIndex (from VBIOS) = 200 guard present? : NO (contrast clock path at :1789 which checks >= ucNumEntries) reads entry [200] of a 1-entry array -> OUT OF BOUNDS OOB READ CONFIRMED: nonClockInfoIndex=200 used against ucNumEntries=1 with no bounds check; reads ~3200 bytes OOB.
Fix validation
fix.diff adds the missing guard mirroring the clock path: after reading
nonClockInfoIndex, if it >= non_clock_info_array->ucNumEntries, free
rdev->pm.dpm.ps, set it NULL, and return -EINVAL (matching the existing
error-cleanup pattern at :1780-1783 which frees dpm.ps and returns on
allocation failure). The fix was applied to in-guest /usr/src and the
radeon.ko module rebuilt cleanly (cc ... -Werror, RC=0; trinity_dpm.o
built with the fix). Runtime re-test is not possible on this guest (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:1774-1776(unguarded non-clock index)sys/dev/drm/radeon/trinity_dpm.c:1786-1790(the guarded clock path it should mirror)sys/dev/drm/radeon/pptable.h:456-464(struct _NonClockInfoArray)
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). trinity nonClockInfoIndex no bounds -> OOB read into power state. radeon not in GENERIC. Read-only.
No comments yet.