si_ib_parse() lacks packet-fits-in-IB validation -> OOB reads up to 6 dwords past the IB sub-allocation + WRITE_DATA (pkt->count - 2) underflow spin
Summary
The VM-path CS parser walks with only 'while (idx < ib->length_dw)' and never checks the packet fits - unlike the non-VM twin (radeon_cs.c:768). Dispatched handlers read fixed offsets regardless of pkt->count (COND_WRITE ib[idx+5], CP_DMA ib[idx+4], etc.). A TYPE3 header as the last IB dword reads up to 24 bytes past ib->ptr[length_dw]; the IB is an exact-size radeon_sa_bo sub-allocation in the GTT pool BO. Additionally 'for (i = 0; i < (pkt->count - 2); i++)' underflows for count<2 (int -2 -> ~4 billion unsigned bound): with a context-space start register the walk spins ~2^30 iterations per ioctl - seconds of kernel CPU, repeatable and parallel (no mutex held during parse). VERIFIED: harness 3/3 - WRITE_DATA count=0 -> loop bound 4294967294 + STILL SPINNING after 5M iterations; lone TYPE3 COND_WRITE header at length_dw=1 -> SIGSEGV on first handler read past the allocation; -DFIXED rejects cleanly. Dormant on stock DragonFly SI (accel_working=false unconditionally -> radeon_cs_ioctl returns EBUSY) but live the moment that one-line local hack is reverted or on statement-identical upstream trees; evergreen_ib_parse shares the pattern for pre-SI VM asics. Local user with DRM device open: DoS + weak 1-bit oracle; read-only.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3592 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ib_parse_oob.c | β | 7.8 KB | view raw | |
| build.sh | β | 104 B | view raw | |
| run.sh | β | 92 B | view raw | |
| build.log | β | 150 B | view raw | |
| run.log | β | 2.5 KB | view raw | |
| run.fixed.log | β | 725 B | view raw | |
| env.txt | β | 399 B | view raw | |
| fix.diff | β | 3.8 KB | view raw | |
| VERDICT.md | β | 3.9 KB | β raw |
DF-3592 β VERDICT
Status: reproduced (logic-level, on-guest) / impact: local DoS-class OOB read + CPU-stall spin; dormant on stock DFly SI behind accel_working=false
Why logic-level
Reaching si_ib_parse requires (a) an SI GPU β this guest is virtio-only
(no 0x1002 PCI devices, env.txt) β and (b) on DragonFly specifically,
rdev->accel_working to be true, which si.c:6906-6912 unconditionally
sets to false (#ifdef __DragonFly__ "GPU acceleration disabled for now"),
making radeon_cs_ioctl return -EBUSY (radeon_cs.c:662). Neither is
satisfiable here, so the parse loop, its bounds arithmetic, and the exact
dword sequence a malicious CS chunk would deliver were transcribed
statement-faithfully into ib_parse_oob.c, with the IB placed at the end
of an mmap'd page followed by PROT_NONE β an exact analogue of the
kernel's exact-size sa_bo sub-allocation (radeon_sa.c:205).
What was demonstrated (guest, 3/3 runs)
- Scenario B β OOB read past
length_dw: a lone TYPE3 COND_WRITE header (count=0) as the final dword of a length_dw=1 IB. In-kernel the gfx_check entry readsib[pkt->idx+1](si.c:4521) β one dword past the allocation β and COND_WRITE would readib[pkt->idx+6](si.c:4595), six past. The harness faults at the first handler read (SIGSEGV, exit 42), proving the read crosses the allocation boundary at the exact index the kernel would touch. - Scenario A β WRITE_DATA count-underflow spin: count=0 β loop bound
(pkt->count - 2)prints as 4294967294; the register walk from a context-space start register (0x28000, "always valid", si.c:4400) is still spinning after 5,000,000 harness iterations (capped with a report); the kernel walk continues to ~2^30 iterations before the register wraps below 0x28000 β seconds of per-ioctl kernel CPU, repeatable and parallel across threads (parse holds no mutex, radeon_cs.c:549-559). - Fix validation:
-DFIXED(fit check mirroring radeon_cs.c:768 + unsigned-safei + 2 < pkt->count) rejects the trailing packet with -EINVAL before any handler read, suppresses the spin to 0 iterations, exit 0.
Impact ceiling (honest)
- Read-only OOB (CWE-125): worst case 24 bytes past
ib->ptr[length_dw]. In-pool reads hit neighboring IB data (attacker-influenced anyway); past-pool-end reads hit whatever follows the GTT BO's kernel mapping β silent garbage or a panic on an unmapped boundary. No write primitive; the read values only gate register whitelisting (weak oracle, 1 bit per ioctl), so confidentiality impact is negligible. - The spin is a genuine local DoS primitive when the path is live.
- On stock DragonFly (accel_working=false at si.c:6911) both are dormant β reachable only if that DFly-local hack is reverted (one line) or on non-DFly trees carrying the same statement-identical parser (upstream Linux radeon si.c). Rated Medium for the code defect with the gating documented; would be Low on a strictly stock-config impact scale.
Distinctness
Not DF-1130 (that is firmware-header OOB in the new-format loaders). This is the userspace-reachable CS parser. The evergreen_ib_parse sibling (evergreen_cs.c:3519, pre-SI VM asics + SI DMA rings via evergreen_dma_ib_parse) shares the missing fit check β noted here, filed out of scope for this file.
Bottom line
Certain by source (the non-VM twin check at radeon_cs.c:768 proves the authors knew the invariant; the VM path just never got it) and reproduced logic-level on-guest 3/3: OOB read at the exact predicted dword index + underflow spin. fix.diff closes all three facets (fit, min-count, loop bound).
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_testableLogic-level fix validation on-guest: FIXED harness variant (fit check + min counts + unsigned-safe bound, exactly fix.diff) rejects the trailing COND_WRITE before any handler read and reduces the WRITE_DATA walk to 0 iterations; baseline OOB fault + spin reproduce 3/3. A patched-kernel build could not be runtime-tested: no SI GPU on this guest and stock DFly disables SI accel (accel_working=false, si.c:6911), so the CS-ioctl path cannot execute in-kernel here.
run.fixed.log (rejection + 0-iteration walk, exit 0) vs run.log (SIGSEGV + 5M-iteration spin, exit 42); fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log: scenario A 'loop bound (pkt->count - 2) = 4294967294' + 'STILL SPINNING after 5,000,000 iterations'; scenario B 'SIGSEGV ... OOB READ PROVEN: handler touched a dword beyond ib->length_dw' (exit 42); run.fixed.log: 'FIXED: packet (idx 0, count 0) exceeds ib length 1 - rejected' + 'unsigned-safe walk bound -> 0 iterations' (exit 0)
PoC changes
no seed; wrote statement-faithful transcription of si_ib_parse + gfx_check reading cases + si_vm_reg_valid with a guard-page IB analogue; reordered scenarios so the non-faulting spin demo precedes the faulting OOB demo; unbuffered stdout for the SIGSEGV handler report
Verified recommended fix
In si_ib_parse() reject packets where pkt.count+2 > ib->length_dw - idx (radeon_cs.c:768 parity), enforce per-opcode minimum counts (COND_WRITE>=5, CP_DMA>=4, COPY_DATA/COPY_DW>=3, WRITE_DATA>=2), and make the WRITE_DATA walk bound unsigned-safe (i + 2 < pkt->count) in gfx and compute handlers
Verdict
Statement-faithful on-guest harness (3/3): si_ib_parse() dispatches handlers with no packet-fits-in-IB check (loop condition only 'while (idx < ib->length_dw)', si.c:4776; the non-VM twin has one at radeon_cs.c:768). A TYPE3 header as the last IB dword makes gfx_check read ib[pkt->idx+1] immediately past the exact-size sa_bo sub-allocation (guard-page fault at the exact predicted dword; COND_WRITE reaches ib[pkt->idx+6] = up to 24 bytes past). WRITE_DATA with pkt->count<2 underflows (count-2 -> 4294967294) into a register walk that still spins after 5M iterations in the harness (~2^30 in-kernel, seconds of ioctl CPU, parallelizable). Read-only primitive, no write path, weak 1-bit oracle at best. Dormant on stock DragonFly SI: si.c:6911 sets accel_working=false (#ifdef DragonFly) so radeon_cs_ioctl returns -EBUSY; live on accel re-enable (one-line local hack) and on statement-identical non-DFly code. evergreen_ib_parse (evergreen_cs.c:3519) shares the pattern - noted out-of-file.
No comments yet.