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

radeon: dropped radeon_bo_unreserve() in radeon_driver_open_kms permanently holds the IB-pool BO reservation β€” first DRM close (and every later open) hangs uninterruptible, wedging the GPU for all users

Summary

The port reserves the IB-pool BO in open_kms and never releases it - not on success, not on the error path; the only unreserve in the whole driver is postclose's, behind its own blocking reserve. Non-interruptible lock, so on cayman+ with accel_working the first client's close blocks forever and every subsequent open by any user blocks at the reserve. Linux v3.18 has both unreserve calls; the DF port dropped them. Trivial unpriv trigger: bare open()+close() on the card node. Verified by construction (exactly one unreserve site tree-wide) and upstream diff. Restore-the-two-calls fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4564 Β· 5 files
FileTypeDescriptionSize
README.md β€” 3.6 KB ↓ raw
VERDICT.md β€” 3.3 KB ↓ raw
fix.diff β€” 910 B view raw
manifest.json β€” 816 B view raw
verdict.json β€” 3.5 KB view raw

DF-4564 β€” radeon_kms: dropped radeon_bo_unreserve in open path permanently holds the IB-pool BO reservation β†’ first DRM close/open wedges the GPU for every client

  • File: sys/dev/drm/radeon/radeon_kms.c
  • Class: DragonFly port divergence from Linux upstream β†’ lock leak β†’ unprivileged local DoS (requires a Cayman-or-newer radeon GPU)
  • Verified by code trace + upstream diff; not runnable on the audit guest (no radeon GPU)

TL;DR

radeon_driver_open_kms() reserves the IB-pool buffer object and never releases it β€” not on the success path, not on any error path:

sys/dev/drm/radeon/radeon_kms.c:662
    r = radeon_bo_reserve(rdev->ring_tmp_bo.bo, false);   /* taken */
    if (r)
        goto err_vm_fini;
    vm->ib_bo_va = radeon_vm_bo_add(rdev, vm, rdev->ring_tmp_bo.bo);
    if (!vm->ib_bo_va) { r = -ENOMEM; goto err_vm_fini; }  /* STILL HELD */
    r = radeon_vm_bo_set_addr(rdev, vm->ib_bo_va, ...);
    if (r) goto err_vm_fini;                               /* STILL HELD */
    ...
    return 0;                                              /* STILL HELD */

radeon_bo_reserve(bo, false) β†’ ttm_bo_reserve(&bo->tbo, interruptible=false, ...) β†’ ww_mutex_lock(&bo->resv->lock, NULL) (sys/dev/drm/radeon/radeon_object.h:64, sys/dev/drm/include/drm/ttm/ttm_bo_driver.h:653-676) β€” a blocking, non-interruptible reservation. The reservation is not owned by a thread or syscall; nothing ever releases it:

  • the only radeon_bo_unreserve(rdev->ring_tmp_bo.bo) in the entire driver is the one inside radeon_driver_postclose_kms() at radeon_kms.c:737, guarded by its own radeon_bo_reserve() at :733 succeeding.

Consequences on any cayman+ (rdev->family >= CHIP_CAYMAN) radeon with accel_working:

  1. The first client's close() of /dev/dri/card0 hangs forever inside radeon_driver_postclose_kms() at :733 (ww_mutex_lock, uninterruptible). The process cannot be killed; the fd never finishes closing.
  2. Every subsequent open() by any user hangs at :662 β€” the DRM device is permanently wedged for the whole system (X, wayland, every GL app).
  3. The error-path variant (vm_bo_add ENOMEM / set_addr failure) additionally wedges the device with no client at all.

Upstream divergence (the dropped lines)

Linux v3.18 drivers/gpu/drm/radeon/radeon_kms.c (radeon_driver_open_kms), immediately after radeon_vm_bo_set_addr:

        r = radeon_vm_bo_set_addr(rdev, vm->ib_bo_va, RADEON_VA_IB_OFFSET,
                      RADEON_VM_PAGE_READABLE |
                      RADEON_VM_PAGE_SNOOPED);

        radeon_bo_unreserve(rdev->ring_tmp_bo.bo);     /* <-- dropped in DF */
        if (r) {
            radeon_vm_fini(rdev, vm);
            kfree(fpriv);
            return r;
        }

The DragonFly port removed the unconditional radeon_bo_unreserve() (and the equivalent in the !ib_bo_va error path), leaving the reservation permanently held. Restore the upstream lines (see fix.diff).

Reachability / severity

  • Requires a physically present cayman+ radeon (HD 6900 / GCN era) with accel_working β€” the open path only takes the reservation when rdev->accel_working is set (radeon_kms.c:656).
  • Unprivileged: open()+close() of the DRM node only; no DRM_AUTH, no master, no ioctls needed. First close = permanent wedge.
  • Medium severity (hardware-gated unprivileged DoS; no memory corruption).
  • Not testable on the audit guest (QEMU, no radeon PCI device, /dev/dri absent) β€” verdict is a code-trace verdict: not_reproduced runtime, divergence confirmed against upstream sources.

Fix

fix.diff restores the upstream radeon_bo_unreserve() calls (error path and after radeon_vm_bo_set_addr).

VERDICT.md
↓ download raw

DF-4564 VERDICT β€” not runnable on guest; divergence confirmed by code trace + upstream

Status

  • status: not_reproduced (runtime) β€” the audit guest has no radeon GPU (QEMU vCPU only; no PCI radeon, /dev/dri absent). impact: dos (assessed), confidence: likely β€” the reservation leak itself is certain by construction; the runtime hang follows directly from ww_mutex semantics.

