# DF-5229 VERDICT — uvd_v4_2.c 512-byte VCPU-cache window-chain overhang

**Status: untested (hardware-gated) — arithmetic overrun certain with the
*shipping* firmware (not just generic residues); runtime effect
fw-behavior-dependent. Impact: GPU-scope VRAM overhang of 512 bytes past a
pinned bo. Confidence: speculative (mirrors accepted DF-4524/DF-4742/DF-4832;
stronger evidence than DF-4832: the in-tree blob's exact size is known).**

## The defect

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

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

`uvd_v4_2_resume` — the CIK UVD entry (cik.c:8175, kv_dpm.c:1628) — programs
the VCPU LMI cache windows starting **512 bytes into the bo** when the
new-format fw is present, but keeps a **full-blob** region0 size
(uvd_v4_2.c:46-53):

```
addr = (gpu_addr + 0x200) >> 3;                     /* +512 B    :46-47  */
size = RADEON_GPU_PAGE_ALIGN(datasize + 4) >> 3;     /* full blob :51     */
addr += size; size = HEAP >> 3;                      /*           :55-58  */
addr += size; size = (STACK + SESSION*max_handles) >> 3;   /*    :60-64  */
```

The windows are laid out contiguously, so the chain end is
`0x200 + ALIGN(ds+4) + HEAP + STACK + SESSION*N` from the bo base, while the
bo ends at `ALIGN(ds+8) + HEAP + STACK + SESSION*N`:

* `ds%4096 ∈ [0,4088] ∪ [4093,4095]` (4092/4096 = **99.90%** of sizes) →
  `ALIGN(ds+4) == ALIGN(ds+8)` → **chain ends bo_end + 512**.
* `ds%4096 ∈ [4089,4092]` → the `+8` wins a page → fits (3.5 KiB slack).

With the **shipping** firmware this is not a residue argument:
`sys/contrib/dev/drm/radeonfw/bonaire_uvd.bin.uu` decodes to
`datasize=232752 (0x38d30)`, `size_bytes=232752` (`radeon_ucode_validate`
passes, radeon_uvd.c:161), `ucode_version 1.64 ≥ 1.37` → `max_handles=30`
(radeon_uvd.c:165-166), and `datasize%4096=3376` → overhang **exactly 512 B**
(bo=0x222000, span=0x222200). The fw is the *preferred* load for every CIK
UVD ASIC (radeon_uvd.c:124-131 sets `fw_header_present=true` at :153), so the
vulnerable branch is the live path whenever that kmod is loaded.

Overhang lands on the STACK/SESSION window tail = per-session VCPU memory
(50 KiB/handle × 30 handles) → the last 512 bytes of handle-30's session are
GPU-read/written inside the **next VRAM object** allocated after `vcpu_bo`.

## Controls and divergence

* Headerless branch (uvd_v4_2.c:49): +0 offset, `ALIGN(ds+4)` → fits for all
  65536 sizes (arith.log) — same shape as accepted-clean uvd_v2_2
  (uvd_v2_2.c:113-126).
* DF-4832 (uvd_v1_0) is the 128 B headerless twin; DF-4524/DF-4742 are the
  amdgpu VCE 256 B twins. All accepted on arithmetic proof alone.
* **amdgpu fixed this class for the same silicon**:
  `AMDGPU_UVD_FIRMWARE_OFFSET (256) + AMDGPU_UVD_FIRMWARE_SIZE(adev)
  (= ALIGN(ucode_size_bytes+8) − 256)` (amdgpu_uvd.h:32-38) makes
  offset+size exactly the aligned fw region; radeon's uvd_v4_2 (+0x200 skip
  with full-`ALIGN(datasize+4)` size) never got the equivalent. Note the
  blob's real payload offset is 0x100 (`ucode_array_offset_bytes=256`,
  fw_header.txt) — the 0x200 entry skip is the historical (upstream-Linux
  verbatim) value; the fix deliberately does **not** change the entry offset
  (fw-behavior validated by a decade of hardware use), only the region sizes.

## Why it cannot be runtime-verified here

Guest has no AMD ASIC at all (`env.txt`: NO_AMD_PCI, no drm kmods) — needs a
BONAIRE/KAVERI/KABINI/MULLINS/HAWAII GPU + new-format fw + a CS decode
session so the VCPU actually touches the window tail. Identical situation to
the accepted twins DF-4524/DF-4742/DF-4832.

## Threat model

Unprivileged local user on CIK UVD hardware who can submit CS to the UVD ring
(video decode) → the UVD VCPU's session/stack tail maps over the first 512
bytes of the *next* VRAM object. Ceiling: GPU-side cross-object
info-leak/corruption between adjacent GEM bo's (e.g. another user's buffer) —
**not** kernel host memory: the LMI windows map VRAM; past VRAM there is no
valid target (hang/reset, not host corruption). Nothing in uvd_v4_2.c
consumes user-controlled data; all inputs are kernel-fixed constants + fw size.

## Fix validation (arithmetic-exact; not kernel-runnable here)

`fix.diff`: when `fw_header_present`, program
`size0 = (RADEON_GPU_PAGE_ALIGN(datasize + 8) − 0x200) >> 3` so that
`offset0+size0 == ALIGN(datasize+8)` (the fw region the bo actually holds) —
region1/2 shift 512 B earlier within the same bo and the chain ends exactly
at the bo boundary. Harness tail proves: post-fix span == bo_size for every
ds in [0,0x2FFFF] sampled every 7 bytes (both N=10 and N=30), and the
headerless branch is untouched. `git apply --check` clean.

## Negative-check list (everything else hunted in uvd_v4_2.c)

* NULL `uvd_fw` deref via the kv DPM ungate caller — REAL, verified with a
  guest panic + fix validation, filed as **DF-5230**.
* Guarded caller path (cik.c:8167 has_uvd; radeon_uvd.c:286 vcpu_bo) — safe.
* Boot-time gate path (kv_dpm_late_enable → uvd_v1_0_stop) — register writes
  only (uvd_v1_0.c:391-411) — safe.
* 64→32-bit truncations: OFFSET0/SIZE0 regs hold addr>>3 by design; bits
  28-31 at :67 and 32-39 at :71 carry the high bits — correct.
* `max_handles` (≤30) × SESSION fits the handles[] arrays (radeon.h:1685) —
  and SESSION*N cancels between span and bo (arith.log N-independence) — no
  independent overflow.
* This file has no ring/emitter/cntl overrides (v4_2 is resume-only; ring
  funcs come from uvd_v1_0.c via cik.c:8208 r600_ring_init / uvd_v1_0_init)
  — nothing else to audit in it.

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