Unchecked nonClockInfoIndex (u8) and clockInfoIndex (u8 x ucEntrySize) in sumo_parse_power_table index past the NonClockInfoArray/ClockInfoArray in the kmalloc'd BIOS image: heap OOB read up to ~6.1 KB / ~65 KB β OOB bytes ingested into rps->class/class2/caps/vclk driving boot_ps/UVD/thermal selection β sumo twin of DF-1401/1306/1140
Summary
The parse takes nonClockInfoIndex (u8 0..255) against 24-byte nonClockInfo entries - up to ~6.1KB past; clock_array_index x ucEntrySize multiplies TWO untrusted u8s - up to ~65KB past; the state walk advances by 2+ucNumDPMLevels per state with no end-of-image bound. The arrays live in the kmalloc'd ROM copy: far indices page-fault, near indices silently copy adjacent kernel-heap bytes into rps->class/class2/caps/vclk, which drive which ps becomes boot_ps/uvd_ps and UVD clock programming (kernleak class). Harness-proven (fault at exactly the predicted bios+21874; fixed rejects). Bounds-check fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4190 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | verbatim replica; mode 4190 = nonClockInfoIndex=250 -> OOB read past bios image -> SIGSEGV at predicted address | 34.8 KB | view raw |
| build.sh | build-script | cc -O2 -Wall; harness_fixed with -DFIX_ALL | 65 B | view raw |
| run.sh | run-script | all modes, baseline + fixed | 120 B | view raw |
| build.log | build-log | final successful builds (guest cc 8.3) | 1018 B | view raw |
| run.log | run-log | decisive run with bios-relative OOB address computation | 3.5 KB | view raw |
| fix.diff | suggested-fix | bounds-check nonClockInfoIndex and clockInfoIndex against ucNumEntries with clean teardown | 1.3 KB | view raw |
| radeon_fix_apply.log | apply-log | patch(1) hunks on guest /usr/src (hunks 4-5 = this fix) | 808 B | view raw |
| radeon_module_build.log | fix-build-log | radeon.ko build with all four fixes, -Werror, MAKE_RC=0 | 139.5 KB | view raw |
| env.txt | environment | guest uname, cc, no radeon loaded | 568 B | view raw |
| VERDICT.md | verdict | full narrative incl. clockInfoIndex*ucEntrySize reach and state-walk overrun | 4.1 KB | β raw |
DF-4190 β VERDICT
Status: REPRODUCED (logic-level harness; kernel-level repro hardware-gated) Impact: kernel heap OOB read (crash when far out; silent kernel-heap disclosure into power-state fields when the neighbouring pages are mapped) Confidence: certain
Root cause
sumo_parse_power_table() (sys/dev/drm/radeon/sumo_dpm.c:1455-1528) trusts
three u8 indices from the VBIOS PowerPlayInfo image without any bounds check:
non_clock_array_index = power_state->v2.nonClockInfoIndex; /* :1495 u8 0..255 */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
&non_clock_info_array->nonClockInfo[non_clock_array_index]; /* :1496-1497 OOB */
...
for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) {
clock_array_index = idx[j]; /* :1509 u8 */
...
clock_info = (union pplib_clock_info *)
((u8 *)&clock_info_array->clockInfo[0] +
(clock_array_index * clock_info_array->ucEntrySize)); /* :1513-1515 OOB */
nonClockInfo[idx]strides 24 bytes (sizeof(ATOM_PPLIB_NONCLOCK_INFO), pptable.h:298-309): idx 255 reads up to ~6.1 KB past the array.clockInfo[idx]stridesucEntrySizeβ itself a u8 from the image (up to 255), so a single index reaches up to ~65 KB past the clock array.- the state walk itself (
power_state_offset += 2 + ucNumDPMLevels,:1524) advances by up to 257 bytes per state with no end-of-image bound, andidx[j](:1509) reads past the state entry wheneverucNumDPMLevelsexceeds the real array.
The arrays live inside atom_context->bios, a kmalloc'd copy of the GPU ROM
image; the reads run past the end of that heap allocation. Values fetched OOB
flow into rps->class/class2/caps/vclk/dclk (:1412-1418) β which
drive control flow (BOOT/UVD/THERMAL classification β which ps becomes
boot_ps, thermal-state patching) and hardware programming
(radeon_set_uvd_clocks). With a far index the read hits an unmapped page and
panics; with a near index it silently copies adjacent kernel-heap bytes into
state fields (kernleak class, same scoping as the DF-1401/1306/1140 twins).
Trigger / threat model
Crafted VBIOS ROM on a Sumo/Ontario/Palm APU (flashed card, replaced OPROM, VM GPU ROM passthrough). Parse happens at driver load; no privilege needed beyond the attacker controlling the ROM.
Harness proof (mode 4190)
16 KB crafted BIOS image, NonClockInfoArray placed near its end, state with
nonClockInfoIndex = 250:
bios image: 16384 bytes; nonClockInfo[250] lands at bios+21874 (5490 bytes PAST the end) *** SIGNAL 11 at address 0x8004c55b2 *** == the predicted OOB address
The fault address equals the computed &nonClockInfo[250] exactly β the
verbatim kernel index math dereferenced past the image buffer. (In the kernel
the same read at the same relative offset faults identically or, when the
adjacent heap is mapped, silently ingests neighbour bytes.)
Fix validation
harness_fixed 4190(-DFIX_DF4190: rejectnonClockInfoIndex >= ucNumEntries): "invalid nonClockInfoIndex 250", parse returns -22, exit 0, no OOB.fix.diff(both nonClockInfoIndex and clockInfoIndex bounds checks, with clean teardown) applied with the other three sumo fixes on guest/usr/src(radeon_fix_apply.loghunks 4 & 5):radeon.kobuilds under-Werror,MAKE_RC=0.
Kernel references
- sys/dev/drm/radeon/sumo_dpm.c:1495-1497 (nonClockInfoIndex, unchecked)
- sys/dev/drm/radeon/sumo_dpm.c:1509, :1513-1515 (clockInfoIndex Γ ucEntrySize, unchecked)
- sys/dev/drm/radeon/sumo_dpm.c:1524 (unbounded state-array walk)
- sys/dev/drm/radeon/sumo_dpm.c:1412-1418 (OOB bytes ingested into rps fields)
- sys/dev/drm/radeon/pptable.h:298-309, :424-464 (u8 indices / entry sizes)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedharness_fixed 4190 rejects the crafted index (DRM_ERROR, -EINVAL, exit 0); combined fix builds radeon.ko under -Werror (MAKE_RC=0)
run.log fixed section; radeon_fix_apply.log hunks 4-5; radeon_module_build.log MAKE_RC=0
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log '===== mode 4190 =====': 'nonClockInfo[250] lands at bios+21874 (5490 bytes PAST the end of the image)' then 'SIGNAL 11 at address 0x8004c55b2' (the same predicted address); fixed section: 'invalid nonClockInfoIndex 250', exit=0
PoC changes
wrote harness from scratch (no seed): crafted bios image + verbatim parse replica
Verified recommended fix
bounds-check nonClockInfoIndex and clockInfoIndex against the respective ucNumEntries (and ucEntrySize sanity) with clean teardown of already-parsed states, as in fix.diff
Verdict
Reproduced at logic level with the verbatim replica (harness mode 4190): nonClockInfoIndex (u8 from the VBIOS, never bounds-checked) indexes nonClockInfo[250] = 24250 bytes into a NonClockInfoArray placed near the end of the 16 KB bios image; the read lands 5490 bytes past the kmalloc'd buffer and SIGSEGVs at exactly the predicted address (kernel: page-fault panic for far indices; silent ingestion of adjacent kernel-heap bytes into rps->class/class2/caps/vclk for near indices β kernleak class). clockInfoIndexucEntrySize (up to ~65 KB) and the unbounded power_state_offset walk are the same unchecked family in the same function. Sumo twin of DF-1401 (trinity) / DF-1306 (kv) / DF-1140 (si). Kernel-level repro hardware-gated (no ATI APU on guest). Fix proven: harness_fixed rejects the index; radeon.ko builds with the fix under -Werror.
No comments yet.