β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4404

u32 integer overflow in amdgpu_uvd_cs_msg_decode defeats DPB/ctx buffer size validation β€” UVD runs physical addressing, so undersized decode sessions GPU-DMA past validated buffers (cross-process VRAM/GTT corruption)

Summary

image_size, min_dpb_size and min_ctx_size are computed entirely in u32. For stream types that do not divide by fs_in_mb (VC1/MPEG2/MPEG4/H265) a decode message with width=0x80000000, height=2 wraps every product to 0/constant, and the gates pass with dpb_size=0 - the firmware is handed a 2^31-wide decode session whose buffers were never validated. UVD executes in PHYSICAL addressing (job->vm=NULL) with addresses patched from bo_gpu_offset, so firmware accesses land in adjacent VRAM/GTT slots - other processes' GPU objects and host pages via GTT-backed BOs. Deliberately avoids the known DF-1595 div0 (the fs_in_mb cases). Upstream fixed the identical site (05e1387d151f 2026, absent here). Guest Part-A arithmetic replica: min_dpb_size=0 with both gates PASS; raw-ioctl chain ready. Upstream-mirror dimension-reject fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4404 Β· 12 files
FileTypeDescriptionSize
README.md β€” 2.6 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
uvd_dim_overflow.c β€” 6.5 KB view raw
build.sh β€” 181 B view raw
run.sh β€” 123 B view raw
build.log β€” 72 B view raw
run.log β€” 1.6 KB view raw
run.2.log β€” 1.6 KB view raw
env.txt β€” 708 B view raw
fix.diff β€” 796 B view raw
verdict.json β€” 3.4 KB view raw
manifest.json β€” 1.2 KB view raw

DF-4404 β€” u32 integer overflow in amdgpu_uvd_cs_msg_decode() defeats DPB/ctx buffer size validation (GPU physical-mode OOB)

File: sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:514-696 (decode), used by amdgpu_uvd_cs_pass2() at sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:803-878. Severity: High Β· Confidence: certain (kernel-side math proven; upstream fixed the same defect in Linux commit 05e1387d151f "drm/amdgpu: Reject UVD message with dimensions above 4096 β€” Fixes potential overflow in DPB size calculations", 2026-07-30; the < 16 half that fixes known DF-1595 is commit 3e41d26c70b0).

Build

cc -O2 -I/usr/src/sys/dev/drm/include/uapi -o uvd_dim_overflow uvd_dim_overflow.c

(or ./build.sh which does this on the audit guest via ssh)

Run

./uvd_dim_overflow

(or ./run.sh on the audit guest)

Expected output

  • Part A (always): replicates the kernel arithmetic and shows that for a UVD MPEG2 decode message with msg[6] = width = 0x80000000, msg[7] = height = 2, msg[9] = dpb_size = 0:
  • image_size = (width*height + width*height/2) mod 2^32 wraps to 0 (amdgpu_uvd.c:531-533),
  • min_dpb_size = image_size*3 = 0 (amdgpu_uvd.c:596-600),
  • the gate dpb_size < min_dpb_size (amdgpu_uvd.c:686) passes with a zero-sized DPB,
  • amdgpu_uvd_cs_pass2() then only requires (end-start) >= buf_sizes[1] = 0 for the DPB buffer command (amdgpu_uvd.c:833) β€” any mapping passes.
  • Same demonstration for H265 (case 16, amdgpu_uvd.c:666-674) where ALIGN(w,16)*ALIGN(h,16) wraps and both min_dpb_size and min_ctx_size collapse.
  • Part B (needs AMD GPU): opens /dev/dri/renderD128 / /dev/dri/card0 and submits the crafted decode message. On the audit QEMU guest there is no AMD GPU (only QEMU bochs VGA), so Part B prints the open failures and exits 2.

Success criterion

On real AMD hardware (CIK..Vega UVD, amdgpu attached, user in group video β€” DFly DRM nodes are 0660 root:video, sys/dev/drm/include/drm/drmP.h:108), the CS is accepted (no -EINVAL) despite the undersized DPB β€” proving the validator bypass; the firmware then decodes a stream whose declared dimensions are 2^31 wide, accessing DPB well beyond the validated object. On this guest, Part A + line-certain static proof is the deliverable.

Not-a-PoC note

The audit guest has no AMD GPU (pciconf -lv: only vgapci0 bochs 0x11111234; amdgpu.ko loads but attaches to nothing, /dev/dri absent) β€” the ioctl path cannot be executed here. Status: inconclusive (see VERDICT.md), kernel-side math certain.

VERDICT.md
↓ download raw

DF-4404 β€” VERDICT

