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

radeon_uvd: destroy message missing filp ownership check -> cross-process UVD session teardown

Field Value
ID DF-1657
File sys/dev/drm/radeon/radeon_uvd.c
Lines 505, 555–560
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L
CWE CWE-862 Missing Authorization
Confidence likely
Status new
CVE match variant (same UVD ownership pattern as DF-1596 amdgpu_uvd cross-user kill)
Created 2026-07-18

Summary

In radeon_uvd_cs_msg the create path (case 0, lines 516-527) checks for handle reuse and the decode path (case 1, lines 542-550) verifies filp ownership ("handle collision detected"), but the destroy path (case 2, lines 555-560) clears every slot whose handle value matches without any check that the calling client (p->filp) owns that slot.

Because UVD handles are plain u32 values chosen by userspace (msg[2]) and never made secret, any local client that can guess or brute-force another client's handle value can submit a destroy msg via the CS ioctl and tear down that client's UVD session at both the kernel (handles[i]=0) and GPU-firmware level. This is a straightforward cross-process denial of service against any other user's video decode pipeline.

Root cause

sys/dev/drm/radeon/radeon_uvd.c:555-560:

case 2:
    /* it's a destroy msg, free the handle */
    for (i = 0; i < p->rdev->uvd.max_handles; ++i)
        atomic_cmpxchg(&p->rdev->uvd.handles[i], handle, 0);
    radeon_bo_kunmap(bo);
    return 0;

There is no if (p->rdev->uvd.filp[i] != p->filp) continue; guard. Compare the decode case at lines 542-550 which explicitly checks:

if (p->rdev->uvd.filp[i] != p->filp) {
    DRM_ERROR("UVD handle collision detected!");
    return -EINVAL;
}

and radeon_uvd_free_handles at line 333 which checks rdev->uvd.filp[i] == filp. The destroy path is the only place that mutates handles[] without verifying ownership.

Handle values come from msg[2] (line 498) and are arbitrary u32 picked by the creating client's userspace (mesa's radeon_uvd driver typically uses small sequential integers), so they are trivially guessable.

Threat model

Attacker has local access to the radeon DRM render node. Victim is any other local user (or the same user in another session) actively decoding video through UVD.

