# 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)

1. **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 reads `ib[pkt->idx+1]` (si.c:4521) — one dword past the
   allocation — and COND_WRITE would read `ib[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.
2. **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).
3. **Fix validation**: `-DFIXED` (fit check mirroring radeon_cs.c:768 +
   unsigned-safe `i + 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.
