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

vce_v2_0: VCE bo sized exactly AMDGPU_VCE_FIRMWARE_OFFSET (256) bytes short β€” DATA window ends 256 bytes past vcpu_bo (DF-4524 v3 shape, v2 instance)

Summary

sw_init allocates the bo at exactly FW+STACK+DATA (172 pages, zero slack) but mc_resume lays the cache windows out starting at bo+256 - the DATA window (all 16 session handles) ends exactly 256 bytes past the bo, so fw session-data writes land in the adjacent VRAM object. Identical shape to DF-4524 (v3); v4 clamps to bo size (clean). Arithmetic proof in pack; hw-gated. Add-the-offset fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4742 Β· 8 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.9 KB ↓ raw
check_arith.py β€” 1.2 KB view raw
arith.log β€” 264 B view raw
env.txt β€” 316 B view raw
fix.diff β€” 571 B view raw
manifest.json β€” 952 B view raw
verdict.json β€” 2.5 KB view raw

DF-4742 β€” vce_v2_0: VCE bo sized 256 bytes short of the VCPU cache-window layout

Finding

vce_v2_0_sw_init() allocates the shared VCE vcpu_bo at exactly VCE_V2_0_FW_SIZE + VCE_V2_0_STACK_SIZE + VCE_V2_0_DATA_SIZE bytes (sys/dev/drm/amd/amdgpu/vce_v2_0.c:424-425, passed to amdgpu_vce_sw_init() β†’ amdgpu_bo_create_kernel(adev, size, ...), sys/dev/drm/amd/amdgpu/amdgpu_vce.c:181).

But vce_v2_0_mc_resume() programs the VCE VCPU cache windows starting at bo offset AMDGPU_VCE_FIRMWARE_OFFSET = 256 (sys/dev/drm/amd/amdgpu/vce_v2_0.c:185-198; AMDGPU_VCE_FIRMWARE_OFFSET defined at sys/dev/drm/amd/amdgpu/amdgpu_vce.h:28):

  • window 0 (firmware): bo+256 .. bo+256+262144
  • window 1 (stack): bo+256+262144 .. +65536
  • window 2 (DATA, per-session data for all AMDGPU_MAX_VCE_HANDLES=16 handles): bo+256+327680 .. +376832 β†’ ends at bo+704768

bo size = 262144 + 65536 + 376832 = 704512 bytes = exactly 172 pages (no allocator slack β€” page-aligned total). The DATA window therefore ends 256 bytes past the end of the bo: the VCE firmware's per-session DATA writes into the last 256 bytes of the window land in the next VRAM object beyond vcpu_bo.

Identical shape to the already-filed v3 instance DF-4524 (vce_v3_0.c, instance-1 DATA window overruns by 256). vce_v4_0 is clean because it reads back amdgpu_bo_size(adev->vce.vcpu_bo) and clamps the window sizes to the actual bo (vce_v4_0.c:440).

Reachability / threat

Hardware-gated: requires a CIK-era Sea Islands ASIC with VCE 2.0 (Bonaire / Kaveri / Kabini / Hawaii / Mullins) present and initialized (amdgpu loaded, VCE fw present). The DATA window is written by the VCE firmware itself whenever encode sessions exist; sessions are created by any local user with DRM render-node access via the CS ioctl (amdgpu_vce_ring_parse_cs handles, DF-4447 wiring). Impact ceiling: GPU-side out-of-bounds write of up to 256 bytes into the VRAM object allocated after vcpu_bo β€” cross-object VRAM corruption (another process's buffer / display buffer). Host-RAM or code-exec impact is not established (firmware-defined write pattern) β€” impact is speculative; the 256-byte window overrun itself is pure arithmetic and certain.

Status

NOT runnable on the audit guest (QEMU virtio/bochs GPU, no AMD hardware). Mirror of DF-4524's treatment: status untested, arithmetic verified by check_arith.py in this pack.

Fix

Add the firmware offset to the bo size (one-line; keeps window layout intact):

--- a/sys/dev/drm/amd/amdgpu/vce_v2_0.c
+++ b/sys/dev/drm/amd/amdgpu/vce_v2_0.c
@@ -424,7 +424,8 @@ static int vce_v2_0_sw_init(void *handle)
    }

    r = amdgpu_vce_sw_init(adev, VCE_V2_0_FW_SIZE +
-       VCE_V2_0_STACK_SIZE + VCE_V2_0_DATA_SIZE);
+       AMDGPU_VCE_FIRMWARE_OFFSET +
+       VCE_V2_0_STACK_SIZE + VCE_V2_0_DATA_SIZE);
    if (r)
        return r;

(Alternative, v4_0-style: clamp each VCE_VCPU_CACHE_SIZEn window to amdgpu_bo_size(adev->vce.vcpu_bo) in vce_v2_0_mc_resume().)

VERDICT.md
↓ download raw

DF-4742 VERDICT β€” vce_v2_0 VCE bo 256 bytes short

Status: untested (end-to-end), reproduced: 0, confidence: speculative (impact) / arithmetic certain β€” identical treatment to the sibling finding DF-4524 (vce_v3_0), which also could not be run for lack of AMD hardware on the audit guest.

