Julius Werner | 76dab5f | 2020-03-19 21:09:35 -0700 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
| 2 | |
Yu-Ping Wu | f1a8dde | 2022-04-27 10:44:00 +0800 | [diff] [blame] | 3 | #include <commonlib/endian.h> |
| 4 | #include <string.h> |
| 5 | |
Julius Werner | 76dab5f | 2020-03-19 21:09:35 -0700 | [diff] [blame] | 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|. */ |
Julius Werner | d96ca24 | 2022-08-08 18:08:35 -0700 | [diff] [blame] | 70 | if (vb2_hash_calculate(false, buffer_get(&elf) + pelf.phdr[bb_segment].p_offset, |
Julius Werner | 76dab5f | 2020-03-19 21:09:35 -0700 | [diff] [blame] | 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 | |
Yu-Ping Wu | f1a8dde | 2022-04-27 10:44:00 +0800 | [diff] [blame] | 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 | |
Julius Werner | d96ca24 | 2022-08-08 18:08:35 -0700 | [diff] [blame] | 162 | if (vb2_hash_calculate(false, buffer_get(&buffer), |
Yu-Ping Wu | f1a8dde | 2022-04-27 10:44:00 +0800 | [diff] [blame] | 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 | |
Julius Werner | 76dab5f | 2020-03-19 21:09:35 -0700 | [diff] [blame] | 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; |
Yu-Ping Wu | f1a8dde | 2022-04-27 10:44:00 +0800 | [diff] [blame] | 208 | else if (mediatek_probe(buffer)) |
| 209 | return mediatek_fixup; |
Julius Werner | 76dab5f | 2020-03-19 21:09:35 -0700 | [diff] [blame] | 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 | } |