uvd_v4_2_resume programs the VCPU cache-window chain +0x200 into vcpu_bo with a full-blob region0 size β chain ends 512 bytes past the pinned bo with the shipping CIK UVD firmware (radeon twin of DF-4832/DF-4524/DF-4742)
Summary
With fw_header_present (live path for BONAIRE/KABINI/KAVERI/HAWAII/MULLINS), region0 starts gpu_addr+0x200 but is sized RADEON_GPU_PAGE_ALIGN(datasize+4) for the FULL blob while the bo is sized ALIGN(datasize+8)+HEAP+STACK+SESSION*N - the contiguous region chain ends bo_end+512 for 99.90% of fw sizes; shipping bonaire_uvd datasize=232752 gives exactly +512B, mapping the SESSION tail over the head of the adjacent VRAM GEM object (GPU-scope cross-object read/write, unpriv decode CS). Arithmetic-proven exhaustively (65472/65536 sizes, min=max=512B, shipping fw decoded from in-tree .uu); hardware-gated acceptance basis same as DF-4524/4742/4832. offset+size==fw-region invariant fix (amdgpu precedent amdgpu_uvd.h:32-38).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5229 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 6.0 KB | β raw | |
| check_arith.py | β | 5.3 KB | view raw | |
| arith.log | β | 949 B | view raw | |
| decode_fw.py | β | 1.5 KB | view raw | |
| fw_header.txt | β | 885 B | view raw | |
| bonaire_uvd.bin | β | 227.3 KB | β download | |
| build.sh | β | 135 B | view raw | |
| run.sh | β | 168 B | view raw | |
| env.txt | β | 25 B | view raw | |
| fix.diff | β | 872 B | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 4.3 KB | view raw |
DF-5229 β radeon UVD v4.2 VCPU cache-window chain overruns vcpu_bo by 512 bytes (shipping CIK firmware)
File: sys/dev/drm/radeon/uvd_v4_2.c:46-64 (vs bo sizing sys/dev/drm/radeon/radeon_uvd.c:184-186)
Severity: Low (GPU-scope VRAM overhang only) Β· Bucket: memcorrupt
Family: radeon twin of DF-4832 (uvd_v1_0, 128 B); amdgpu cousins DF-4524 (vce_v3_0, 256 B) / DF-4742 (vce_v2_0, 256 B)
What to run
./build.sh # host: decode the in-tree shipping fw -> fw_header.txt python3 check_arith.py # -> arith.log (exhaustive sweep; no kernel/hw needed)
Expected output (decisive lines, arith.log)
header path: overrun bytes: min=512 max=512 ; vulnerable sizes: 65472/65536 (99.90%) safe residues (ds%4096): [4089, 4090, 4091, 4092] control: headerless path ... overrun sizes: 0/65536 datasize=232752 (v1.64 fw -> N=30): bo_size=2236416 span=2236928 overrun=512 bytes last window tail = gpu_addr + 0x222200 vs bo end gpu_addr + 0x222000 post-fix span == bo_size (header path): True
The shipping CIK UVD firmware (sys/contrib/dev/drm/radeonfw/bonaire_uvd.bin.uu,
loaded as radeonkmsfw_bonaire_uvd β the preferred fw for
BONAIRE/KABINI/KAVERI/HAWAII/MULLINS, radeon_uvd.c:124-131,139-141) decodes as
datasize=232752, %4096=3376 (fw_header.txt), so ALIGN(ds+4)==ALIGN(ds+8)
and the STACK/SESSION window programmed by uvd_v4_2_resume ends exactly
512 bytes past the end of rdev->uvd.vcpu_bo β inside the VRAM object
allocated after it.
Reproduce on hardware (not possible here)
- Bare metal / GPU-passthrough VM with a CIK UVD GPU (BONAIRE, KABINI, KAVERI, HAWAII, MULLINS β radeonkms driver, new-format UVD fw loaded).
- Trigger UVD use (any VDPAU/video decode CS on the UVD ring) β windows are programmed at ring start (cik.c:8175) or DPM ungate (kv_dpm.c:1628).
- The last 512 B of the SESSION region (tail of handle 30's session) are GPU-written into the adjacent VRAM object β cross-object GPU read/write between GEM bo's (e.g. another user's buffer).
Control & fix
- Headerless control (uvd_v4_2.c:49 +0 /
ALIGN(ds+4)): 0 overruns for all 65536 sizes β identical to the accepted-clean uvd_v2_2 shape. fix.diff: shrink region0 by the 0x200 skip sooffset0+size0equals the fw region the bo actually holds (ALIGN(datasize+8)) β the same invariant amdgpu uses (AMDGPU_UVD_FIRMWARE_OFFSET + AMDGPU_UVD_FIRMWARE_SIZE == ALIGN(ucode_size+8),sys/dev/drm/amd/amdgpu/amdgpu_uvd.h:32-38). Post-fix span == bo_size for every ds in [0,0x2FFFF] and both N (arith.log).
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+8wins 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_fwderef 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.
Fix verification
not_testableFix not kernel-validated: no CIK UVD GPU on this guest, no baseline to flip. fix.diff is arithmetic-exact: post-fix span == bo_size for every ds in [0,0x2FFFF] (sampled every 7 bytes) and both max_handles values; headerless branch untouched; git apply --check clean.
fix.diff (git apply --check clean; window chain ends exactly at bo end); arith.log tail (fix-validation sweep)
Confirmed kernel references
- sys/dev/drm/radeon/uvd_v4_2.c:46
- sys/dev/drm/radeon/uvd_v4_2.c:51
- sys/dev/drm/radeon/uvd_v4_2.c:55
- sys/dev/drm/radeon/uvd_v4_2.c:60
- sys/dev/drm/radeon/radeon_uvd.c:184
- sys/dev/drm/radeon/radeon_uvd.c:153
- sys/dev/drm/radeon/radeon.h:1675
- sys/dev/drm/radeon/cik.c:8175
- sys/dev/drm/radeon/kv_dpm.c:1628
- sys/dev/drm/amd/amdgpu/amdgpu_uvd.h:32
Detail
Evidence (decisive lines)
arith.log (exhaustive: min=max=512B, 65472/65536 sizes vulnerable, safe residues exactly [4089..4092]; headerless control 0 overruns; shipping fw bo=0x222000 span=0x222200; post-fix span==bo_size for all sampled ds and both N); fw_header.txt (decoded shipping blob: datasize 232752, size_bytes==datasize so radeon_ucode_validate passes, ucode 1.64 -> N=30, payload offset 0x100); VERDICT.md (derivation + amdgpu divergence + negative checks); env.txt (guest: NO_AMD_PCI)
PoC changes
no seed; arithmetic harness written fresh DF-4832-style (its check_arith.py already modeled v4_2_span and cross-noted the 512B shape); added the exact shipping-firmware constants decoded from the in-tree .uu blob
Verified recommended fix
size region0 so offset0+size0 == the fw region the bo holds: when fw_header_present, size = (RADEON_GPU_PAGE_ALIGN(datasize+8) - 0x200) >> 3 (amdgpu invariant), keeping the whole window chain inside vcpu_bo
Verdict
uvd_v4_2_resume (sys/dev/drm/radeon/uvd_v4_2.c:46-64) programs the UVD VCPU LMI cache windows starting 512 bytes into vcpu_bo when fw_header_present (the live path for BONAIRE/KABINI/KAVERI/HAWAII/MULLINS with the preferred new-format fw, radeon_uvd.c:124-131,153) but keeps region0 sized RADEON_GPU_PAGE_ALIGN(datasize+4) for the FULL blob, while radeon_uvd.c:184-186 sizes the bo at ALIGN(datasize+8)+HEAP+STACK+SESSIONN. The contiguous window chain therefore ends at 0x200+ALIGN(ds+4)+HEAP+STACK+SESSIONN = bo_end+512 for 99.90% of fw sizes (safe only for ds%4096 in [4089..4092]). With the SHIPPING firmware (in-tree sys/contrib/dev/drm/radeonfw/bonaire_uvd.bin.uu: datasize=232752, %4096=3376, ucode 1.64 -> max_handles=30) the overhang is exactly +512 bytes (bo=0x222000, span=0x222200): the tail of the 30-handle SESSION region is GPU-mapped into the VRAM object allocated after vcpu_bo. Proven exhaustively by check_arith.py (arith.log; headerless control 0/65536 overruns, N-independence shown). Not runtime-verifiable: guest has no AMD ASIC (env.txt) and the window-tail access is fw-behavior-dependent during a real decode session -- same acceptance basis as DF-4524/DF-4742/DF-4832 (DF-4832's harness already cross-noted this exact shape). amdgpu fixed the class for the same silicon via the offset+size==fw-region invariant (amdgpu_uvd.h:32-38); radeon never got it. GPU-scope only: LMI windows map VRAM, not host memory; ceiling is 512B cross-object GPU read/write between adjacent GEM bo's during video decode (CS-reachable by unprivileged users on real hw). No kernel host-memory impact; no user-controlled input in this file.
No comments yet.