radeon_verify_access returns 0 unconditionally: GEM ownership check dropped β any video-group user can mmap every other user's GPU buffer objects
Summary
The port's TTM .verify_access authorizes every mmap of every user BO to any open file of the card node: the per-file GEM-handle authorization (drm_vma_node_verify_access) present upstream and live in the sibling amdgpu_ttm.c was #if 0'd out, leaving a bare return 0. Full chain: mmap -> drm_mmap_single (no auth gate) -> ttm_bo_mmap -> best-match vma lookup over every user BO -> verify==0 -> cross-user read/write of other users' scanout buffers, textures, upload buffers. Kernel-internal BOs carry no vma node, so ceiling is user GEM objects (Medium). Restore-the-upstream-check fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4429 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 5.7 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| poc_mmap_bo.c | β | 2.4 KB | view raw | |
| victim_bo.c | β | 1.9 KB | view raw | |
| build.sh | β | 125 B | view raw | |
| run.sh | β | 129 B | view raw | |
| fix.diff | β | 840 B | view raw | |
| env.txt | β | 275 B | view raw | |
| verdict.json | β | 3.9 KB | view raw | |
| manifest.json | β | 968 B | view raw |
DF-4429 β radeon_verify_access drops the GEM ownership check (unconditional return 0)
What
sys/dev/drm/radeon/radeon_ttm.c:234-244 β radeon_verify_access(), the
.verify_access callback of struct ttm_bo_driver radeon_bo_driver
(radeon_ttm.c:882), is stubbed to return 0 for every caller:
static int radeon_verify_access(struct ttm_buffer_object *bo, struct file *filp)
{
#if 0
struct radeon_bo *rbo = container_of(bo, struct radeon_bo, tbo);
#endif
if (radeon_ttm_tt_has_userptr(bo->ttm)) /* always false: see 849-861 */
return -EPERM;
return 0;
}
radeon_ttm_tt_has_userptr() itself is hardwired to return false
(radeon_ttm.c:849-861, the real body is #if 0), so the function is an
unconditional return 0. The per-file GEM-handle authorization that upstream
Linux radeon performs (drm_vma_node_verify_access(&rbo->tbo.vma_node,
filp->private_data)) is gone, and the sibling driver in this very tree still
has it: sys/dev/drm/amd/amdgpu/amdgpu_ttm.c:311-325 calls
drm_vma_node_verify_access(&abo->gem_base.vma_node, filp->private_data).
Attack chain (source-verified)
/dev/dri/cardNis root:video 0660 (sys/dev/drm/include/drm/drmP.h:108-110, created atsys/dev/drm/drm_drv.c:1178). Any local user in groupvideocan open it. (On a shared desktop every GPU user is in that group.)mmap(fd, addr, len, prot, MAP_SHARED, offset)on the card node hitsdrm_cdevsw.d_mmap_singleβdrm_mmap_single()(sys/dev/drm/drm_drv.c:1254-1266) βttm_bo_mmap_single()(sys/dev/drm/ttm/ttm_bo_vm.c:766-807) βttm_bo_mmap()(sys/dev/drm/ttm/ttm_bo_vm.c:431-473). No authenticated check on this path (thepriv->authenticatedgate exists only on the legacydrm_mmapmap-hash path,sys/dev/drm/drm_vm.c:53-55).ttm_bo_vm_lookup()(ttm_bo_vm.c:408-429) resolves the requested page range inbdev->vma_managerwith best-match semantics: any node that spans the requested range is returned (drm_vma_offset_lookup_locked()doc,sys/dev/drm/drm_vma_manager.c). Every user-created GEM BO is registered there βttm_bo_init()addsbo->vma_nodeforttm_bo_type_deviceandttm_bo_type_sg(sys/dev/drm/ttm/ttm_bo.c:1266-1271) β and radeon hands exactly those offsets to userspace (radeon_bo_mmap_offset()βdrm_vma_node_offset_addr(&bo->tbo.vma_node),sys/dev/drm/radeon/radeon_object.h:117-120).ttm_bo_mmap()then callsdriver->verify_access(bo, filp)(ttm_bo_vm.c:447) β which for radeon always succeeds, regardless of whether the calling file holds a GEM handle for the BO.- Result: a mapping over the victim BO (
cdev_pager_allocate, OBJT_MGTDEVICE,ttm_bo_vm.c:794-800); faults resolve to the BO's pages (GTT system pages or VRAM via the fictitious range). Read and write.
Practical exploitation
Offsets are device-global, allocated sequentially from page DRM_FILE_PAGE_OFFSET
(= 2^20 pages = 4 GiB, radeon_ttm.c:48/906). They are only returned through
per-handle ioctls, but because lookup is best-match and verify_access is a
no-op, an attacker does not need exact knowledge: probing one page at a time
from offset 4 GiB upward maps every live BO (hit β the node spanning that
page; miss β EINVAL). This gives a video-group user read/write access to every
other user's radeon GPU buffers: Xorg's scanout/framebuffer contents (other
users' windows, VT contents), other users' textures and upload buffers.
Not reachable: kernel-internal BOs (ring buffers, IB pool, page tables) are
ttm_bo_type_kernel and never get a vma node (ttm_bo.c:1269-1271), so the
ceiling is user-created GEM objects β cross-user disclosure + integrity, not
direct kernel-memory access.
PoC
poc_mmap_bo.c β unprivileged prober: opens the card, sweeps single-page
mmap()s from DRM_FILE_PAGE_OFFSET, classifies hits, and (in --read mode)
dumps the first bytes of each mapped page. Run as victim: victim_bo.c
allocates a GTT BO with a marker pattern via libdrm-free raw ioctls.
Build / run (on a machine with radeon KMS hardware)
cc -O2 -o poc_mmap_bo poc_mmap_bo.c cc -O2 -o victim_bo victim_bo.c # terminal A (victim user, in video group): ./victim_bo # allocates marker BO, prints its mmap offset # terminal B (attacker user, in video group): ./poc_mmap_bo # sweeps offsets; marker appears in foreign BO page ./poc_mmap_bo --read # dump foreign BO contents
Success criterion: attacker's sweep finds and reads a BO it never created (marker pattern from the victim's user visible in the dump), while the same PoC against the amdgpu node (or after the fix) returns EACCES.
Verification status
The audit QEMU guest has no AMD/ATI GPU (env.txt: no /dev/dri,
vgapci0 = 0x1234:1111 bochs stub), so the trigger cannot run here. Verdict is
source-verified only (status untested), following the DF-4018/DF-4019
sibling precedent.
Fix
Restore the upstream check, mirroring the in-tree amdgpu implementation:
--- a/sys/dev/drm/radeon/radeon_ttm.c
+++ b/sys/dev/drm/radeon/radeon_ttm.c
@@ -234,10 +234,11 @@ static void radeon_move_null(struct ttm_buffer_object *bo,
static int radeon_verify_access(struct ttm_buffer_object *bo, struct file *filp)
{
-#if 0
struct radeon_bo *rbo = container_of(bo, struct radeon_bo, tbo);
-#endif
- if (radeon_ttm_tt_has_userptr(bo->ttm))
- return -EPERM;
+ if (radeon_ttm_tt_has_userptr(bo->ttm))
+ return -EPERM;
- return 0;
+ return drm_vma_node_verify_access(&rbo->gem_base.vma_node,
+ filp->private_data);
}
drm_vma_node_allow() is already recorded per GEM handle creation
(sys/dev/drm/drm_gem.c:471 in drm_gem_handle_create()), so no other
change is required. See fix.diff.
DF-4429 VERDICT β radeon_verify_access: GEM ownership check dropped
Bottom line
Source-verified, hardware-gated (not runnable on the audit guest): the
DragonFlyBSD radeon port's .verify_access TTM callback authorizes every
mmap of every user-created GPU buffer object to any open file descriptor
of the card node. The upstream authorization call is #if 0'd out; the
sibling amdgpu driver in the same tree still performs it.
Reproduction status
untested β the audit QEMU guest has no AMD/ATI GPU (env.txt: no
/dev/dri, only the QEMU bochs VGA stub 0x1234:1111), so the radeon driver
never attaches there and the trigger cannot execute. The finding is verified
by complete source-path tracing (below), matching the DF-4018/DF-4019 sibling
precedent for amdgpu_ttm hardware-gated verdicts.
Why the bug is real β complete path, every hop cited
- Sink:
sys/dev/drm/radeon/radeon_ttm.c:234-244βradeon_verify_access()reduces toreturn 0. The only conditional callsradeon_ttm_tt_has_userptr(), which is itself hardwiredreturn false(radeon_ttm.c:849-861β real body#if 0). Registered as.verify_accessatradeon_ttm.c:882. - Reachability of the sink: mmap on
/dev/dri/cardNβdrm_mmap_single()(sys/dev/drm/drm_drv.c:1254-1266) βttm_bo_mmap_single()(sys/dev/drm/ttm/ttm_bo_vm.c:766-807) βttm_bo_mmap()(ttm_bo_vm.c:431-473, callsverify_accessat :447). This path performs nopriv->authenticatedcheck β the legacy-auth gate exists only in the old map-hashdrm_mmap(sys/dev/drm/drm_vm.c:53-55), not indrm_mmap_single. - Object discovery:
ttm_bo_vm_lookup()(ttm_bo_vm.c:408-429) usesdrm_vma_offset_lookup_locked()β best-match: any node spanning the requested page range matches. All user BOs (ttm_bo_type_device, created by everyRADEON_GEM_CREATE) are registered inbdev->vma_manageratsys/dev/drm/ttm/ttm_bo.c:1266-1271; offsets handed to userspace come from exactly that node (sys/dev/drm/radeon/radeon_object.h:117-120). Therefore single-page probes fromDRM_FILE_PAGE_OFFSET(4 GiB,radeon_ttm.c:48,906) upward enumerate and map every live user BO. - Contrast in-tree:
sys/dev/drm/amd/amdgpu/amdgpu_ttm.c:311-325performsdrm_vma_node_verify_access(&abo->gem_base.vma_node, filp->private_data); the allow-side is recorded per GEM handle atsys/dev/drm/drm_gem.c:471. Upstream Linux radeon performs the same check. - Access gate: card node is root:video 0660
(
sys/dev/drm/include/drm/drmP.h:108-110,sys/dev/drm/drm_drv.c:1178). Attackers must be in groupvideoβ i.e. exactly the class of users who share a GPU on multi-user desktops.
Impact
Cross-user confidentiality and integrity of GPU memory: any video-group
user can read/write every other user's radeon GEM BOs β Xorg's scanout
buffers (window/VT contents of other users), textures, upload buffers.
Kernel-internal BOs (ttm_bo_type_kernel: rings, IB pool) carry no vma node
(ttm_bo.c:1269) and are not exposed; the ceiling is user GEM objects.
No direct kernel-memory disclosure β not Critical; multi-user group-gated β
Medium.
Exploit chain (for a hardware owner)
victim (userA): ./victim_bo fills a 4 MiB GTT BO with DF4429MARK.
attacker (userB): ./poc_mmap_bo --read sweeps 4 GiB..8 GiB mmap offsets on
the same card node; hits whose dump contains the marker prove foreign-BO
read; PROT_WRITE mappings give symmetric write.
Fix validation
fix.diff restores drm_vma_node_verify_access(&rbo->gem_base.vma_node,
filp->private_data) (mirrors amdgpu_ttm.c). Cannot be kernel-built/tested on
this guest (no radeon hardware; the change only compiles in the radeon module
path) β fix_status: not_testable.
Kernel refs
- sys/dev/drm/radeon/radeon_ttm.c:234-244 (the stub)
- sys/dev/drm/radeon/radeon_ttm.c:849-861 (has_userptr hardwired false)
- sys/dev/drm/radeon/radeon_ttm.c:882 (.verify_access registration)
- sys/dev/drm/ttm/ttm_bo_vm.c:431-473 (ttm_bo_mmap, verify at :447)
- sys/dev/drm/ttm/ttm_bo.c:1266-1271 (which BOs get vma nodes)
- sys/dev/drm/amd/amdgpu/amdgpu_ttm.c:311-325 (in-tree correct sibling)
- sys/dev/drm/drm_drv.c:1254-1266, drm_vm.c:53-55, drmP.h:108-110
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 ownership check exactly as the in-tree amdgpu driver does it (amdgpu_ttm.c:311-325); allow-side already exists (drm_gem.c:471). Cannot be compiled/loaded on the audit guest (no radeon hardware; module only attaches to real devices), so fix validation is not_testable here.
fix.diff; VERDICT.md 'Fix validation' section
Confirmed kernel references
Detail
Exploit chain
['victim (userA, video group): victim_bo.c creates 4MiB GTT BO, fills marker DF4429MARK', 'attacker (userB, video group): poc_mmap_bo.c sweeps single-page mmap(fd,off>=4GiB) on the same card node β best-match lookup + verify_access==0 maps every live BO', "read marker from userB's dump => cross-user GPU memory disclosure; PROT_WRITE mapping gives symmetric corruption of userA's buffers"]
Evidence (decisive lines)
README.md (chain with path:line at every hop), VERDICT.md (full narrative + why guest-untestable), poc_mmap_bo.c + victim_bo.c (runnable on any radeon-equipped DFly box), env.txt (guest hardware absence proof), fix.diff (restores drm_vma_node_verify_access, mirrors amdgpu_ttm.c:311-325)
PoC changes
authored fresh (no seed sketch). Compile-verified on the audit guest: needed -I/usr/src/sys/dev/drm/include/uapi -I/usr/src/sys/dev/drm/include -D__user= -D__kernel= for the uapi ioctls and
Verified recommended fix
radeon_verify_access: return drm_vma_node_verify_access(&rbo->gem_base.vma_node, filp->private_data);
Verdict
source-verified cross-user GPU memory access-control bypass; untested on the audit guest because it has no AMD GPU (no /dev/dri, bochs VGA only β env.txt). Full path traced: mmap(card) -> drm_mmap_single (drm_drv.c:1254, no auth check) -> ttm_bo_mmap_single (ttm_bo_vm.c:766) -> ttm_bo_mmap (ttm_bo_vm.c:447 verify_access==0) with best-match vma lookup (ttm_bo.c:1266 registers every user BO from page 2^20/4GiB up), node perms root:video 0660 (drmP.h:108).
No comments yet.