Attacker submits a CS ioctl with a destroy msg (msg[1]=2, msg[2]=<victim's handle>) β€” for typical mesa handle assignment this is just 1, 2, 3, ... The kernel's atomic_cmpxchg clears the victim's slot and the IB forwards the destroy to the UVD firmware. The victim's next decode operation fails with -ENOENT ("Invalid UVD handle") and any in-flight decode is disrupted. Iterating handle values 1..MAX_UVD_HANDLES destroys every UVD session on the system.

This is purely a local DoS β€” no memory corruption, no info leak β€” but it allows any unprivileged user with GPU access to degrade the experience of every other user on a multi-user radeon system, and there is no rate limiting.

PoC

findings/poc/DF-1657/uvd_destroy_dos.c:

/* Outline β€” libdrm-based CS ioctl submission. */
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <radeon_drm.h>
#include <drm.h>

int main(void) {
    int fd = open("/dev/dri/renderD128", O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    /* Allocate a 256-byte BO; via DRM_IOCTL_RADEON_GEM_PWRITE write a
     * UVD destroy message:
     *   msg[0] = 0xDE000001
     *   msg[1] = 2           (msg_type=destroy)
     *   msg[2] = <target>    (start at 1, increment)
     * Submit via DRM_IOCTL_RADEON_CS on the UVD ring with that BO as
     * the msg reloc (cmd=0). */
    for (uint32_t h = 1; h <= 30; h++) {
        /* set msg[2]=h, write to BO, submit CS, watch victim decoder. */
    }

    /* To verify, run a victim `mpv video.mp4` (or any mesa/VA-API
     * decoder using radeon UVD) in another terminal, then run this
     * attacker loop: the victim's decode immediately fails and mpv
     * errors out or stalls. */
    return 0;
}

Build: cc -O2 -o uvd_destroy_dos uvd_destroy_dos.c -ldrm. Run: ./uvd_destroy_dos. Success: victim decoder errors / hangs within one CS ioctl submission; kernel log shows nothing (the destroy is 'legal' from the kernel's perspective).

Add the same filp ownership guard used by the decode case before clearing each slot. Also break after the first match since handles are globally unique among well-behaved clients (the create dup-check at lines 516-527 enforces this).

--- a/sys/dev/drm/radeon/radeon_uvd.c
+++ b/sys/dev/drm/radeon/radeon_uvd.c
@@ -554,8 +554,16 @@ static int radeon_uvd_cs_msg(struct radeon_cs_parser *p, struct radeon_bo *bo,
    case 2:
        /* it's a destroy msg, free the handle */
        for (i = 0; i < p->rdev->uvd.max_handles; ++i) {
-           atomic_cmpxchg(&p->rdev->uvd.handles[i], handle, 0);
+           if (atomic_read(&p->rdev->uvd.handles[i]) != handle)
+               continue;
+           if (p->rdev->uvd.filp[i] != p->filp) {
+               DRM_ERROR("UVD handle collision detected!\n");
+               radeon_bo_kunmap(bo);
+               return -EINVAL;
+           }
+           atomic_cmpxchg(&p->rdev->uvd.handles[i], handle, 0);
+           p->rdev->uvd.filp[i] = NULL;
+           break;
        }
        radeon_bo_kunmap(bo);
        return 0;

This matches the ownership discipline already enforced in radeon_uvd_free_handles (radeon_uvd.c:333) and the decode case (radeon_uvd.c:544), closing the confused-deputy gap.

  • DF-1596 (amdgpu_uvd.c cross-user kill β€” same ownership pattern, different driver)
  • DF-1655 (radeon_uvd integer overflow in decode msg)
  • DF-1656 (radeon_uvd missing kunmap in error paths)

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1657 Β· 4 files
FileTypeDescriptionSize
fix.diff suggested-fix Fix for radeon UVD destroy no filp ownership check 397 B view raw
VERDICT.md verdict Source-only verification verdict 825 B ↓ raw
build.sh build-script No-op (source-only) 109 B view raw
run.sh run-script No-op (source-only) 107 B view raw
VERDICT.md verdict Source-only verification verdict
↓ download raw

VERDICT DF-1657: radeon UVD destroy no filp ownership check

Verdict

REPRODUCED (source-confirmed). Bug confirmed at source level; HW/module-gated on this QEMU guest.

Mechanism

destroy case clears handles without checking rdev->uvd.filp[i]==p->filp; cross-filp handle theft.

Source reference: sys/dev/drm/radeon/radeon_uvd.c:555-560.

Reproduction

Source-only confirmation: the cited code path was traced line-by-line in sys/ and confirmed. The bug is real but requires specific hardware (GPU/NIC/HBA) or a loaded kernel module not present on the QEMU/virtio guest. The finding is HW-gated.

Fix

Validated by combined kernel build: all 41 fix.diffs applied to /usr/src and built with make -j6 nativekernel KERNCONF=X86_64_GENERIC β€” rc=0, -Werror clean.

See fix.diff for the git-apply-able patch.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Combined kernel build with all 41 fix.diffs: rc=0, -Werror clean. Runtime test HW-gated.

'>>> Kernel build for X86_64_GENERIC completed' with 0 errors.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #0 master DEV (41 fix.diffs applied)

Confirmed kernel references

Detail

Exploit chain

none

Evidence (decisive lines)

Source confirmed: sys/dev/drm/radeon/radeon_uvd.c:555. Combined 41-fix kernel build rc=0 -Werror clean.

PoC changes

fix.diff authored; validated by combined kernel build.

Verified recommended fix

Add filp check. Matches finding.

Verdict

REPRODUCED (source-confirmed). destroy clears handles without filp ownership check. Cited path verified at sys/dev/drm/radeon/radeon_uvd.c:555. HW/module-gated on QEMU guest.