Heap buffer overflow via state_index/i index mismatch in radeon_atombios_parse_power_table_6
Summary
radeon_atombios_parse_power_table_6 at :2698-2735: loop allocates clock_info per index i (state ucNumDPMLevels) but writes via state_index which lags when prior state has zero valid clock modes. state_index<i -> writes to power_state[state_index].clock_info sized for a DIFFERENT state -> heap overflow. Attacker crafts VBIOS with state[0] having 1 invalid DPM level (stall state_index) then state[1] with N valid levels -> overflow power_state[0].clock_info[1..N] by (N-1)*sizeof(pm_clock_info) bytes. Malicious PCIe GPU/VFIO passthrough at driver probe. Fix: use i instead of state_index for all writes within loop body.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1198 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | replica of power_table_6 loop: alloc[i] vs write[state_index] mismatch -> overflow | 5.1 KB | view raw |
| VERDICT.md | verdict | full narrative + fix (alloc at state_index, free on skip) | 4.0 KB | β raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 172 B | view raw |
| run.sh | run-script | ./harness | 66 B | view raw |
| run.log | run-log | 3 OOB clock_info writes, 48-byte canary clobber | 732 B | view raw |
| env.txt | environment | uname, cc version | 418 B | view raw |
| fix.diff | suggested-fix | allocate clock_info at state_index (not i); free on zero-valid-modes skip | 1.8 KB | view raw |
| fix_build.log | build-log | radeon.ko rebuilt with both radeon fixes, -Werror, rc=0 | 143.5 KB | view raw |
| README.md | readme | human reproduce doc | 1.4 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-1198 β radeon power_table_6 state_index/i mismatch -> heap overflow
File: sys/dev/drm/radeon/radeon_atombios.c:2697-2735
Class: memory corruption (hardware/firmware-attacker; malicious CISS PCI device
or malicious VBIOS at driver attach). No local-unprivileged syscall trigger on the
audit guest (no HP Smart Array / no AMD GPU present).
Reproduce
./build.sh && ./run.sh
What the harness does
harness.c is a faithful userspace replica of the kernel parsing routine cited
above, fed crafted controller/VBIOS data that the real malicious device would
supply. It demonstrates the out-of-bounds access / overflow / underflow using the
real kernel macros and struct sizes, with a canary or computed-index check to
make the OOB observable without needing the hardware.
Expected output
A [BUG REPRODUCED] (or UNDERFLOW for DF-1199) marker plus the computed
out-of-range index / overflow byte count / underflowed loop count. See run.log
for the captured decisive run.
Fix
See fix.diff (git-apply-able) and VERDICT.md. The fix was validated to
compile (module rebuilt with -Werror) β see fix_build.log. No live-kernel
trigger exists on the guest, so the fix is validated at the
applies + compiles + closes-the-code-path level.
Artifacts
VERDICT.md (full narrative), harness.c, build.sh, run.sh,
run.log, env.txt, fix.diff, fix_build.log, manifest.json.
DF-1198 β radeon_atombios_parse_power_table_6 state_index/i mismatch (heap overflow)
Verdict
REPRODUCED (harness) β real bug confirmed by source trace + userspace replica.
Impact class: kernel heap overflow of power_state[].clock_info. No
local-unprivileged trigger on the audit guest (no AMD GPU); trigger requires a
malicious VBIOS (malicious PCIe GPU / VFIO passthrough) at driver probe.
uid=0 chain N/A β hardware/firmware-attacker class.
Mechanism (confirmed path:line)
radeon_atombios_parse_power_table_6() (sys/dev/drm/radeon/radeon_atombios.c)
loops over the VBIOS power-state array. It allocates clock_info keyed on
the loop index i, but writes through a second index state_index that
only advances when a state yields β₯1 valid clock mode:
sys/dev/drm/radeon/radeon_atombios.c:2711-2714βrdev->pm.power_state[i].clock_info = kzalloc(sizeof(radeon_pm_clock_info) * (ucNumDPMLevels ? ucNumDPMLevels : 1), GFP_KERNEL);(allocation sized for state i),sys/dev/drm/radeon/radeon_atombios.c:2722-2724βradeon_atombios_parse_pplib_clock_info(rdev, state_index, mode_index, clock_info)which writespower_state[state_index].clock_info[mode_index](radeon_atombios.c:2484etc.),sys/dev/drm/radeon/radeon_atombios.c:2729-2732β the no-DPM branch also writespower_state[state_index].clock_info[0],state_indexincrements only in theif (mode_index)branch (radeon_atombios.c:2736-2739).
When state[0] has DPM levels that all fail validation (mode_index stays 0 β
state_index does not advance), state[1]'s writes land in
power_state[state_index==0].clock_info β a buffer sized for state[0]'s single
DPM level. State[1] writing N valid modes overflows it by
(N β state[0].alloc) * sizeof(struct radeon_pm_clock_info) bytes into the
adjacent heap. The allocation (keyed on i) and the writes (keyed on
state_index) diverge precisely when a prior state stalls state_index.
Harness proof (run.log)
harness.c replicates the loop with VBIOS state[0]={1 DPM level, invalid} and
state[1]={4 DPM levels, all valid}, with a canary slot after state[0]'s
1-element allocation:
state i=1 writes via state_index=0 (alloc for state[0]=1 entries) [!] OOB WRITE clock_info[1] into buffer sized 1 (state_index=0) state i=1 writes via state_index=0 (alloc for state[0]=1 entries) [!] OOB WRITE clock_info[2] into buffer sized 1 (state_index=0) state i=1 writes via state_index=0 (alloc for state[0]=1 entries) [!] OOB WRITE clock_info[3] into buffer sized 1 (state_index=0) [BUG REPRODUCED] 3 OOB clock_info writes, victim canary clobbered 48 bytes
Why not a live-kernel trigger / no uid0 chain
radeon (/boot/kernel/radeon.ko) attaches only to AMD/ATI Radeon PCIe GPUs
(none on the guest β never loaded). The power table is parsed from the GPU's
VBIOS (atom_parse_data_header) at probe β not reachable from any unprivileged
syscall. A malicious VBIOS via VFIO passthrough or a malicious physical GPU is
the realistic threat. Hardware/firmware-attacker class; no local-privesc chain.
Fix (fix.diff)
Make allocation and writes use the same index (state_index): allocate
power_state[state_index].clock_info (instead of [i]), and when a state
yields zero valid modes, free its allocation so the state_index slot is reused
by the next iteration (no leak, no divergence). This preserves the compaction
semantics while closing the overflow. Supersedes the finding's "use i instead of
state_index" proposal β that alternative would break the state_index-based
compaction the rest of the function relies on (post-loop iteration at
radeon_atombios.c:2744 and the returned state_index).
Fix validation
radeon.ko rebuilt from patched source (DF-1198/1199 applied) compiled with
-Werror and linked (/usr/obj/usr/src/sys/dev/drm/radeon/radeon.ko, 2030904 B;
radeon_atombios.o rebuilt, 48448 B). fix_status: not_testable (no AMD GPU for
a live trigger; validated applies + compiles + closes the path).
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). radeon power_table_6 alloc by i but write by state_index -> clock_info OOB. radeon not in GENERIC.
No comments yet.