uvd_v1_0_resume programs VCPU cache windows +128B into vcpu_bo at full sizes β STACK/SESSION window overruns the pinned bo by 128 bytes into the adjacent VRAM object (radeon UVD v1 twin of DF-4524/DF-4742)
Summary
The window base jumps +128B into the bo but the sizes were never shrunk - for 99.8% of fw sizes (ALIGN(datasize+8)==ALIGN(datasize)) the STACK/SESSION window ends exactly 128B past the bo, inside the adjacent VRAM object. Exhaustive mod-4096 arithmetic proof (control uvd_v2_2 clean for all residues). GPU-scope only (LMI maps VRAM, not host memory): unpriv CS-driven decode can cross-object read/write 128B of the next GEM bo on R600/RV770-class hw. Shrink-fw-window-by-16-units fix, arithmetic-exact for every residue. Cross-noted: uvd_v4_2.c:47 shows a 512B-class overhang shape (pass-1 clean; follow-up).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4832 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.7 KB | β raw | |
| VERDICT.md | β | 5.6 KB | β raw | |
| check_arith.py | β | 4.4 KB | view raw | |
| arith.log | β | 1.3 KB | view raw | |
| env.txt | β | 483 B | view raw | |
| fix.diff | β | 824 B | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-4832 β radeon UVD v1.0 VCPU cache windows overrun vcpu_bo by 128 bytes
File: sys/dev/drm/radeon/uvd_v1_0.c:121-135 (vs bo sizing in sys/dev/drm/radeon/radeon_uvd.c:184-186)
Severity: Low (GPU-scope VRAM overhang only) Β· Bucket: memcorrupt (radeon twin of DF-4524 / DF-4742)
What to run
python3 check_arith.py # -> arith.log (same content)
No kernel build or hardware needed for the arithmetic; the runtime effect needs
an R600-class or RV770 ASIC with UVD + its firmware, which this guest does not
have (see env.txt).
Expected output (decisive lines)
exhaustive ds in [1,4095] (one full mod-4096 cycle): overrun bytes: min=128 max=128 ; vulnerable residues: 4088/4096 (99.8%) vulnerable residues are exactly ds % 4096 in [1,4088]: True safe residues (ds % 4096 == 0 or in [4089,4095]): True control (uvd_v2_2 non-RV770): span<=bo for ALL residues: True
i.e. for every legacy UVD firmware whose datasize % 4096 lands in
[1,4088] (99.8% of possible sizes, including all realistic blob sizes), the
STACK/SESSION window programmed into the UVD VCPU ends exactly 128 bytes past
the end of rdev->uvd.vcpu_bo β inside the next VRAM object.
Reproduce on hardware (not possible here)
- Bare metal (or GPU-passthrough VM) with an R600/RV610/RV630/RV670/RV620/
RV635/RS780/RS880 or RV770 GPU (radeonkms driver,
radeonkmsfw_R600_uvd/RS780/RV770firmware loaded). - Trigger UVD use (any VDPAU/video decode via CS on the UVD ring).
- Observe the last 128 bytes of the session/stack region (VRAM addresses
gpu_addr + 128 + ALIGN(ds) + HEAP + STACK + SESSION*N - 128 .. +0) landing in the VRAM object allocated aftervcpu_boβ GPU-side read/write of that neighbor object's head.
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 aftervcpu_bo.datasize % 4096 == 0orβ [4089,4095]β the+8forces one extra GPU page of slack β₯ 128 B β fits.- Legacy headerless fw is always used for these ASICs (
fw_nameis only set for BONAIRE+; radeon_uvd.c:126-132,176), anddatasizeis 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_initring accounting:radeon_ring_lock(β¦, 10)(uvd_v1_0.c:180) vs exactly 10radeon_ring_writes (:187-203) β exact, no ring overflow.uvd_v1_0_fence_emitwrites 10 dwords; every caller reserves far more (radeon_ib.c:135locks64 + RADEON_NUM_SYNCS*8) β nocount_dwunderflow (andradeon_ring_write, radeon.h:2708-2717, only logs on overflow, staying insideptr_mask).uvd_v1_0_ring_test:ris always assigned (radeon_ring_lockresult at :428) before the timeout branch β no uninit read; timeout bounded byusec_timeout.uvd_v1_0_startworst-case ~10.5 smdelaybusy-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_execute32-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.
Fix verification
not_testableFix not kernel-validated: no radeon GPU on this guest, no baseline to flip. fix.diff is arithmetic-exact: post-fix span == bo_size for every ds in [0,0x2FFFF] (arith.log tail).
fix.diff (applies clean via git apply --check; window chain ends exactly at bo end)
Confirmed kernel references
Detail
Evidence (decisive lines)
arith.log (exhaustive mod-4096 sweep: overrun min=max=128B, 4088/4096 residues; uvd_v2_2 control clean for all residues; post-fix span==bo_size for all ds in [0,0x2FFFF]); VERDICT.md (full derivation + negative-check list); env.txt (guest GPU absent)
PoC changes
n/a (no seed; arithmetic harness written fresh, DF-4742 style)
Verified recommended fix
Shrink the fw window by the 128B entry skip in uvd_v1_0_resume (size = (RADEON_GPU_PAGE_ALIGN(datasize+8) >> 3) - 16) so the window chain ends at the bo boundary, or clamp windows to bo size v4_0-style
Verdict
uvd_v1_0_resume programs the UVD VCPU LMI cache windows starting 128 bytes into vcpu_bo (addr=(gpu_addr>>3)+16, uvd_v1_0.c:121) but keeps full-size regions (ALIGN(datasize), HEAP, STACK+SESSIONN, uvd_v1_0.c:122-135), while radeon_uvd.c:184-186 sizes the bo at ALIGN(datasize+8)+HEAP+STACK+SESSIONN. For datasize%4096 in [1,4088] (4088/4096 = 99.8% of sizes, incl. every realistic legacy blob; these ASICs always use legacy headerless fw) the STACK/SESSION window ends exactly bo_end+128B, in the VRAM object allocated after vcpu_bo. Affects the whole R600 class (r600.c:3080) and RV770 (uvd_v2_2.c:106). Arithmetic proven exhaustively by check_arith.py (arith.log: min=max=128B overrun; control uvd_v2_2 fits for ALL residues). End-to-end not runnable: guest has no AMD ASIC (bochs chip=0x11111234, no drm modules, env.txt) and the window-tail write is fw-behavior-dependent -- speculative, mirroring accepted twins DF-4524 (vce_v3_0) and DF-4742 (vce_v2_0). GPU-scope only: LMI windows map VRAM, not host memory; ceiling is 128B cross-object GPU read/write between adjacent GEM bo's during video decode (CS-reachable by unpriv users on real hw). No kernel host-memory impact.
No comments yet.