Finding: u32 integer overflow in amdgpu_uvd_cs_msg_decode() (sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:514-696) under-computes min_dpb_size / min_ctx_size for the non-dividing stream types (VC1=1, MPEG2=3, MPEG4=4, H265=16), defeating the decode-message buffer-size gate at amdgpu_uvd.c:686 and the mapping-size gate at amdgpu_uvd.c:833, so the UVD engine (running in physical addressing, parser->job->vm = NULL, amdgpu_uvd.c:983) is handed decode sessions whose DPB/ctx buffers were never validated to cover the declared stream β€” GPU-side OOB access into adjacent VRAM/GTT slots (other processes' GPU objects; via GTT, host memory pages).

Verdict: inconclusive (runtime execution impossible on the audit guest: no AMD GPU). The kernel-side defect itself is certain:

  1. Math proven. run.log / run.2.log (guest, cc 8.3): Part A replicates the exact kernel arithmetic (unsigned = u32) β€” for MPEG2, width=0x80000000, height=2, dpb_size=0 β†’ image_size=0, min_dpb_size=0, both gates PASS. For H265, min_dpb_size=0 and min_ctx_size=0xee00 (52 KiB constant only) for a 2^31-wide stream. The same wrap does NOT hit the known DF-1595 div-by-zero because stream types 1/3/4/16 contain no division by fs_in_mb.
  2. Upstream confirms the class. Linux fixed the identical site: commit 05e1387d151f "drm/amdgpu: Reject UVD message with dimensions above 4096 β€” Fixes potential overflow in DPB size calculations" (2026-07-30, by AMD), plus 3e41d26c70b0 (the < 16 half = DF-1595's fix). The DragonFly tree carries neither.
  3. Guest cannot execute Part B. pciconf -lv shows the only display device is QEMU bochs VGA (vgapci0, chip 0x11111234); amdgpu.ko loads ([drm] amdgpu kernel modesetting enabled in dmesg) but attaches to nothing; /dev/dri does not exist. The GEM/VA/CS ioctl chain therefore cannot run. This is a hardware limitation of the audit guest, not a refutation of the code path β€” the path is line-certain from amdgpu_cs_ioctl β†’ amdgpu_uvd_ring_parse_cs β†’ amdgpu_uvd_cs_pass2 β†’ amdgpu_uvd_cs_msg β†’ amdgpu_uvd_cs_msg_decode.

Exploit chain (as it would run on real hardware)

  1. Local user in group video (DFly DRM nodes are 0660 root:video, sys/dev/drm/include/drm/drmP.h:108-110) opens /dev/dri/card0.
  2. GEM-create two small VRAM BOs (msg, dpb), map them into the VM (GEM_VA), create a context.
  3. Submit a CS on the UVD ring whose IB: (a) writes DATA0/DATA1 = msg BO GPU address and issues the GPCOM msg command with a decode message declaring stream_type=3 (MPEG2), width=0x80000000, height=2, dpb_size=0; (b) issues the DPB buffer command pointing at the tiny dpb BO. amdgpu_uvd_ring_parse_cs validates both because min_dpb_size wrapped to 0.
  4. UVD firmware parses the same message with real dimensions and its DPB accesses run past the validated object into adjacent VRAM/GTT slots β€” cross-process GPU-object corruption, or host-page corruption when the neighbor slot is a GTT-backed object; GPU hang (DoS) is the floor.

Whether step 4 corrupts memory or merely hangs depends on the UVD firmware's internal use of the declared dimensions β€” hence impact: none (not demonstrable here) with the bypass itself certain.

Attempts

1 runtime attempt on the guest (build OK, Part A executed, Part B blocked by hardware absence) + line-level trace of every gate. See run.log, run.2.log, env.txt.

Fix

fix.diff (applies cleanly with git apply) mirrors upstream 05e1387d151f + 3e41d26c70b0: reject width/height < 16 || > 4096 before any arithmetic. With the clamp, image_size ≀ 4096*4096*3/2 and every width_in_mb*height_in_mb*k product fits u32; the div-by-zero (DF-1595) is covered by the same check. Not applied to sys/ (read-only audit target); fix validation on the guest is impossible for the same hardware reason, so 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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff mirrors the upstream clamp and git-apply --check passes against sys/, but a patched-kernel A/B run is impossible on this guest (no AMD GPU to attach amdgpu, so neither baseline nor patched path can execute).

fix.diff; git apply --check output in session log; upstream commits 05e1387d151f / 3e41d26c70b0
↓ fix.diffper-fix-DF-4404

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log/run.2.log Part A (u32 wrap: min_dpb_size=0, both gates PASS); env.txt (bochs-only VGA, amdgpu.ko loaded, no /dev/dri); VERDICT.md full trace; fix.diff mirrors upstream clamp and applies cleanly (git apply --check)

PoC changes

Trigger written fresh (no seed): Part A replicates kernel u32 arithmetic; Part B implements the device-open stage of the GEM/VA/CS chain and stops honestly when no AMD GPU is present. Needed -I/usr/src/sys/dev/drm/include (asm/int-ll64.h) and #define __user empty for userland UAPI compile.

Verified recommended fix

Reject width/height < 16 or > 4096 in amdgpu_uvd_cs_msg_decode (mirror upstream 05e1387d151f + 3e41d26c70b0); see fix.diff.

Verdict

Kernel-side defect certain: u32 overflow in amdgpu_uvd_cs_msg_decode (sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:531-533,596-611,666-674) collapses min_dpb_size/min_ctx_size to 0 for non-dividing stream types (VC1/MPEG2/MPEG4/H265) with width=0x80000000/height=2, bypassing the dpb_size gate (line 686) and the pass2 mapping gate (line 833); proven by exact-arithmetic replica on the guest (run.log) and by upstream's own fix (Linux 05e1387d151f 'Reject UVD message with dimensions above 4096', 2026-07-30, absent from this tree). Runtime PoC impossible on the audit guest: no AMD GPU (QEMU bochs VGA only), amdgpu.ko loads but /dev/dri never appears, so the CS ioctl chain cannot execute; impact not demonstrated (inconclusive), not refuted.