DF-4864 / df4864_trigger.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 | /* * DF-4864 โ amdgpu VM-clear IB overflow harness (userspace replica) * ================================================================ * * Bug: amdgpu_vm_clear_bo() (sys/dev/drm/amd/amdgpu/amdgpu_vm.c) allocates a * 64-byte (16-dword) SA sub-allocation for the clear-IB: * * r = amdgpu_job_alloc_with_ib(adev, 64, &job); [amdgpu_vm.c:855] * * but on CHIP_RAVEN (the only ASIC with pte_support_ats, amdgpu_vm.c:3172) * the ROOT page-directory clear (amdgpu_vm_init -> amdgpu_vm_clear_bo with * level == root_level) takes BOTH branches: * * if (ats_entries) amdgpu_vm_set_pte_pde(... ats_entries ...); (10 dw) * if (entries) amdgpu_vm_set_pte_pde(... entries ...); (10 dw) * * amdgpu_vm_set_pte_pde is a macro (amdgpu_vm.h:293) into * sdma_v4_0_vm_set_pte_pde (sdma_v4_0.c:1184-1200) which emits exactly * TEN dwords per call, writing through ib->ptr[ib->length_dw++] with no * bound of any kind. 2 x 10 = 20 dwords are written into 16 dwords, and * amdgpu_ring_pad_ib() then appends another 4 NOP dwords * ((8 - (20 & 7)) % 8), for 24 dwords = 96 bytes into a 64-byte kernel * SA sub-allocation: a deterministic 32-byte kernel heap OOB write on * every VM create on Raven. * * Trigger numbers (Raven rev0/1, gmc_v9_0.c:941: vm 256TB, block 9, 3 levels): * root_level = PDB2 (amdgpu_vm.c:2986), root PD = 512 entries * (amdgpu_vm_num_entries: round_up(2^36, 2^27) >> 27 = 512) * ats_entries = AMDGPU_GMC_HOLE_START(2^47) >> (27 + 12) = 256 * entries = 512 - 256 = 256 -> BOTH branches taken. * * (Raven rev>=2, gmc_v9_0.c:945: 128TB+512GB -> 257-entry root PD, * ats_entries = 256, entries = 1 -> both branches too.) * * This harness reproduces the EXACT emit/pad sequence (code copied verbatim * from sdma_v4_0.c) against a canaried 64-byte buffer, and probes whether the * running host actually has AMD graphics hardware (the kernel path itself * needs a Raven APU, which this QEMU guest does not have). * * Build: cc -O2 -Wall -o df4864_trigger df4864_trigger.c * Run: ./df4864_trigger */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> /* ---- verbatim packet macros: vega10_sdma_pkt_open.h / sdma_v4_0.c ---- */ #define SDMA_OP_NOP 0 #define SDMA_OP_PTEPDE 12 #define SDMA_PKT_HEADER_OP(x) (((uint32_t)(x) & 0xff) << 0) #define lower_32_bits(n) ((uint32_t)(uint64_t)(n)) #define upper_32_bits(n) ((uint32_t)(((uint64_t)(n)) >> 32)) struct amdgpu_ib_replica { uint32_t *ptr; uint32_t length_dw; }; /* verbatim: sys/dev/drm/amd/amdgpu/sdma_v4_0.c:1184-1200 * sdma_v4_0_vm_set_pte_pde() โ 10 dwords, unbounded */ static void sdma_v4_0_vm_set_pte_pde(struct amdgpu_ib_replica *ib, uint64_t pe, uint64_t addr, unsigned count, uint32_t incr, uint64_t flags) { ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_PTEPDE); ib->ptr[ib->length_dw++] = lower_32_bits(pe); /* dst addr */ ib->ptr[ib->length_dw++] = upper_32_bits(pe); ib->ptr[ib->length_dw++] = lower_32_bits(flags);/* mask */ ib->ptr[ib->length_dw++] = upper_32_bits(flags); ib->ptr[ib->length_dw++] = lower_32_bits(addr); /* value */ ib->ptr[ib->length_dw++] = upper_32_bits(addr); ib->ptr[ib->length_dw++] = incr; /* increment size */ ib->ptr[ib->length_dw++] = 0; ib->ptr[ib->length_dw++] = count - 1; /* number of entries */ } /* verbatim math: sys/dev/drm/amd/amdgpu/sdma_v4_0.c:1208-1222 * sdma_v4_0_ring_pad_ib() โ pads length_dw up to a multiple of 8 dwords */ static void sdma_v4_0_ring_pad_ib(struct amdgpu_ib_replica *ib) { /* kernel variant consults a per-SDMA burst_nop flag; default path below */ uint32_t pad_count = (8 - (ib->length_dw & 0x7)) % 8; uint32_t i; for (i = 0; i < pad_count; i++) ib->ptr[ib->length_dw++] = SDMA_PKT_HEADER_OP(SDMA_OP_NOP); } #define CANARY 0xAA /* Replicates amdgpu_vm_clear_bo()'s IB usage against a canaried pool. */ static int run_clear_bo_case(const char *name, int dual, uint64_t pt_gpu_addr, unsigned ats_entries, unsigned entries) { /* the kernel's SA sub-allocation is 64 bytes (amdgpu_ib_get -> * amdgpu_sa_bo_new(..., size=64, align=256)); we place it inside a * larger canaried region so the overrun is visible, exactly like the * real overrun lands in whatever follows the 64-byte SA hole. */ static uint8_t region[256 /*before*/ + 64 /*the IB*/ + 256 /*after*/]; struct amdgpu_ib_replica ib; uint32_t over_dwords; int i, damaged = 0; memset(region, CANARY, sizeof(region)); ib.ptr = (uint32_t *)(region + 256); ib.length_dw = 0; printf("[%s] amdgpu_job_alloc_with_ib(adev, 64, &job): ib buffer = 16 dwords (64 bytes)\n", name); /* amdgpu_vm.c:867-874 โ the emit sequence */ if (ats_entries) { uint64_t ats_value = 0x00007fe0ULL /* AMDGPU_PTE_DEFAULT_ATC|PDE_PTE, illustrative */; sdma_v4_0_vm_set_pte_pde(&ib, pt_gpu_addr, 0, ats_entries, 0, ats_value); printf("[%s] after set_pte_pde(ats_entries=%u): length_dw=%u\n", name, ats_entries, ib.length_dw); } if (entries) { sdma_v4_0_vm_set_pte_pde(&ib, pt_gpu_addr + (uint64_t)ats_entries * 8, 0, entries, 0, 0); printf("[%s] after set_pte_pde(entries=%u): length_dw=%u\n", name, entries, ib.length_dw); } sdma_v4_0_ring_pad_ib(&ib); printf("[%s] after amdgpu_ring_pad_ib(): length_dw=%u\n", name, ib.length_dw); over_dwords = ib.length_dw > 16 ? ib.length_dw - 16 : 0; printf("[%s] WARN_ON(length_dw > 64) in kernel: %s (bound is the BYTE size โ never trips)\n", name, ib.length_dw > 64 ? "would fire" : "silent"); for (i = 0; i < 256; i++) if (region[256 + 64 + i] != CANARY) damaged++; if (over_dwords) { printf("[%s] *** OOB WRITE: %u dwords (%u bytes) past the 64-byte SA sub-allocation ***\n", name, over_dwords, over_dwords * 4); printf("[%s] *** %d/256 canary bytes after the IB destroyed; first OOB dwords: ", name, damaged); for (i = 0; i < (int)over_dwords && i < 8; i++) printf("0x%08x ", ib.ptr[16 + i]); printf("***\n"); } else { printf("[%s] fits exactly (no overrun) โ this is the non-ATS / non-root case\n", name); } printf("\n"); return over_dwords ? 1 : 0; } static void probe_hw(void) { char buf[4096]; FILE *f; int fd; printf("== hardware probe (kernel-path reachability on THIS host) ==\n"); fd = open("/dev/dri/0", O_RDWR); if (fd >= 0) { printf(" /dev/dri/0: OPEN โ a DRM device is present\n"); close(fd); } else { printf(" /dev/dri/0: %s (no DRM node usable)\n", strerror(errno)); } fd = open("/dev/drm/0", O_RDWR); /* DragonFly drm node name */ if (fd >= 0) { printf(" /dev/drm/0: OPEN\n"); close(fd); } else { printf(" /dev/drm/0: %s\n", strerror(errno)); } f = popen("pciconf -l 2>/dev/null", "r"); if (f) { int amd_vga = 0; while (fgets(buf, sizeof(buf), f)) { if (strstr(buf, "vgapci") || strstr(buf, "display")) printf(" pci: %s", buf); if (strstr(buf, "vgapci") && (strstr(buf, "chip=0x") && strstr(buf, "0x1002"))) amd_vga = 1; } pclose(f); printf(" AMD (0x1002) VGA device: %s\n", amd_vga ? "PRESENT" : "absent"); printf(" -> amdgpu(4) can attach on this host: %s\n", amd_vga ? "YES" : "NO"); } printf("\n"); } int main(void) { int rc = 0; printf("DF-4864 harness: amdgpu_vm_clear_bo() 64-byte IB vs 2x10-dw set_pte_pde + SDMA pad\n"); printf("kernel refs: amdgpu_vm.c:855 alloc(64) / :867-874 dual emit / sdma_v4_0.c:1184-1200 emit\n"); printf(" sdma_v4_0.c:1208-1222 pad / amdgpu_ib.c:61-81 SA(64,align 256)\n\n"); probe_hw(); /* Case 1 โ CHIP_RAVEN root PD clear (the bug): 512-entry root PD, * hole split 256/256 (rev0/1). Dual set_pte_pde. */ rc |= run_clear_bo_case("Raven-root-512", 1, 0x8000000000000000ULL - 0x4000, 256, 256); /* Case 2 โ Raven2 root PD (rev>=2): 257 entries, ats=256, entries=1 */ rc |= run_clear_bo_case("Raven2-root-257", 1, 0x8000000000000000ULL - 0x4000, 256, 1); /* Case 3 โ non-ATS / non-root single call (boundary: exactly fits) */ rc |= run_clear_bo_case("non-ATS-single", 0, 0x123450000ULL, 0, 512); printf("== summary ==\n"); printf(" cases with OOB write: %s\n", rc ? "YES โ 32-byte (24dw) overrun into 16dw buffer" : "no"); return 0; } |