# DF-4832 VERDICT — uvd_v1_0.c 128-byte VCPU-cache window overhang

**Status: untested (hardware-gated) — arithmetic overrun certain, runtime
effect fw-behavior-dependent. Impact: GPU-scope VRAM overhang of 128 bytes
past a pinned bo. Confidence: speculative (mirrors DF-4524/DF-4742).**

## The defect

`radeon_uvd_init` sizes the shared UVD firmware bo once, for every UVD
generation (radeon_uvd.c:184-186):

```
bo_size = RADEON_GPU_PAGE_ALIGN(uvd_fw->datasize + 8)   /* +8 jump slot */
        + RADEON_UVD_STACK_SIZE + RADEON_UVD_HEAP_SIZE
        + RADEON_UVD_SESSION_SIZE * max_handles          /* radeon.h:1675-1677 */
```

`uvd_v1_0_resume` (uvd_v1_0.c:121-135), used for the whole R600 class
(r600.c:3080) and for RV770 via uvd_v2_2.c:106, programs the VCPU LMI cache
windows shifted **+16 window-units (128 bytes) into the bo** and then lays out
the full fw + HEAP + STACK/SESSION sizes after that offset:

```
addr = (gpu_addr >> 3) + 16;                          /* +128 B   :121 */
size = RADEON_GPU_PAGE_ALIGN(datasize) >> 3;           /* ALIGN(ds):122  */
addr += size; size = HEAP >> 3;                        /* :126-127 */
addr += size; size = (STACK + SESSION*max_handles)>>3; /* :131-133 */
```

Total VCPU-visible span = `128 + ALIGN(datasize) + HEAP + STACK + SESSION*N`,
while the bo ends at `ALIGN(datasize+8) + HEAP + STACK + SESSION*N`.

* `datasize % 4096 ∈ [1,4088]` → `ALIGN(datasize+8) == ALIGN(datasize)` →
  **span = bo_end + 128**. The last 128 bytes of the STACK/SESSION window lie
  in the VRAM object allocated after `vcpu_bo`.
* `datasize % 4096 == 0` or `∈ [4089,4095]` → the `+8` forces one extra GPU
  page of slack ≥ 128 B → fits.
* Legacy headerless fw is *always* used for these ASICs (`fw_name` is only set
  for BONAIRE+; radeon_uvd.c:126-132,176), and `datasize` is the raw blob
  size, so 99.8% of possible sizes are in the vulnerable class
  (`check_arith.py`, exhaustive mod-4096 sweep — `arith.log`).

Control: `uvd_v2_2` (non-RV770) starts its windows at +0 and sizes region0
`ALIGN(datasize+4) ≤ ALIGN(datasize+8)` → fits for **every** residue
(proven by the harness). The overhang is specific to the v1.0 +128 layout.
Cross-note (out of this file's scope, uvd_v4_2.c:47 uses a +0x200 skip with
`ALIGN(datasize+4)`): the same harness shows a 512-byte-class overhang shape
there when `fw_header_present` — filed for the orchestrator's attention, not
re-filed here (uvd_v4_2.c was pass-1 `clean`).

## Why it cannot be runtime-verified here

The guest has no AMD ASIC at all — QEMU bochs `vgapci0 chip=0x11111234`,
no drm/radeon/agp modules, no `hw.drm` sysctls (`env.txt`). The defect
additionally requires the r600-era UVD firmware blob and a CS-driven decode
session so the VCPU actually touches the window tail (fw-defined behavior).
Identical situation to the accepted twins DF-4524 (vce_v3_0, amdgpu) and
DF-4742 (vce_v2_0, amdgpu), which were filed on arithmetic proof alone.

## Threat model

An unprivileged local user on affected hardware who can submit CS to the UVD
ring (video decode) gets the UVD VCPU's session/stack tail mapped over the
first 128 bytes of the *next* VRAM object. Ceiling: GPU-side cross-object
info-leak/corruption between GEM bo's (e.g. another user's buffer) — **not**
kernel host memory; the window is a GPU-physical (LMI) mapping into VRAM, and
past the end of VRAM the LMI simply has no valid target (hang/reset, not host
corruption). No kernel memory-safety impact; kernel-controlled inputs only
(fixed bo address, fixed constants); nothing in uvd_v1_0.c consumes
user-controlled data on these paths.

## Negative-check list (what else was hunted in uvd_v1_0.c and killed)

* `uvd_v1_0_init` ring accounting: `radeon_ring_lock(…, 10)` (uvd_v1_0.c:180)
  vs exactly 10 `radeon_ring_write`s (:187-203) — exact, no ring overflow.
* `uvd_v1_0_fence_emit` writes 10 dwords; every caller reserves far more
  (`radeon_ib.c:135` locks `64 + RADEON_NUM_SYNCS*8`) — no `count_dw`
  underflow (and `radeon_ring_write`, radeon.h:2708-2717, only logs on
  overflow, staying inside `ptr_mask`).
* `uvd_v1_0_ring_test`: `r` is always assigned (`radeon_ring_lock` result at
  :428) before the timeout branch — no uninit read; timeout bounded by
  `usec_timeout`.
* `uvd_v1_0_start` worst-case ~10.5 s `mdelay` busy-wait (:329-347) is
  upstream-shared and reachable only from device init/resume (r600.c:3080,
  rv770.c:1743) — no unpriv trigger path; not filed.
* `uvd_v1_0_ib_execute` 32-bit IB base (:486-487) — upstream layout, upper
  bits via UVD_LMI_EXT40_ADDR (:364); IB gpu_addr/length pre-validated by the
  CS parser (radeon_uvd.c, DF-1655/DF-4500 domain); no kernel-scope effect.
* `WREG32(UVD_FW_START, *((uint32_t*)uvd.cpu_addr))` (:145) reads 4 bytes of a
  ≥ 500 KB mapped bo — in bounds.
* suspend/resume handle-table lifecycle lives in radeon_uvd.c (pass-1/2
  findings DF-1655/1656/1657/4500/4501 cover it); uvd_v1_0.c touches no
  handle tables.

## Fix

`fix.diff` shrinks the fw window by the 128-byte entry skip so the window
chain ends exactly at the bo boundary (`size = (ALIGN(datasize+8)>>3) - 16`),
keeping all subsequent windows inside `vcpu_bo` for every residue. Not
kernel-validated (no hardware); arithmetic-exact against the verified layout
(`check_arith.py` proves span == bo_size after the fix for all 4096 residues).

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