Why the leak is certain by construction

  1. sys/dev/drm/radeon/radeon_kms.c:662 takes radeon_bo_reserve(rdev->ring_tmp_bo.bo, false).
  2. rg radeon_bo_unreserve radeon_kms.c β†’ exactly ONE hit, at :737, inside postclose and behind a second reserve (:733) that must first succeed.
  3. No other code path in the driver unreserves ring_tmp_bo (rg ring_tmp_bo sys/dev/drm/radeon/*.c: radeon_semaphore.c:45 and radeon_ib.c:61 allocate from the SA manager without reserving the BO; radeon_ib.c:200/209/218/241/300 initialize/suspend/fini it at device level, not per-open).
  4. radeon_bo_reserve(bo, false) β†’ ttm_bo_reserve(&bo->tbo, true, false, NULL) (radeon_object.h:64-69) β†’ ww_mutex_lock(&bo->resv->lock, NULL) (ttm_bo_driver.h:653-676): blocking and non-interruptible.
  5. First postclose therefore blocks forever at :733; every later open blocks forever at :662. Cayman+ only (the vm path is under rdev->family >= CHIP_CAYMAN, :648) and accel_working-gated (:656).

Upstream reference

Linux v3.18 drivers/gpu/drm/radeon/radeon_kms.c, radeon_driver_open_kms: radeon_bo_unreserve(rdev->ring_tmp_bo.bo); appears (a) in the !bo_va error path and (b) unconditionally right after radeon_vm_bo_set_addr β€” both dropped in the DragonFly port.

What was checked and found clean in this file (pass 2)

  • radeon_info_ioctl copyout sizing: RADEON_INFO_SI_TILE_MODE_ARRAY copies 432 from tile_mode_array[32] and CIK_MACROTILE 416 from macrotile_mode_array[16] β€” sizes match the declarations (radeon.h:2166,2197,2198). Every request id writes exactly sizeof(u32) or sizeof(u64) to the user-supplied pointer; copy_to_user faults safely.
  • All copy_from_user inputs (CRTC_FROM_ID / WANT_HYPERZ / WANT_CMASK / RING_WORKING / READ_REG) are bounds/allowlist checked before use.
  • gem_va (radeon_gem.c) is guarded by vm_manager.enabled; CS ioctl checks accel_working up front (radeon_cs.c:662) β€” the half-initialized fpriv->vm created by open when !accel_working is never dereferenced.
  • vblank enable/disable bounds-check crtc (radeon_kms.c:840,865); radeon_get_vblank_counter_kms returning (u32)-EINVAL on bad pipe is cosmetic only.
  • ioctl table: all KMS entries DRM_AUTH; legacy entries drm_invalid_op; GEM_USERPTR disabled (#if 0).
  • postclose/open accel_working asymmetry (vm_fini skipped if accel died between open and close) is an init/resume-time-only leak β€” recorded as a negative note, not a finding.

Fix

fix.diff restores the two upstream unreserve calls. Fix not runtime-testable on this guest (no GPU): 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 restores the upstream unreserve calls verbatim; not runtime-testable on this guest (no radeon GPU).

fix.diff; upstream reference in VERDICT.md
↓ fix.diffper-fix-DF-4564

Confirmed kernel references

Detail

Exploit chain

unpriv user with /dev/dri/card0 access on cayman+ hw: open(); close() -> close hangs uninterruptibly in radeon_driver_postclose_kms (ww_mutex_lock) and the fd never finishes closing; every later open() by any process (X server, GL apps) blocks forever at the same reservation in radeon_driver_open_kms -> whole-system GPU DoS. Error-path variant (vm_bo_add ENOMEM / set_addr failure) wedges the device without any client surviving.

Evidence (decisive lines)

VERDICT.md (construction proof + upstream v3.18 excerpt + rg proof that the only unreserve is radeon_kms.c:737); README.md; fix.diff restoring the two upstream unreserve calls

PoC changes

No seed PoC; no trigger authored - the bug is a lock-lifetime defect requiring physical cayman+ radeon hardware to exercise; evidence is the code-trace divergence against Linux v3.18 drivers/gpu/drm/radeon/radeon_kms.c radeon_driver_open_kms.

Verified recommended fix

Restore the two upstream radeon_bo_unreserve(rdev->ring_tmp_bo.bo) calls in radeon_driver_open_kms() - error path after failed vm_bo_add, and unconditionally after radeon_vm_bo_set_addr (see fix.diff).

Verdict

DragonFly's port of radeon_driver_open_kms() dropped both upstream radeon_bo_unreserve(rdev->ring_tmp_bo.bo) calls (Linux v3.18 radeon_kms.c): the IB-pool BO reservation taken at radeon_kms.c:662 is never released on any path - the only unreserve in the whole driver is postclose's, guarded by its own blocking reserve at :733. radeon_bo_reserve(bo,false) is a non-interruptible ww_mutex_lock, so on any cayman+ radeon with accel_working the first client close() blocks forever in postclose and every subsequent open() by any user blocks forever at :662 - the DRM device is permanently wedged system-wide by an unprivileged open/close with no auth or ioctls. Runtime not verifiable on the audit guest (QEMU, no radeon GPU, /dev/dri absent), so the runtime manifestation is assessed from the ww_mutex semantics; the reservation leak itself is certain by construction and by upstream diff.