β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1725

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1725 Β· 10 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative: mechanism, Phase 6, fix
↓ download 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->relocs allocated for nrelocs entries.
  • sys/dev/drm/radeon/radeon_cs.c:865 β€” bound check is if (idx >= relocs_chunk->length_dw) return -EINVAL; β€” admits any idx in [0..length_dw-1].
  • sys/dev/drm/radeon/radeon_cs.c:878 β€” *cs_reloc = &p->relocs[(idx / 4)]; uses integer division; for length_dw not a multiple of 4, e.g. 5, nrelocs = 1 but idx = 4 is admitted by the check, and idx/4 = 1 indexes one past end of p->relocs.
  • Callers (r600_cs.c, evergreen_cs.c) then deref reloc->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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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.
↓ fix.diffradeon.ko module rebuild (loadable .ko) - applied fix.diff (and DF-1727/1753/1754/1783 in the same source tree), 'make' rc=0, radeon.ko 2029704 bytes built clean

Confirmed kernel references

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.