# VERDICT — DF-4278: R300_ZB_ZPASS_ADDR reloc patch lacks bo-bounds validation → GPU OOB write

**status: untested | reproduced: 0 | impact: none (not demonstrated on guest) | confidence: likely**

## Bottom line

The code defect is certain and provable by reading the tree; runtime
verification is impossible on the audit guest because it has no ATI/radeon
GPU (QEMU virtual VGA only — `env.txt`). This is a hardware-gated finding
(r300..rv570 class asics served by `r300.c`).

## Root cause (path:line)

`sys/dev/drm/radeon/r300.c:1073-1082`, in `r300_packet0_check()`:

```c
	case R300_ZB_ZPASS_ADDR:
		r = radeon_cs_packet_next_reloc(p, &reloc, 0);
		if (r) { ... return r; }
		ib[idx] = idx_value + ((u32)reloc->gpu_offset);   /* <-- no bound check */
		break;
```

`idx_value` is the raw u32 the user placed in the IB after the
`PACKET0(0x4f5c, 0)` header. The patch makes the GPU occlusion-query counter
address = `reloc_bo_gpu_offset + idx_value`. Every sibling address register
(COLOROFFSET/DEPTHOFFSET/AARESOLVE_OFFSET/TX_OFFSET) stores `offset` + `robj`
into `track` and `r100_cs_track_check()` later enforces
`offset + pitch*cpp*maxy <= radeon_bo_size(robj)` (r100.c:2264, 2283, 2303).
ZPASS_ADDR stores nothing and validates nothing → the user picks ANY u32
offset past the relocation BO's base, and the GPU's SC unit increments a
4-byte counter there on every passing pixel.

## Attack path

Unprivileged user → open `/dev/dri/card0` (render access) →
`DRM_IOCTL_RADEON_CS` (`radeon_cs_ioctl`, radeon_cs.c) → chunk IB parsed by
`r300_cs_parse` (r300.c:1274) → `r100_cs_parse_packet0` (r100.c:1375, 0x4f5c
is in `r300_reg_safe_bm`) → `r300_packet0_check` case `R300_ZB_ZPASS_ADDR`
(r300.c:1073) → patched IB executes on the CP → Z-pass counting writes at
`gpu_offset + attacker_u32`.

## Impact analysis (why "likely" not "certain")

- The missing check is **certain** (direct code reading; systemic across
  r100.c:1778 and r300.c:1073).
- The *impact ceiling* is a repeatable GPU-side increment-write (add N pixels
  per draw) at an attacker-chosen 32-bit GPU VA. GTT BOs are packed
  contiguously in the aperture, so a large offset lands in neighboring
  allocations: other users' BOs, the kernel-managed radeon IB pool
  (`radeon_sa_manager`, GTT-resident), or scanout buffers. Corrupting a
  to-be-executed IB after validation is the theoretical worst case
  (unvalidated GPU commands), which is why this matters beyond "my own
  buffer".
- It does **not** directly write host kernel memory (GPU VA space only) and
  requires r300..rv570 hardware — hence Medium, not High.
- Identical code exists in upstream Linux (carried for 15+ years); this does
  not make it less of a DragonFly defect, it makes coordinated disclosure
  easier.

## Why untested (per the honest-status table)

Guest is DragonFly 6.5-DEVELOPMENT (X86_64_GENERIC) under QEMU;
`pciconf -l` shows **no** vendor-0x1002 device and no radeon DRM device
(`env.txt`). The cited path is unreachable at runtime on this guest —
hardware-gated. No amount of guest work can change that, so the trigger is
shipped as a faithful pure-ioctl recipe (`zpass_oob.c`) with exact expected
output for vulnerable vs patched kernels.

## Fix

`fix.diff` adds the missing validation consistent with the sibling cases:

```c
	case R300_ZB_ZPASS_ADDR:
		r = radeon_cs_packet_next_reloc(p, &reloc, 0);
		if (r) { ... }
		if ((u64)idx_value + 8 > radeon_bo_size(reloc->robj)) {
			DRM_ERROR("ZB_ZPASS_ADDR offset %u past BO\n", idx_value);
			return -EINVAL;
		}
		ib[idx] = idx_value + ((u32)reloc->gpu_offset);
		break;
```

(8 bytes covers r500's 64-bit occlusion counter; r300 uses 4.)

Fix validation on hw-gated paths cannot be run on this guest →
`fix_status: not_testable`.

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