DF-4820 / ring_restore_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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | /* * DF-4820 โ mock environment for the verbatim radeon_ring_restore() body. * * Reproduces, in userspace, the exact kernel conditions that hold when * radeon_ring_restore() is called from radeon_gpu_reset() * (sys/dev/drm/radeon/radeon_device.c:1879-1887): * * - radeon_ring_lock() -> radeon_ring_alloc() fails with an error. * Two deterministic failure modes exist at that point, both proven in * the surrounding kernel source: * * (1) -EDEADLK: the GPU was just reset, so rdev->needs_reset is still * true (it is only cleared at radeon_device.c:1921-1922, AFTER the * restore loop). radeon_fence_wait_seq_timeout() * (radeon_fence.c:511-517) has `|| rdev->needs_reset` in its wait * condition and converts it to -EDEADLK. * * (2) -ENOENT: no fences pending (radeon_fence.c:649-654). * * alloc() can only reach the fence wait when the aligned request equals * ring_size/4 dwords โ i.e. the ring was saturated (within align_mask * dwords of full) when the GPU hung. On the fresh post-reset ring * (ring->wptr = 0, cik.c:4104) free_dw maxes at ring_size/4, so the * request can never be satisfied -> error return is guaranteed. * * - `data` points to a kvmalloc_array() allocation made by * radeon_ring_backup() (radeon_ring.c:317). The caller * (radeon_device.c:1881-1882) ignores radeon_ring_restore()'s return * value and never frees `data` on the success-of-reset branch. * * kvmalloc_array()/kvfree() are instrumented to track live allocations. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stdbool.h> /* ---- kernel types / return codes (minimal mock) ---------------------- */ /* values are cosmetic for the mock; only sign matters to the body */ #define ENOENT 2 #define EDEADLK 35 #define ENOMEM 12 struct radeon_device { int dummy; }; struct radeon_ring { unsigned ring_size; /* bytes, power of 2 */ unsigned align_mask; unsigned ptr_mask; unsigned ring_free_dw; }; /* ---- allocation accounting (all test blocks are the same size) ------- */ static unsigned long live_allocs; static unsigned long total_allocs; static unsigned long block_bytes; static void *kvmalloc_array(size_t n, size_t sz, unsigned gfp) { void *p = malloc(n * sz); if (p) { live_allocs++; total_allocs++; } return p; } static void kvfree(void *p) { if (p) { if (live_allocs == 0) { fprintf(stderr, "kvfree of untracked pointer %p!\n", p); exit(3); } live_allocs--; free(p); } } /* ---- mocked collaborators -------------------------------------------- */ /* error injected by radeon_ring_lock(); -EDEADLK (mode 1) or -ENOENT (2) */ static int lock_err = -EDEADLK; static int radeon_ring_lock(struct radeon_device *rdev, struct radeon_ring *ring, unsigned ndw) { /* * Model of radeon_ring_alloc() (radeon_ring.c:104-127) reduced to * its decision on the fresh (just-resumed, wptr=0) ring: * * ndw = 16383 (saturated-ring backup size: full - 1 dw) * aligned = (16383 + 0xf) & ~0xf = 16384 == ring->ring_size/4 * while (16384 > 16384 - 1): * radeon_ring_free_size -> free_dw = 16384 (maximum) * ndw < free_dw ? 16384 < 16384 ? no * radeon_fence_wait_next -> -EDEADLK / -ENOENT -> return r */ unsigned aligned = (ndw + ring->align_mask) & ~ring->align_mask; if (aligned == ring->ring_size / 4) { printf(" [alloc-model] ndw=%u aligned=%u == ring_size/4=%u:" " unsatisfiable on fresh ring -> fence wait -> error %d\n", ndw, aligned, ring->ring_size / 4, lock_err); return lock_err; } return 0; } static void radeon_ring_write(struct radeon_ring *ring, uint32_t v) { } static void radeon_ring_unlock_commit(struct radeon_device *rdev, struct radeon_ring *ring, bool hdp_flush) { /* successful path: body then calls kvfree(data) itself */ } int radeon_ring_restore(struct radeon_device *rdev, struct radeon_ring *ring, unsigned size, uint32_t *data) { int i, r; if (!size || !data) return 0; /* restore the saved ring content */ r = radeon_ring_lock(rdev, ring, size); if (r) { kvfree(data); return r; } for (i = 0; i < size; ++i) { radeon_ring_write(ring, data[i]); } radeon_ring_unlock_commit(rdev, ring, false); kvfree(data); return 0; } /* * DF-4820 main: drive the verbatim radeon_ring_restore() kernel body * (extracted mechanically from the kernel tree by build.sh) in the two * kernel-proven error modes, and measure leaked allocations. * * Each iteration simulates one GPU reset cycle: * radeon_ring_backup() -> kvmalloc_array(size) [tracked] * radeon_ring_restore() -> early `return r` w/o kvfree [the bug] * caller ignores retval (radeon_device.c:1881-1882) */ /* NOTE: no #include here โ build.sh concatenates harness_env.h + the * verbatim kernel body + this file into a single translation unit. */ int main(int argc, char **argv) { struct radeon_device rdev = { 0 }; struct radeon_ring ring = { .ring_size = 64 * 1024, /* typical CP ring */ .align_mask = 0xf, .ptr_mask = (64 * 1024 / 4) - 1, .ring_free_dw = 64 * 1024 / 4, }; /* saturated-ring backup size: ring 100% full minus 1 dword * (radeon_ring_alloc guarantees >= 1 spare dw; commit padding can * bring free_dw to the align boundary โ 16383 is the max pending) */ unsigned size = (ring.ring_size / 4) - 1; int iters = argc > 1 ? atoi(argv[1]) : 1000; int mode = argc > 2 ? atoi(argv[2]) : 1; /* 1=-EDEADLK 2=-ENOENT */ int i, r, failed = 0; block_bytes = (unsigned long)size * sizeof(uint32_t); lock_err = (mode == 2) ? -ENOENT : -EDEADLK; printf("DF-4820 radeon_ring_restore() backup-buffer leak harness\n"); printf("ring_size=%u dw=%u align_mask=0x%x backup_size=%u dw (%lu B)\n", ring.ring_size, ring.ring_size / 4, ring.align_mask, size, block_bytes); printf("injected radeon_ring_lock error: %d (%s)\n", lock_err, mode == 2 ? "-ENOENT (no pending fences)" : "-EDEADLK (needs_reset still true during restore)"); printf("iterations: %d%s\n\n", iters, #ifdef FIXED " [FIXED build: kvfree(data) added on error path]" #else " [BASELINE build: stock kernel body]" #endif ); for (i = 0; i < iters; i++) { uint32_t *data = kvmalloc_array(size, sizeof(uint32_t), 0); if (!data) { fprintf(stderr, "oom at iter %d\n", i); return 2; } /* radeon_device.c:1881: return value ignored */ r = radeon_ring_restore(&rdev, &ring, size, data); if (r) failed++; if (i == 0) printf(" iter 0: radeon_ring_restore -> %d\n", r); } printf("\ntotal restore calls : %d\n", iters); printf("returned error : %d\n", failed); printf("allocations made : %lu (%lu bytes)\n", total_allocs, total_allocs * block_bytes); printf("allocations freed : %lu\n", total_allocs - live_allocs); printf("LIVE (leaked) blocks: %lu (%lu bytes = %lu KiB)\n", live_allocs, live_allocs * block_bytes, live_allocs * block_bytes / 1024); #ifdef FIXED if (live_allocs != 0) { printf("RESULT: FIXED build still leaks โ FAILURE\n"); return 1; } printf("RESULT: FIXED build leaks nothing โ fix VALIDATED\n"); #else if (live_allocs == (unsigned long)iters && failed == iters) { printf("RESULT: LEAK REPRODUCED โ every failed restore leaks " "its %lu-byte kvmalloc backup buffer\n", block_bytes); return 0; } if (live_allocs == 0 && failed == 0) { printf("RESULT: no failure injected โ harness misconfigured\n"); return 2; } printf("RESULT: unexpected state (live=%lu failed=%d)\n", live_allocs, failed); return 2; #endif } |