OOB read/write past IB end in evergreen_dma_cs_parse (no per-packet bounds check)
Summary
evergreen_dma_cs_parse at evergreen_cs.c:2806: only checks p->idx<length_dw for header. Each DMA sub-case then indexes ib[idx+1..idx+8] and writes ib[idx+N]+=reloc->gpu_offset WITHOUT checking body fits in IB. DMA_PACKET_WRITE writes ib[idx+1]/ib[idx+2] (:2835-2840). DMA_PACKET_COPY sub_cmd 0x08 writes up to ib[idx+8]. Unlike CP path through radeon_cs_packet_parse (strict count+1+idx>=length_dw). IB is sa_bo sub-alloc of exact length_dw*4 bytes with 256B align -> OOB writes corrupt adjacent IB pool / sibling process IB. DRM_AUTH on HD 5000/6000 DMA ring. Fix: check idx+body_max<=length_dw per packet.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1246 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace replication of evergreen_dma_cs_parse; proves ib[idx+8] OOB read+write | 4.0 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -o harness harness.c | 93 B | view raw |
| run.sh | run-script | ./harness | 48 B | view raw |
| run.log | run-log | harness output: idx+8=11 >= length_dw=4 OOB | 395 B | view raw |
| fix.diff | suggested-fix | per-packet body-size bounds check in evergreen_dma_cs_parse | 1.5 KB | view raw |
| fix_build.log | build-log | radeon.ko builds clean with -Werror (fix compiles) | 24.5 KB | view raw |
| env.txt | environment | uname, cc, module list | 346 B | view raw |
| README.md | readme | how to reproduce | 533 B | β raw |
| VERDICT.md | verdict | full analysis | 3.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-1246 β reproduction
Userspace harness replicating evergreen_dma_cs_parse()'s header-only
bounds check and the resulting ib[idx+N] OOB read+write.
The live kernel trigger requires an AMD/ATI radeon GPU (absent from the QEMU guest); the harness proves the IB-body OOB primitive.
./build.sh && ./run.sh
Expected: PRIMITIVE CONFIRMED: idx+8 (11) >= length_dw (4); OOB read+write past IB.
Fix: fix.diff adds a per-packet body-size bounds check. Builds cleanly
into radeon.ko (-Werror). See VERDICT.md.
DF-1246 β radeon evergreen_dma_cs_parse IB body OOB read+write
Verdict (one line)
CONFIRMED REAL (source trace + harness primitive), NOT reproduced on audit guest (no AMD/ATI radeon GPU).
Finding
sys/dev/drm/radeon/evergreen_cs.c:evergreen_dma_cs_parse() bounds-checks
only the header word of each DMA packet (evergreen_cs.c:2806,
if (p->idx >= ib_chunk->length_dw)) but then reads and writes body
words ib[idx+N] for N up to 8 (and reads via radeon_get_ib_value(p, idx+N))
with no check that the body fits in the IB. A crafted indirect buffer whose
packet header is near the end causes idx+1..idx+8 to index past the ib
allocation: adjacent kernel memory is read (leak) and written back
(corruption).
Mechanism (path:line)
evergreen_cs.c:2806β loop entry guards only the header:if (p->idx >= ib_chunk->length_dw) return -EINVAL;evergreen_cs.c:2811-2812βidx = p->idx; header = ib[idx];- Body accesses with NO bounds check, e.g.:
-
evergreen_cs.c:2830ib[idx+1] += (u32)(dst_reloc->gpu_offset >> 8);(DMA_PACKET_WRITE tiled) -evergreen_cs.c:2897radeon_get_ib_value(p, idx+8)(COPY L2T read) -evergreen_cs.c:2899ib[idx+8] += upper_32_bits(dst_reloc->gpu_offset);(COPY L2T write) radeon_get_ib_value(p, idx)isp->ib.ptr[idx]with no check (sys/dev/drm/radeon/radeon.h:1098-1105).- Contrast the CP path, which strictly checks
count+1+idx >= length_dwviaradeon_cs_packet_parseβ the DMA path omits this.
Why not reproduced on the audit guest
radeon is a loadable DRM module (/boot/kernel/radeon.ko), not in
X86_64_GENERIC, and the QEMU/KVM guest has no AMD/ATI GPU (no PCI
GPU, no /dev/dri/renderD128). The CS-parse path is reached only from the
radeon DRM ioctl after a GPU attach. There is no GPU to attach, so the path
is dead at runtime on this guest. The realistic trigger is an unprivileged
local user on a machine with an affected radeon GPU submitting a crafted
DMA command stream β a legitimate local-privilege-escalation / kernel-memory-
corruption threat on such hardware, but not exercisable here.
Primitive proof (harness)
harness.c replicates the parse loop with a 4-word IB and a header at the
last word, then emulates the COPY L2T detile access (ib[idx+8]). Result:
Header bounds check (evergreen_cs.c:2806) PASSED: idx=3 < length_dw=4 idx+8 = 11, length_dw = 4 ib[idx+8] read = 0x00000000 (OUT OF BOUNDS: idx+8=11 >= length_dw=4) ib[idx+8] written back = 0x11111111 (heap word corrupted) PRIMITIVE CONFIRMED: idx+8 (11) >= length_dw (4); OOB read+write past IB.
Fix
fix.diff adds a per-packet body-size bounds check after the header is
parsed (evergreen_cs.c): it computes the maximum body word count need
for each cmd/sub_cmd (2..10 words) and returns -EINVAL if
idx + need > ib_chunk->length_dw before any ib[idx+N] access. This
mirrors the strict check the CP path already has. Validated: builds
cleanly into radeon.ko with -Werror.
Reproduce
ssh dfbsd-maxx; cd poc/DF-1246 && cc -O2 -Wall -o harness harness.c && ./harness
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. evergreen_dma_cs_parse header-only bounds check -> ib[idx+8] OOB read+write. radeon not in GENERIC.
No comments yet.