What was verified, and how

  1. Allocation size β€” vce_v2_0_sw_init() calls amdgpu_vce_sw_init(adev, VCE_V2_0_FW_SIZE + VCE_V2_0_STACK_SIZE + VCE_V2_0_DATA_SIZE) (sys/dev/drm/amd/amdgpu/vce_v2_0.c:424-425). amdgpu_vce_sw_init() creates vcpu_bo at exactly that size (amdgpu_vce.c:181-183). No other allocation backs the windows.
  2. Window layout β€” vce_v2_0_mc_resume() (vce_v2_0.c:185-198) programs VCPU_CACHE_OFFSET0/1/2 at bo+256, bo+256+256K, bo+256+320K with sizes 256K / 64K / 23552*16. AMDGPU_VCE_FIRMWARE_OFFSET is 256 (amdgpu_vce.h:28).
  3. Arithmetic β€” bo = 262144+65536+376832 = 704512 bytes = exactly 172 pages (no allocator slack); DATA window ends at 704768 = bo_end + 256. Verified by check_arith.py (see arith.log).
  4. Firmware upload position β€” amdgpu_vce_resume() copies the fw blob to bo offset 0 (amdgpu_vce.c:299-309) while the VCPU fetches fw through the bo+256 window; this offset-256 head is exactly the 256 bytes the sw_init size forgets. The fw copy itself does NOT overflow (datasize < FW window), so the host-side memcpy is safe β€” the exposure is the GPU-side DATA window tail, written by the VCE firmware during encode sessions.
  5. Family consistency β€” v3 instance filed as DF-4524 (vce_v3_0.c:430, sw_init size FW + 2*(STACK+DATA) vs windows ending at 256+FW+2STACK+2DATA). v4 clean via amdgpu_bo_size() clamp (vce_v4_0.c:440). v2 sits in the same defective generation with a single instance of DATA.

Why not reproduced on the guest

The audit guest is QEMU (bochs/virtio GPU, pciconf -l shows vgapci0@pci0:0:2:0 chip=0x11111234), no AMD ASIC, so amdgpu never binds a device, vce_v2_0_early_init/sw_init never run. The overrun is firmware-behavior-dependent (whether the VCE fw actually touches the last 256 B of the DATA window) β€” same reason DF-4524 was filed speculative.

Negative checks performed on vce_v2_0.c (pass 2)

  • IRQ dispatch entry->src_data[0] is switch-guarded to 0/1, ring[AMDGPU_MAX_VCE_RINGS] (vce_v2_0.c:540-549, amdgpu_vce.h:48) β€” safe.
  • emit_frame_size=6 == amdgpu_vce_ring_emit_fence dwords (amdgpu_vce.c:1056-1061); emit_ib_size=4 == emit_ib dwords (amdgpu_vce.c:1038-1041) β€” ring accounting exact.
  • wait_for_idle lacks udelay but is upstream-identical; MMIO reads self-pace (~Β΅s each); not attacker-amplifiable beyond GPU-reset paths.
  • firmware_loaded/lmi_clean mdelay loops (~10 s worst) run only in hw_init / powergating (root/PM contexts) β€” not attacker-triggered.
  • sw_fini early-return when amdgpu_vce_suspend sees live handles (amdgpu_vce.c:266-274) leaks bo/fw on unload with active sessions β€” root-triggered error-path leak, upstream-inherited; not filed.
  • parser wiring parse_cs = amdgpu_vce_ring_parse_cs (vce_v2_0.c:615) β€” DF-4447 (desync) lives in amdgpu_vce.c and is NOT re-filed; the only v2-specific parser branch (0x0500000c hw-config, amdgpu_vce.c:838-850) rejects the command on BONAIRE/KABINI/HAWAII (v2 asics) while allowing KAVERI/MULLINS β€” functional asymmetry, no memory-safety impact.

+ AMDGPU_VCE_FIRMWARE_OFFSET in the sw_init size (see fix.diff), or clamp window sizes to amdgpu_bo_size() v4_0-style in mc_resume.

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 not kernel-validated: no AMD GPU on this guest, no baseline to flip. diff is arithmetic-exact against the verified window layout.

fix.diff (+256 bytes to bo size => window2 end == bo end)
↓ fix.diffper-fix-DF-4742

Confirmed kernel references

Detail

Evidence (decisive lines)

arith.log (bo size 704512/172 pages, window2 end 704768, overrun=256B); VERDICT.md negative-check list; env.txt (guest GPU absent)

PoC changes

n/a (no seed; arithmetic harness written fresh)

Verified recommended fix

Add AMDGPU_VCE_FIRMWARE_OFFSET (256) to the amdgpu_vce_sw_init() size in vce_v2_0_sw_init(), or clamp CACHE_SIZEn windows to amdgpu_bo_size() v4_0-style

Verdict

vce_v2_0_sw_init sizes vcpu_bo at exactly FW+STACK+DATA (vce_v2_0.c:424-425 -> amdgpu_vce.c:181 bo_create_kernel) while vce_v2_0_mc_resume maps the VCPU cache windows starting AMDGPU_VCE_FIRMWARE_OFFSET=256 bytes into the bo (vce_v2_0.c:185-198): bo=704512 (exactly 172 pages, zero slack), DATA window ends at bo+704768 = bo_end+256, so the per-session DATA window's last 256 bytes lie in the VRAM object following vcpu_bo. Same defect shape as the filed v3 instance DF-4524; v4 is clean (clamps to amdgpu_bo_size, vce_v4_0.c:440). Arithmetic proven by check_arith.py (arith.log). End-to-end not runnable: guest has no AMD ASIC (QEMU bochs vgapci0 chip=0x11111234), and the actual fw write into the window tail is fw-behavior-dependent β€” hence speculative, mirroring DF-4524's treatment.