DF-1483 / harness.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 | /* * DF-1483 harness — amdgpu_cs_user_fence_chunk 32-bit offset overflow * * Reproduces the vulnerable bounds check at * sys/dev/drm/amd/amdgpu/amdgpu_cs.c:58-62 * amdgpu_cs_user_fence_chunk() * * The kernel validates the user-supplied fence offset: * * size = amdgpu_bo_size(bo); // :58 PAGE_SIZE (4096) * if (size != PAGE_SIZE || (data->offset + 8) > size) { // :59 32-bit add! * r = -EINVAL; ... // :60-61 * } * ... * *offset = data->offset; // :69 stored raw u32 * * `data->offset` is __u32 (drm_amdgpu_cs_chunk_fence.offset, * amdgpu_drm.h:588). The addition `(data->offset + 8)` is performed in 32-bit * unsigned arithmetic (both operands are u32/int-width), so it WRAPS: * * data->offset = 0xFFFFFFF8 -> (0xFFFFFFF8 + 8) = 0x100000000 -> 0 (mod 2^32) * 0 > 4096 == false -> check PASSES * *offset = 0xFFFFFFF8 * * Later: * job->uf_addr = (u64)0xFFFFFFF8; // amdgpu_cs_parser_init :234 * job->uf_addr += amdgpu_bo_gpu_offset(uf); // parser_bos :742 * ring emits an 8-byte write at uf_addr // amdgpu_ib.c:243 * * Result: the GPU writes 8 bytes (a predictable fence sequence counter) at * ~4 GB past the PAGE_SIZE fence BO's GPU offset -> into another process's * VRAM/GTT BO or the GTT aperture. Cross-process GPU buffer corruption / DoS. * Reachable from unprivileged /dev/dri/renderDXX (mode 0666). * * Upstream Linux uses 64-bit math for this check. The DragonFly port kept the * 32-bit add. * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> typedef uint32_t u32; typedef uint64_t u64; #define PAGE_SIZE 4096UL /* Reproduce the exact kernel check at amdgpu_cs.c:58-62. `data_offset` is the * __u32 from userspace; `size` is amdgpu_bo_size() which must be PAGE_SIZE. * Returns 0 = check PASSED (bug: attacker wins), -1 = check rejected. */ static int fence_check_kernel_style(u32 data_offset) { u32 size = (u32)PAGE_SIZE; /* amdgpu_cs.c:59 -- note 32-bit arithmetic on (data->offset + 8) */ if (size != (u32)PAGE_SIZE || (data_offset + 8) > size) { return -1; /* rejected */ } return 0; /* passed */ } /* Correct check (what upstream Linux / the fix uses): 64-bit math. */ static int fence_check_fixed(u32 data_offset) { u64 size = PAGE_SIZE; /* Cast to u64 BEFORE the add so it cannot wrap. */ if (size != PAGE_SIZE || ((u64)data_offset + 8) > size) { return -1; /* rejected */ } return 0; /* passed */ } int main(void) { printf("DF-1483 amdgpu_cs_user_fence_chunk 32-bit offset overflow harness\n\n"); /* A few attacker-chosen offsets that wrap the 32-bit add to a small value. */ struct { u32 off; const char *note; } cases[] = { { 0x00000000, "in-bounds baseline (rejected is fine)" }, { 0x00001000, "exactly PAGE_SIZE (in-bounds boundary, accepted)" }, { 0x00001001, "one past PAGE_SIZE-8 (rejected is correct)" }, { 0xFFFFFFF8, "0xFFFFFFF8: +8 wraps to 0 -> check PASSES (BUG)" }, { 0xFFFFFFF9, "0xFFFFFFF9: +8 wraps to 1 -> check PASSES (BUG)" }, { 0xFFFFFFFF, "0xFFFFFFFF: +8 wraps to 7 -> check PASSES (BUG)" }, }; printf(" %-12s | %-12s | %-12s | %s\n", "data->offset", "kernel-check", "fixed-check", "note"); printf(" --------------|--------------|--------------|------------------------\n"); int bug_confirmed = 0; for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) { u32 off = cases[i].off; int kc = fence_check_kernel_style(off); int fc = fence_check_fixed(off); printf(" 0x%08x | %-12s | %-12s | %s\n", off, kc == 0 ? "PASS" : "REJECT", fc == 0 ? "PASS" : "REJECT", cases[i].note); /* The bug: kernel accepts an offset that the 64-bit check rejects. */ if (kc == 0 && fc == -1) { bug_confirmed = 1; } } printf("\n--- What the GPU writes when the buggy check passes ---\n"); u32 bad_off = 0xFFFFFFF8; u64 uf_addr = (u64)bad_off; /* amdgpu_cs.c:69 *offset = data->offset */ u64 gpu_off = 0x80000000ULL; /* hypothetical amdgpu_bo_gpu_offset */ uf_addr += gpu_off; /* parser_bos :742 */ printf(" data->offset = 0x%08x\n", bad_off); printf(" job->uf_addr (init) = 0x%016llx\n", (unsigned long long)(u64)bad_off); printf(" + amdgpu_bo_gpu_off = 0x%016llx\n", (unsigned long long)gpu_off); printf(" final uf_addr = 0x%016llx <- GPU writes 8 bytes HERE\n", (unsigned long long)uf_addr); printf(" bytes past BO base = %llu (== 0xFFFFFFF8, ~4 GB past PAGE_SIZE BO)\n", (unsigned long long)bad_off); if (bug_confirmed) { printf("\nRESULT: integer-overflow bypass CONFIRMED at amdgpu_cs.c:59\n"); printf("(data->offset + 8) is 32-bit: 0xFFFFFFF8+8 wraps to 0, passes the\n"); printf("check. uf_addr=0xFFFFFFF8 is then emitted as a GPU write ~4GB past\n"); printf("the PAGE_SIZE fence BO -> cross-process GPU buffer corruption.\n"); printf("Fix: cast data->offset to u64 BEFORE the add (matches upstream Linux).\n"); return 0; } printf("\nUNEXPECTED: overflow bypass not observed\n"); return 1; } |