DF-3592 / ib_parse_oob.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 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | /* * DF-3592 โ si_ib_parse() lacks any packet-fits-in-IB validation; packet * handlers read up to ib[idx+6] on a count-0/short packet at the end of the * IB -> kernel OOB read past the exact-size sa_bo sub-allocation; and * PACKET3_WRITE_DATA with pkt->count < 2 underflows into a ~2^30-iteration * register-walk spin (per-CSioctl CPU stall). * * Statement-faithful logic harness of sys/dev/drm/radeon/si.c: * - si_ib_parse() si.c:4722-4779 * - si_vm_packet3_gfx_check() si.c:4516-4632 (entry + COPY_DATA + * WRITE_DATA + COND_WRITE reads) * - si_vm_reg_valid() si.c:4397-4441 * with ib->length_dw / packet headers exactly as a userspace RADEON_CS * ioctl (radeon_cs.c:599-653) would deliver them (dwords copied 1:1 into an * exact-size radeon_sa_bo sub-allocation, radeon_sa.c:190-212). * * No SI GPU on this guest (virtio-only), so the IB lives in an mmap'd page * ending at a PROT_NONE guard: the kernel-equivalent OOB read faults here * observably at the exact dword index the kernel would touch. * * Build: cc -O -Wall -o ib_parse_oob ib_parse_oob.c * Fixed: cc -O -Wall -DFIXED -o ib_parse_oob_fixed ib_parse_oob.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <signal.h> #include <unistd.h> #include <sys/mman.h> /* radeon_reg.h:3710-3713 */ #define RADEON_CP_PACKET_GET_TYPE(h) (((h) >> 30) & 3) #define RADEON_CP_PACKET_GET_COUNT(h) (((h) >> 16) & 0x3FFF) #define RADEON_CP_PACKET3_GET_OPCODE(h) (((h) >> 8) & 0xFF) #define RADEON_PACKET_TYPE3 3 /* sid.h:1636,1747 */ #define PACKET3_WRITE_DATA 0x37 #define PACKET3_COND_WRITE 0x45 struct radeon_cs_packet { unsigned idx; unsigned type; int count; unsigned one_reg_wr; unsigned opcode; }; struct radeon_ib { uint32_t *ptr; uint32_t length_dw; }; static volatile unsigned oob_idx_marker; /* si_vm_reg_valid(), si.c:4397-4441 (verbatim logic; config whitelist * elided to its default deny) */ static int si_vm_reg_valid(uint32_t reg) { if (reg >= 0x28000) return 1; if (reg >= 0xB000 && reg < 0xC000) return 1; (void)reg; return 0; } static void segv_handler(int sig, siginfo_t *si, void *ctx) { (void)sig; (void)ctx; printf(" SIGSEGV at %p\n", si->si_addr); printf(" OOB READ PROVEN: handler touched a dword beyond " "ib->length_dw (marker idx=%u) โ in-kernel this is a silent " "read past the exact-size sa_bo sub-allocation\n", oob_idx_marker); _exit(42); } /* --- si_vm_packet3_gfx_check() reading cases, si.c:4516-4632 --- */ static int gfx_COND_WRITE(uint32_t *ib, struct radeon_cs_packet *pkt) { uint32_t idx = pkt->idx + 1; uint32_t idx_value = ib[idx]; /* si.c:4521 โ 1 past header */ if (idx_value & 0x100) { uint32_t reg = ib[idx + 5] * 4; /* si.c:4595 โ 6 past header */ if (!si_vm_reg_valid(reg)) return -1; } return 0; } static int gfx_WRITE_DATA(uint32_t *ib, struct radeon_cs_packet *pkt) { uint32_t idx = pkt->idx + 1; uint32_t idx_value = ib[idx]; uint32_t start_reg, i; if ((idx_value & 0xf00) == 0) { start_reg = ib[idx + 1] * 4; /* si.c:4580 */ if (idx_value & 0x10000) { if (!si_vm_reg_valid(start_reg)) return -1; } else { /* si.c:4585: for (i = 0; i < (pkt->count - 2); i++) * count < 2 underflows: (int)(0-2) as unsigned bound */ printf(" WRITE_DATA register walk: count=%d -> " "loop bound (pkt->count - 2) = %u\n", pkt->count, (uint32_t)(pkt->count - 2)); #ifdef FIXED /* fix.diff: unsigned-safe bound */ if (pkt->count <= 2) printf("FIXED: unsigned-safe walk bound -> " "0 iterations (no spin)\n"); for (i = 0; i + 2 < (uint32_t)pkt->count; i++) { #else for (i = 0; i < (uint32_t)(pkt->count - 2); i++) { #endif uint32_t reg = start_reg + (4 * i); if (!si_vm_reg_valid(reg)) { printf(" walk exited after %u " "iterations (reg 0x%x invalid)\n", i, reg); return -1; } if (i >= 5 * 1000 * 1000) { printf(" STILL SPINNING after " "5,000,000 iterations; kernel " "walk would continue to ~2^30 " "before reg wraps invalid " "(seconds of ioctl CPU)\n"); return 0; } } } } return 0; } /* si_ib_parse(), si.c:4722-4779 */ static int si_ib_parse(struct radeon_ib *ib) { int ret = 0; uint32_t idx = 0, i; struct radeon_cs_packet pkt; do { pkt.idx = idx; pkt.type = RADEON_CP_PACKET_GET_TYPE(ib->ptr[idx]); pkt.count = RADEON_CP_PACKET_GET_COUNT(ib->ptr[idx]); pkt.one_reg_wr = 0; switch (pkt.type) { case 0: printf("Packet0 not allowed!\n"); ret = -1; break; case 2: idx += 1; break; case RADEON_PACKET_TYPE3: pkt.opcode = RADEON_CP_PACKET3_GET_OPCODE(ib->ptr[idx]); #ifdef FIXED /* fix.diff: the whole packet must fit in the IB * (mirrors radeon_cs.c:768 for the non-VM path) */ if (pkt.count + 2 > ib->length_dw - idx) { printf("FIXED: packet (idx %u, count %d) " "exceeds ib length %u โ rejected\n", idx, pkt.count, ib->length_dw); ret = -1; break; } #endif if (pkt.opcode == PACKET3_COND_WRITE) { oob_idx_marker = pkt.idx; ret = gfx_COND_WRITE(ib->ptr, &pkt); } else if (pkt.opcode == PACKET3_WRITE_DATA) { ret = gfx_WRITE_DATA(ib->ptr, &pkt); } else { printf(" (opcode 0x%02x allowed-case, no " "handler reads)\n", pkt.opcode); } idx += pkt.count + 2; break; default: printf("Unknown packet type %d !\n", pkt.type); ret = -1; break; } if (ret) { for (i = 0; i < ib->length_dw; i++) printf("\t0x%08x%s\n", ib->ptr[i], i == idx ? " <---" : ""); break; } } while (idx < ib->length_dw); return ret; } int main(void) { struct sigaction sa; long pagesz = sysconf(_SC_PAGESIZE); uint32_t *ibmem; struct radeon_ib ib; int r; setvbuf(stdout, NULL, _IONBF, 0); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = segv_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); /* ---- scenario A: WRITE_DATA count=0 register-walk underflow ---- */ printf("== DF-3592 scenario A: WRITE_DATA pkt->count=0 -> " "(count - 2) underflow spin ==\n"); ibmem = mmap(NULL, pagesz * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (ibmem == MAP_FAILED) { perror("mmap"); return 1; } ib.ptr = (uint32_t *)((char *)ibmem + 8); /* mid-page, 4 dwords */ ib.length_dw = 4; ib.ptr[0] = (3u << 30) | (0u << 16) | (0x37u << 8); /* hdr */ ib.ptr[1] = 0x00000000; /* idx_value: dst=register, !inc */ ib.ptr[2] = 0x0000A000; /* start_reg = 0xA000*4 = 0x28000 (valid) */ r = si_ib_parse(&ib); printf(" scenario A parse ret=%d\n\n", r); /* ---- scenario B: trailing COND_WRITE header, length_dw=1 ---- */ printf("== DF-3592 scenario B: trailing TYPE3 COND_WRITE header, " "length_dw=1 ==\n"); ibmem = mmap(NULL, pagesz * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (ibmem == MAP_FAILED) { perror("mmap"); return 1; } if (mprotect((char *)ibmem + pagesz, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 1; } /* exact-size sub-allocation analogue: 4 bytes ending at the guard * (kernel: sa_bo->eoffset = soffset + size, radeon_sa.c:205) */ ib.ptr = (uint32_t *)((char *)ibmem + pagesz - 4); ib.length_dw = 1; ib.ptr[0] = (3u << 30) | (0u << 16) | (0x45u << 8); printf(" ib->length_dw=1, header=0x%08x (type3, count=0, " "COND_WRITE)\n", ib.ptr[0]); printf(" gfx_check entry reads ib[1]; COND_WRITE reads ib[6] " "(si.c:4595) โ both past the 4-byte allocation\n"); r = si_ib_parse(&ib); #ifdef FIXED printf("FIXED build: scenario B parse ret=%d (rejected before any " "handler read)\n", r); return (r != 0) ? 0 : 1; #else printf("baseline: scenario B parse ret=%d โ returned without fault?! " "(in-kernel the read would be a silent heap overread)\n", r); return 1; #endif } |