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)
PoC verification
Evidence pack
findings/poc/DF-4564 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 insideradeon_driver_postclose_kms()at radeon_kms.c:737, guarded by its ownradeon_bo_reserve()at :733 succeeding.
Consequences on any cayman+ (rdev->family >= CHIP_CAYMAN) radeon with
accel_working:
- The first client's
close()of/dev/dri/card0hangs forever insideradeon_driver_postclose_kms()at :733 (ww_mutex_lock, uninterruptible). The process cannot be killed; the fd never finishes closing. - Every subsequent
open()by any user hangs at :662 β the DRM device is permanently wedged for the whole system (X, wayland, every GL app). - 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 whenrdev->accel_workingis 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/driabsent) β verdict is a code-trace verdict:not_reproducedruntime, divergence confirmed against upstream sources.
Fix
fix.diff restores the upstream radeon_bo_unreserve() calls (error path and
after radeon_vm_bo_set_addr).
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
sys/dev/drm/radeon/radeon_kms.c:662takesradeon_bo_reserve(rdev->ring_tmp_bo.bo, false).rg radeon_bo_unreserve radeon_kms.cβ exactly ONE hit, at :737, inside postclose and behind a second reserve (:733) that must first succeed.- 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). 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.- 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 frommacrotile_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 checksaccel_workingup front (radeon_cs.c:662) β the half-initializedfpriv->vmcreated by open when!accel_workingis never dereferenced. - vblank enable/disable bounds-check crtc (radeon_kms.c:840,865);
radeon_get_vblank_counter_kmsreturning (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_workingasymmetry (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_testablefix.diff restores the upstream unreserve calls verbatim; not runtime-testable on this guest (no radeon GPU).
fix.diff; upstream reference in VERDICT.md
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.
No comments yet.