evergreen_ib_parse()/evergreen_vm_packet3_check(): no packet-fits-in-IB validation + fixed-offset payload reads without pkt->count checks β OOB reads up to 5 dwords past the IB sub-allocation + kernel-log disclosure oracle (DF-3592/DF-3764 family, pre-SI VM path)
Summary
The cayman-GFX-ring .ib_parse, reached from the unprivileged RADEON_CS ioctl whenever cs_flags has RADEON_CS_USE_VM, hand-rolls packet walking: evergreen_ib_parse advances idx += pkt.count+2 without ever checking the packet fits ib->length_dw (contrast radeon_cs_packet_parse), and evergreen_vm_packet3_check reads ib[pkt->idx+1] unconditionally plus ib[idx+3..5] for COPY_DW/CP_DMA/COND_WRITE with no pkt->count validation. A TYPE3 header as the last IB dword forces reads up to 5 dwords past the IB end, inside the kernel-mapped IB pool (neighbouring users' IBs); the OOB dword is printed to the log by DRM_ERROR('Invalid register 0x%x in CS') - a one-dword-per-CS disclosure oracle. Local unprivileged user with a DRM-authenticated fd on cayman (VM enabled at boot).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3837 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 1.4 KB | β raw | |
| VERDICT.md | file | 3.4 KB | β raw | |
| build.log | file | 98 B | view raw | |
| build.sh | file | 82 B | view raw | |
| env.txt | file | 286 B | view raw | |
| fix.diff | file | 1.3 KB | view raw | |
| manifest.json | file | 750 B | view raw | |
| run.log | file | 959 B | view raw | |
| run.sh | file | 105 B | view raw | |
| trigger.c | file | 4.7 KB | view raw | |
| verdict.json | file | 3.2 KB | view raw |
DF-3837 β evergreen_ib_parse()/evergreen_vm_packet3_check(): no packet-fits-in-IB validation + fixed-offset payload reads without pkt->count checks -> OOB reads up to 5 dwords past the IB sub-allocation in the kernel-mapped IB pool, with the OOB-read dword echoed to the kernel log ("Invalid register 0x%x in CS").
WHAT TO LOOK AT trigger.c userspace RADEON_CS ioctl PoC (VM path): a trailing COND_WRITE packet with count=0 at IB end -> handler reads ib[idx+5] (evergreen_cs.c:3407) past the IB. fix.diff packet-fit check + per-opcode count checks. verdict.json machine verdict (status: inconclusive β no GPU on guest). run.log honest guest run: builds clean, /dev/dri absent (virtio-only).
BUILD cc -O2 -o trigger trigger.c RUN ./trigger /dev/dri/card0 EXPECT On real cayman hardware with VM enabled (radeon_cs.c:546-556 -> radeon_ring_ib_parse -> evergreen_ib_parse, registered at radeon_asic.c:1619 for the cayman gfx ring): the CS is accepted or rejected after the OOB read; dmesg then shows "Invalid register 0x???????? in CS" where the value came from past the IB end (other users' IBs in the pool). On the audit QEMU guest: "No such file or directory".
FAMILY DF-3592 (si_ib_parse), DF-3764 (r100_packet3_check); this is the evergreen/cayman instance flagged in DF-3666's negative notes.
DF-3837 VERDICT β status: inconclusive (hardware-gated), source-level: CERTAIN
Reachability chain (all cited in sys/dev/drm/radeon/):
1. Unprivileged local user with a DRM-authenticated fd issues
DRM_IOCTL_RADEON_CS (nr 0x26, dispatched on cmd&0xff β drm_ioctl.c:809).
2. radeon_cs.c:546-556 radeon_cs_parser_chase/relocate: when
cs_flags & RADEON_CS_USE_VM (flags chunk value 0x02, fully user-chosen),
radeon_ring_ib_parse() -> radeon_asic.c:1619 evergreen_ib_parse() for
the cayman GFX ring. VM is enabled at boot on cayman.
3. evergreen_cs.c:3525-3550: evergreen_ib_parse() hand-rolls packet
parsing β it NEVER checks idx + pkt.count + 2 <= ib->length_dw
(contrast radeon_cs.c:768 which the non-VM path uses).
4. evergreen_vm_packet3_check():
:3350 u32 idx_value = ib[pkt->idx + 1]; unconditional
:3405-3410 COND_WRITE reads ib[idx+5] β NO count check (needs >= 5)
:3412-3417 COPY_DW reads ib[idx+3] β NO count check (needs >= 3)
:3434-3436 CP_DMA reads ib[idx+1], ib[idx+2], ib[idx+4]
β NO count check
=> a TYPE3 packet header placed as the LAST dword of the IB makes the
parser read up to 5 dwords past ib->length_dw. ib->ptr is a
sub-allocation of the kernel-mapped IB pool (RADEON_IB_POOL), so the OOB
reads hit neighbouring IBs (other processes' command streams) and pool
slack.
5. Disclosure: evergreen_cs.c:3341 DRM_ERROR("Invalid register 0x%x in CS",
reg) prints the OOB-read value (reg = ib[idx+5]*4) to the log β a
read-out oracle over adjacent pool memory, one dword per crafted CS
(dmesg readable). Values also gate which packets are accepted
(validation-bypass input).
Why not reproduced: the audit QEMU guest is virtio-only β no /dev/dri, no drm subsystem (run.log: "ls: /dev/dri: No such file or directory", "sysctl: unknown oid 'hw.drm'"). The PoC compiles clean on the guest (build.log) and fails exactly at open() with ENOENT. No radeon hardware is available anywhere in this audit environment, so the OOB read could not be observed at runtime. The code path, however, is unconditional once the CS reaches evergreen_ib_parse with a short trailing packet β there is no plausible reading of the source under which the reads are bounded (idx+1..idx+5 are evaluated before any count validation, and the loop itself advances by pkt.count+2 without a fit check).
Severity: Medium (kernel OOB read within the IB pool + log disclosure oracle; not a write primitive). Same family/severity as DF-3592 (si.c, Medium/certain) and DF-3764 (r100.c, Medium/certain).
Fix: fix.diff β (a) packet-fit check in evergreen_ib_parse before dispatch; (b) require the spec'd pkt->count for COND_WRITE/COPY_DW/CP_DMA (and guard the unconditional ib[idx] with idx+1 < length_dw). Fix validation: not possible on this guest (no GPU) β fix_status not_applicable; the added checks are structurally identical to the ones radeon_cs_packet_parse() already enforces on the non-VM path.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableFix validation impossible on this guest: the vulnerable device (AMD radeon CS / Intel 2200BG) does not exist, so neither baseline nor patched kernel can exercise the path. fix.diff authored against the read-only sys/ tree, never applied.
fix.diff in this pack; structurally mirrors the bounds checks the same driver family already enforces elsewhere (radeon_cs_packet_parse fit check; iwn ring-index masking).
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md (what to look at), VERDICT.md (line-by-line trace), trigger.c/mkbadfw.c, build.log (clean guest compile, gcc 8.3), run.log (honest ENOENT / generator RUN_OK), env.txt (guest has no DRI, no iwi NIC), fix.diff
PoC changes
PoC authored fresh for this finding (cross-file family seed): raw DRM ioctl ABI (cmd low byte 0x66 dispatches RADEON_CS on DF drm_ioctl.c:809), chunk structs and packet encodings verified against sys/dev/drm/include/uapi/drm/radeon_drm.h; iwi mkbadfw.c emits the v3 combined-image header per if_iwireg.h:161-166.
Verified recommended fix
Add idx+1<length_dw guard, pkt.idx+pkt.count+2<=length_dw fit check before dispatch, and exact pkt->count requirements for COND_WRITE/COPY_DW/CP_DMA in the VM parser.
Verdict
Source-level certain, runtime inconclusive: the audit QEMU guest is virtio-only (no /dev/dri, hw.drm absent), so the CS ioctl cannot be exercised. The VM-path IB parser hand-rolls packet walking with no fit check (unlike radeon_cs_packet_parse at radeon_cs.c:768) and evergreen_vm_packet3_check reads ib[pkt->idx+1] unconditionally plus ib[idx+3..idx+5] for COND_WRITE/COPY_DW/CP_DMA without any pkt->count validation, so a TYPE3 header as the last IB dword forces reads up to 5 dwords past ib->length_dw inside the kernel-mapped IB pool; the OOB dword is echoed to the log by DRM_ERROR("Invalid register 0x%x in CS"). PoC compiles clean on the guest and fails at open(/dev/dri/card0) with ENOENT, as documented in run.log.
No comments yet.