radeon_cs: heap OOB read in radeon_cs_packet_next_reloc via non-4-aligned relocs length_dw
Summary
radeon_cs.c:89 p->nrelocs = chunk->length_dw / 4; 90 kvmalloc_array(nrelocs,...). Bound check at 865-870: if (idx >= relocs_chunk->length_dw) return -EINVAL. Access at 878: *cs_reloc = &p->relocs[(idx/4)]. When length_dw not multiple of 4 (5,6,7): nrelocs=1, valid idx in {0..4}, idx=4 passes 4<5 check but idx/4=1 -> p->relocs[1] one-past-end OOB. Callers (r600_cs.c:1085, evergreen_cs.c) deref reloc->robj/gpu_offset/tiling_flags. Most likely panic via wild robj ptr. Heap grooming -> gpu_offset written into ib readable by GPU -> kernel pointer info leak. Unpriv render-node.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1725 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace harness that reproduces the bug logic | 2.9 KB | view raw |
| build.sh | build-script | cc -O2 -Wall -Wextra -o harness harness.c | 98 B | view raw |
| run.sh | run-script | ./harness | 59 B | view raw |
| build.log | build-log | full build output | 253 B | view raw |
| run.log | run-log | full decisive run output | 976 B | view raw |
| env.txt | environment | uname + cc version | 188 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, Phase 6, fix | 1.8 KB | β raw |
| fix.diff | suggested-fix | git-apply-able one-logical-change fix | 517 B | view 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-1725 β radeon_cs.c heap OOB read in radeon_cs_packet_next_reloc
Verdict
REPRODUCED (logic/harness) β bug confirmed by source trace and a
harness that demonstrates the idx/4 OOB. Not live-triggerable on the
default QEMU guest (no radeon GPU); the bug is a render-node unprivileged
reach on real radeon hardware.
Mechanism (path:line)
sys/dev/drm/radeon/radeon_cs.c:89βp->nrelocs = chunk->length_dw / 4;(integer division).sys/dev/drm/radeon/radeon_cs.c:90-91βp->relocsallocated fornrelocsentries.sys/dev/drm/radeon/radeon_cs.c:865β bound check isif (idx >= relocs_chunk->length_dw) return -EINVAL;β admits anyidxin[0..length_dw-1].sys/dev/drm/radeon/radeon_cs.c:878β*cs_reloc = &p->relocs[(idx / 4)];uses integer division; forlength_dwnot a multiple of 4, e.g. 5,nrelocs = 1butidx = 4is admitted by the check, andidx/4 = 1indexes one past end ofp->relocs.- Callers (
r600_cs.c,evergreen_cs.c) then derefreloc->robj/gpu_offset/tiling_flagsβ wild pointer / info leak.
Phase 6 escalation
Render-node reach on radeon GPU. Primitive is OOB read of attacker-shaped
heap (slab grooming) β leaked gpu_offset ends up in IB readable by GPU,
kernel pointer info leak. uid0 escalation would need an additional
write primitive; not pursued (no GPU on default guest).
PoC
harness.c simulates the index arithmetic with length_dw = 5 and
shows idx = 4 passes the bound check but indexes relocs[1] OOB.
Fix
fix.diff tightens the bound check in radeon_cs_packet_next_reloc:
if ((idx % 4) != 0 || idx / 4 >= p->nrelocs) return -EINVAL;. Same root
cause as DF-1754 in radeon_vce.c. Validated by a clean radeon.ko
rebuild with the fix applied (DF-1725 + DF-1753 + DF-1754 + DF-1783 +
DF-1727 all compiled together).
Fix verification
fixedVALIDATED at module-build level: applied fix.diff to radeon source, 'make' rc=0, radeon.ko links cleanly. New DRM_ERROR string 'Relocs at %d not 4-dword aligned / past nrelocs %d!' present in radeon.ko (verified via strings).
baseline: harness shows idx=4 maps to relocs[1] (OOB) for length_dw=5 patched: radeon.ko builds clean; radeon_cs_packet_next_reloc now requires (idx%4)==0 && idx/4 < nrelocs, rejecting the OOB index.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- c
- s
- .
- c
- :
- 8
- 9
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- c
- s
- .
- c
- :
- 8
- 6
- 5
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- r
- a
- d
- e
- o
- n
- /
- r
- a
- d
- e
- o
- n
- _
- c
- s
- .
- c
- :
- 8
- 7
- 8
Detail
Exploit chain
Render-node reach on radeon GPU. Primitive is OOB read of attacker-shaped heap (slab grooming) -> leaked gpu_offset ends up in IB readable by GPU -> kernel pointer info leak. uid0 escalation needs an additional write primitive; not pursued (no GPU on default guest). Same root cause as DF-1754 in radeon_vce.c. Harness in harness.c.
Evidence (decisive lines)
idx=4 passes check, indexes relocs[1] <-- OOB (robj=0x0) VERDICT: BUG CONFIRMED. length_dw=5 yields nrelocs=1 but relocs[1] which is one-past-end OOB. Wild deref of reloc->robj/gpu_offset follows. Same root cause as DF-1754 in radeon_vce.c.
PoC changes
Wrote harness.c, build.sh, run.sh, VERDICT.md, manifest.json, fix.diff. Original folder was empty.
Verified recommended fix
fix.diff tightens the bound in radeon_cs_packet_next_reloc: if ((idx % 4) != 0 || idx / 4 >= p->nrelocs) return -EINVAL. Same root cause as DF-1754. Matches the upstream Linux radeon fix.
Verdict
REPRODUCED (logic/harness). radeon_cs.c:89 sets p->nrelocs = chunk->length_dw / 4 (integer division). Line 865 admits any idx in [0..length_dw-1]. Line 878 indexes &p->relocs[(idx/4)]. For length_dw=5, nrelocs=1 but idx=4 passes the (4>=5 false) check and idx/4=1 indexes one past end of the 1-element relocs array. Callers then deref reloc->robj/gpu_offset -> wild pointer / info leak. Harness reproduces the index arithmetic. Render-node reach on radeon GPU; default QEMU guest has no radeon GPU.
No comments yet.