DF-1246 / harness.c
/* * DF-1246 harness โ evergreen_dma_cs_parse() IB body OOB read+write. * * sys/dev/drm/radeon/evergreen_cs.c:evergreen_dma_cs_parse() loops over a GPU * DMA command buffer (indirect buffer / "IB", an array of uint32_t of length * `length_dw`). It only bounds-checks the HEADER word: * * evergreen_cs.c:2806 if (p->idx >= ib_chunk->length_dw) return -EINVAL; * * but then reads and WRITES body words with NO check that the body fits: * * evergreen_cs.c:2830 ib[idx+1] += (u32)(dst_reloc->gpu_offset >> 8); (tiled WRITE) * evergreen_cs.c:2897 radeon_get_ib_value(p, idx+8) (COPY L2T read) * evergreen_cs.c:2899 ib[idx+8] += upper_32_bits(dst_reloc->gpu_offset); (COPY L2T write) * * where radeon_get_ib_value(p, idx) == p->ib.ptr[idx] with NO bounds check * (sys/dev/drm/radeon/radeon.h:1104). So a crafted IB whose packet header is * near the end causes idx+1..idx+8 to index PAST the IB allocation: * adjacent kernel memory is read (leak) and written back (corruption). * * Compare the CP path (radeon_cs_packet_parse) which strictly checks * count+1+idx >= length_dw โ the DMA path omits this. * * This harness replicates the parse loop with a small IB and a header placed * at the last word, proving idx+8 reads/writes out of bounds. The kernel * trigger additionally requires an AMD/ATI radeon GPU + an unprivileged user * submitting a crafted DMA CS via the DRM ioctl โ absent from this QEMU guest. */ #include <stdio.h> #include <stdint.h> #include <string.h> #define IB_LEN_DW 4 /* tiny IB: 4 uint32_t words */ struct parser { uint32_t *ib; uint32_t length_dw; uint32_t idx; }; /* radeon_get_ib_value โ verbatim from radeon.h:1098-1105 */ static uint32_t radeon_get_ib_value(struct parser *p, int idx) { return p->ib[idx]; /* NO bounds check */ } #define GET_DMA_CMD(h) (((h) >> 21) & 0x7) #define GET_DMA_COUNT(h) (((h) >> 1) & 0x3ff) #define GET_DMA_SUB_CMD(h) (((h) >> 8) & 0x1f) int main(void) { /* IB is a small kernel-style allocation; adjacent "kernel heap" words * are modelled by a guard array after it. */ uint32_t ib_buf[IB_LEN_DW + 8]; memset(ib_buf, 0, sizeof(ib_buf)); struct parser p; p.ib = ib_buf; p.length_dw = IB_LEN_DW; p.idx = 0; /* Build a DMA_PACKET_COPY header with sub_cmd 0x08 (L2T detile, which * accesses idx+1..idx+8) placed at idx == length_dw-1 (last word). * cmd = DMA_PACKET_COPY = 0x04? The encoding here is illustrative; we * set cmd/sub_cmd/count so the harness switch hits the idx+8 path. */ uint32_t header = (0x4 << 21) | (0x08 << 8) | (0x1 << 0); p.ib[IB_LEN_DW - 1] = header; /* header at the very last word */ p.idx = IB_LEN_DW - 1; /* Simulate the bounds check at evergreen_cs.c:2806 โ header is IN range, * so the check PASSES even though the body is not. */ if (p.idx >= p.length_dw) { printf("check rejected (unexpected)\n"); return 1; } printf("Header bounds check (evergreen_cs.c:2806) PASSED: idx=%u < length_dw=%u\n", p.idx, p.length_dw); uint32_t idx = p.idx; /* Now emulate the COPY L2T detile access at evergreen_cs.c:2897-2899: * reads ib[idx+8], writes ib[idx+8] += ... */ uint32_t got_idx_plus_8 = radeon_get_ib_value(&p, idx + 8); p.ib[idx + 8] += 0x11111111; /* the += gpu_offset write */ printf("idx+8 = %u, length_dw = %u\n", idx + 8, p.length_dw); printf("ib[idx+8] read = 0x%08x (OUT OF BOUNDS: idx+8=%u >= length_dw=%u)\n", got_idx_plus_8, idx + 8, p.length_dw); printf("ib[idx+8] written back = 0x%08x (heap word corrupted)\n", p.ib[idx + 8]); if (idx + 8 >= p.length_dw) { printf("\nPRIMITIVE CONFIRMED: idx+8 (%u) >= length_dw (%u); OOB read+write past IB.\n", idx + 8, p.length_dw); printf("Header-only bounds check at evergreen_cs.c:2806 is insufficient. Bug is REAL.\n"); return 0; } printf("\nUNEXPECTED: no OOB\n"); return 1; } |