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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1246 Β· 11 files
FileTypeDescriptionSize
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
README.md readme how to reproduce
↓ download 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.

VERDICT.md verdict full analysis
↓ download raw

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)

  1. evergreen_cs.c:2806 β€” loop entry guards only the header: if (p->idx >= ib_chunk->length_dw) return -EINVAL;
  2. evergreen_cs.c:2811-2812 β€” idx = p->idx; header = ib[idx];
  3. Body accesses with NO bounds check, e.g.: - evergreen_cs.c:2830 ib[idx+1] += (u32)(dst_reloc->gpu_offset >> 8); (DMA_PACKET_WRITE tiled) - evergreen_cs.c:2897 radeon_get_ib_value(p, idx+8) (COPY L2T read) - evergreen_cs.c:2899 ib[idx+8] += upper_32_bits(dst_reloc->gpu_offset); (COPY L2T write)
  4. radeon_get_ib_value(p, idx) is p->ib.ptr[idx] with no check (sys/dev/drm/radeon/radeon.h:1098-1105).
  5. Contrast the CP path, which strictly checks count+1+idx >= length_dw via radeon_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_testable

compile+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.