platform_fixups.c
1 /* SPDX-License-Identifier: GPL-2.0-only */ 2 3 #include <commonlib/endian.h> 4 #include <string.h> 5 6 #include "cbfs.h" 7 #include "cbfs_sections.h" 8 #include "elfparsing.h" 9 10 /* 11 * NOTE: This currently only implements support for MBN version 6 (as used by sc7180). Support 12 * for other MBN versions could probably be added but may require more parsing to tell them 13 * apart, and minor modifications (e.g. different hash algorithm). Add later as needed. 14 */ 15 static void *qualcomm_find_hash(struct buffer *in, size_t bb_offset, struct vb2_hash *real_hash) 16 { 17 struct buffer elf; 18 buffer_clone(&elf, in); 19 20 /* When buffer_size(&elf) becomes this small, we know we've searched through 32KiB (or 21 the whole bootblock) without finding anything, so we know we can stop looking. */ 22 size_t search_end_size = MIN(0, buffer_size(in) - 32 * KiB); 23 24 /* To identify a Qualcomm image, first we find the GPT header... */ 25 while (buffer_size(&elf) > search_end_size && 26 !buffer_check_magic(&elf, "EFI PART", 8)) 27 buffer_seek(&elf, 512); 28 29 /* ...then shortly afterwards there's an ELF header... */ 30 while (buffer_size(&elf) > search_end_size && 31 !buffer_check_magic(&elf, ELFMAG, 4)) 32 buffer_seek(&elf, 512); 33 34 if (buffer_size(&elf) <= search_end_size) 35 return NULL; /* Doesn't seem to be a Qualcomm image. */ 36 37 struct parsed_elf pelf; 38 if (parse_elf(&elf, &pelf, ELF_PARSE_PHDR)) 39 return NULL; /* Not an ELF -- guess not a Qualcomm MBN after all? */ 40 41 /* Qualcomm stores an array of SHA-384 hashes in a special ELF segment. One special one 42 to start with, and then one for each segment in order. */ 43 void *bb_hash = NULL; 44 void *hashtable = NULL; 45 int i; 46 int bb_segment = -1; 47 for (i = 0; i < pelf.ehdr.e_phnum; i++) { 48 Elf64_Phdr *ph = &pelf.phdr[i]; 49 if ((ph->p_flags & PF_QC_SG_MASK) == PF_QC_SG_HASH) { 50 if ((int)ph->p_filesz != 51 (pelf.ehdr.e_phnum + 1) * VB2_SHA384_DIGEST_SIZE) { 52 ERROR("fixups: Qualcomm hash segment has wrong size!\n"); 53 goto destroy_elf; 54 } /* Found the table with the hashes -- store its address. */ 55 hashtable = buffer_get(&elf) + ph->p_offset; 56 } else if (bb_segment < 0 && ph->p_offset + ph->p_filesz < buffer_size(&elf) && 57 buffer_offset(&elf) + ph->p_offset <= bb_offset && 58 buffer_offset(&elf) + ph->p_offset + ph->p_filesz > bb_offset) { 59 bb_segment = i; /* Found the bootblock segment -- store its index. */ 60 } 61 } 62 if (!hashtable) /* ELF but no special QC hash segment -- guess not QC after all? */ 63 goto destroy_elf; 64 if (bb_segment < 0) { /* Can assume it's QC if we found the special segment. */ 65 ERROR("fixups: Cannot find bootblock code in Qualcomm MBN!\n"); 66 goto destroy_elf; 67 } 68 69 /* Pass out the actual hash of the current bootblock segment in |real_hash|. */ 70 if (vb2_hash_calculate(false, buffer_get(&elf) + pelf.phdr[bb_segment].p_offset, 71 pelf.phdr[bb_segment].p_filesz, VB2_HASH_SHA384, real_hash)) { 72 ERROR("fixups: vboot digest error\n"); 73 goto destroy_elf; 74 } /* Return pointer to where the bootblock hash needs to go in Qualcomm's table. */ 75 bb_hash = hashtable + (bb_segment + 1) * VB2_SHA384_DIGEST_SIZE; 76 77 destroy_elf: 78 parsed_elf_destroy(&pelf); 79 return bb_hash; 80 } 81 82 static bool qualcomm_probe(struct buffer *buffer, size_t offset) 83 { 84 struct vb2_hash real_hash; 85 void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash); 86 if (!table_hash) 87 return false; 88 89 if (memcmp(real_hash.raw, table_hash, VB2_SHA384_DIGEST_SIZE)) { 90 ERROR("fixups: Identified Qualcomm MBN, but existing hash doesn't match!\n"); 91 return false; 92 } 93 94 return true; 95 } 96 97 static int qualcomm_fixup(struct buffer *buffer, size_t offset) 98 { 99 struct vb2_hash real_hash; 100 void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash); 101 if (!table_hash) { 102 ERROR("fixups: Cannot find Qualcomm MBN headers anymore!\n"); 103 return -1; 104 } 105 106 memcpy(table_hash, real_hash.raw, VB2_SHA384_DIGEST_SIZE); 107 INFO("fixups: Updated Qualcomm MBN header bootblock hash.\n"); 108 return 0; 109 } 110 111 /* 112 * MediaTek bootblock.bin layout (see util/mtkheader/gen-bl-img.py): 113 * header 2048 bytes 114 * gfh info 176 bytes, where bytes 32-35 (in little endian) is the 115 * total size excluding the header (gfh info + data + hash) 116 * data `data_size` bytes 117 * hash 32 bytes, SHA256 of "gfh info + data" 118 * padding 119 */ 120 #define MEDIATEK_BOOTBLOCK_HEADER_SIZE 2048 121 #define MEDIATEK_BOOTBLOCK_GFH_SIZE 176 122 static void *mediatek_find_hash(struct buffer *bootblock, struct vb2_hash *real_hash) 123 { 124 struct buffer buffer; 125 size_t data_size; 126 const char emmc_magic[] = "EMMC_BOOT"; 127 const char sf_magic[] = "SF_BOOT"; 128 const char brlyt_magic[] = "BRLYT"; 129 const size_t brlyt_offset = 512; 130 131 buffer_clone(&buffer, bootblock); 132 133 /* Doesn't seem to be MediaTek image */ 134 if (buffer_size(&buffer) < 135 MEDIATEK_BOOTBLOCK_HEADER_SIZE + MEDIATEK_BOOTBLOCK_GFH_SIZE) 136 return NULL; 137 138 /* Check header magic */ 139 if (!buffer_check_magic(&buffer, emmc_magic, strlen(emmc_magic)) && 140 !buffer_check_magic(&buffer, sf_magic, strlen(sf_magic))) 141 return NULL; 142 143 /* Check "BRLYT" */ 144 buffer_seek(&buffer, brlyt_offset); 145 if (!buffer_check_magic(&buffer, brlyt_magic, strlen(brlyt_magic))) 146 return NULL; 147 148 buffer_seek(&buffer, MEDIATEK_BOOTBLOCK_HEADER_SIZE - brlyt_offset); 149 data_size = read_le32(buffer_get(&buffer) + 32); 150 if (data_size <= MEDIATEK_BOOTBLOCK_GFH_SIZE + VB2_SHA256_DIGEST_SIZE) { 151 ERROR("fixups: MediaTek: data size too small: %zu\n", data_size); 152 return NULL; 153 } 154 data_size -= MEDIATEK_BOOTBLOCK_GFH_SIZE + VB2_SHA256_DIGEST_SIZE; 155 156 if (buffer_size(&buffer) < 157 MEDIATEK_BOOTBLOCK_GFH_SIZE + data_size + VB2_SHA256_DIGEST_SIZE) { 158 ERROR("fixups: MediaTek: not enough data: %zu\n", buffer_size(&buffer)); 159 return NULL; 160 } 161 162 if (vb2_hash_calculate(false, buffer_get(&buffer), 163 MEDIATEK_BOOTBLOCK_GFH_SIZE + data_size, 164 VB2_HASH_SHA256, real_hash)) { 165 ERROR("fixups: MediaTek: vboot digest error\n"); 166 return NULL; 167 } 168 169 buffer_seek(&buffer, MEDIATEK_BOOTBLOCK_GFH_SIZE + data_size); 170 return buffer_get(&buffer); 171 } 172 173 static bool mediatek_probe(struct buffer *buffer) 174 { 175 struct vb2_hash real_hash; 176 void *hash = mediatek_find_hash(buffer, &real_hash); 177 if (!hash) 178 return false; 179 180 if (memcmp(real_hash.raw, hash, VB2_SHA256_DIGEST_SIZE)) { 181 ERROR("fixups: Found MediaTek bootblock, but existing hash doesn't match!\n"); 182 return false; 183 } 184 185 return true; 186 } 187 188 static int mediatek_fixup(struct buffer *buffer, unused size_t offset) 189 { 190 struct vb2_hash real_hash; 191 void *hash = mediatek_find_hash(buffer, &real_hash); 192 if (!hash) { 193 ERROR("fixups: Cannot find MediaTek header anymore!\n"); 194 return -1; 195 } 196 197 memcpy(hash, real_hash.raw, VB2_SHA256_DIGEST_SIZE); 198 INFO("fixups: Updated MediaTek bootblock hash.\n"); 199 return 0; 200 } 201 202 platform_fixup_func platform_fixups_probe(struct buffer *buffer, size_t offset, 203 const char *region_name) 204 { 205 if (!strcmp(region_name, SECTION_NAME_BOOTBLOCK)) { 206 if (qualcomm_probe(buffer, offset)) 207 return qualcomm_fixup; 208 else if (mediatek_probe(buffer)) 209 return mediatek_fixup; 210 } else if (!strcmp(region_name, SECTION_NAME_PRIMARY_CBFS)) { 211 /* TODO: add fixups for primary CBFS bootblock platforms, if needed */ 212 } else { 213 ERROR("%s called for unexpected FMAP region %s!\n", __func__, region_name); 214 } 215 216 return NULL; 217 }