| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <commonlib/endian.h> |
| #include <string.h> |
| |
| #include "cbfs.h" |
| #include "cbfs_sections.h" |
| #include "elfparsing.h" |
| |
| /* |
| * NOTE: This currently only implements support for MBN version 6 (as used by sc7180). Support |
| * for other MBN versions could probably be added but may require more parsing to tell them |
| * apart, and minor modifications (e.g. different hash algorithm). Add later as needed. |
| */ |
| static void *qualcomm_find_hash(struct buffer *in, size_t bb_offset, struct vb2_hash *real_hash) |
| { |
| struct buffer elf; |
| buffer_clone(&elf, in); |
| |
| /* When buffer_size(&elf) becomes this small, we know we've searched through 32KiB (or |
| the whole bootblock) without finding anything, so we know we can stop looking. */ |
| size_t search_end_size = MIN(0, buffer_size(in) - 32 * KiB); |
| |
| /* To identify a Qualcomm image, first we find the GPT header... */ |
| while (buffer_size(&elf) > search_end_size && |
| !buffer_check_magic(&elf, "EFI PART", 8)) |
| buffer_seek(&elf, 512); |
| |
| /* ...then shortly afterwards there's an ELF header... */ |
| while (buffer_size(&elf) > search_end_size && |
| !buffer_check_magic(&elf, ELFMAG, 4)) |
| buffer_seek(&elf, 512); |
| |
| if (buffer_size(&elf) <= search_end_size) |
| return NULL; /* Doesn't seem to be a Qualcomm image. */ |
| |
| struct parsed_elf pelf; |
| if (parse_elf(&elf, &pelf, ELF_PARSE_PHDR)) |
| return NULL; /* Not an ELF -- guess not a Qualcomm MBN after all? */ |
| |
| /* Qualcomm stores an array of SHA-384 hashes in a special ELF segment. One special one |
| to start with, and then one for each segment in order. */ |
| void *bb_hash = NULL; |
| void *hashtable = NULL; |
| int i; |
| int bb_segment = -1; |
| for (i = 0; i < pelf.ehdr.e_phnum; i++) { |
| Elf64_Phdr *ph = &pelf.phdr[i]; |
| if ((ph->p_flags & PF_QC_SG_MASK) == PF_QC_SG_HASH) { |
| if ((int)ph->p_filesz != |
| (pelf.ehdr.e_phnum + 1) * VB2_SHA384_DIGEST_SIZE) { |
| ERROR("fixups: Qualcomm hash segment has wrong size!\n"); |
| goto destroy_elf; |
| } /* Found the table with the hashes -- store its address. */ |
| hashtable = buffer_get(&elf) + ph->p_offset; |
| } else if (bb_segment < 0 && ph->p_offset + ph->p_filesz < buffer_size(&elf) && |
| buffer_offset(&elf) + ph->p_offset <= bb_offset && |
| buffer_offset(&elf) + ph->p_offset + ph->p_filesz > bb_offset) { |
| bb_segment = i; /* Found the bootblock segment -- store its index. */ |
| } |
| } |
| if (!hashtable) /* ELF but no special QC hash segment -- guess not QC after all? */ |
| goto destroy_elf; |
| if (bb_segment < 0) { /* Can assume it's QC if we found the special segment. */ |
| ERROR("fixups: Cannot find bootblock code in Qualcomm MBN!\n"); |
| goto destroy_elf; |
| } |
| |
| /* Pass out the actual hash of the current bootblock segment in |real_hash|. */ |
| if (vb2_hash_calculate(false, buffer_get(&elf) + pelf.phdr[bb_segment].p_offset, |
| pelf.phdr[bb_segment].p_filesz, VB2_HASH_SHA384, real_hash)) { |
| ERROR("fixups: vboot digest error\n"); |
| goto destroy_elf; |
| } /* Return pointer to where the bootblock hash needs to go in Qualcomm's table. */ |
| bb_hash = hashtable + (bb_segment + 1) * VB2_SHA384_DIGEST_SIZE; |
| |
| destroy_elf: |
| parsed_elf_destroy(&pelf); |
| return bb_hash; |
| } |
| |
| static bool qualcomm_probe(struct buffer *buffer, size_t offset) |
| { |
| struct vb2_hash real_hash; |
| void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash); |
| if (!table_hash) |
| return false; |
| |
| if (memcmp(real_hash.raw, table_hash, VB2_SHA384_DIGEST_SIZE)) { |
| ERROR("fixups: Identified Qualcomm MBN, but existing hash doesn't match!\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int qualcomm_fixup(struct buffer *buffer, size_t offset) |
| { |
| struct vb2_hash real_hash; |
| void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash); |
| if (!table_hash) { |
| ERROR("fixups: Cannot find Qualcomm MBN headers anymore!\n"); |
| return -1; |
| } |
| |
| memcpy(table_hash, real_hash.raw, VB2_SHA384_DIGEST_SIZE); |
| INFO("fixups: Updated Qualcomm MBN header bootblock hash.\n"); |
| return 0; |
| } |
| |
| /* |
| * MediaTek bootblock.bin layout (see util/mtkheader/gen-bl-img.py): |
| * header 2048 bytes |
| * gfh info 176 bytes, where bytes 32-35 (in little endian) is the |
| * total size excluding the header (gfh info + data + hash) |
| * data `data_size` bytes |
| * hash 32 bytes, SHA256 of "gfh info + data" |
| * padding |
| */ |
| #define MEDIATEK_BOOTBLOCK_HEADER_SIZE 2048 |
| #define MEDIATEK_BOOTBLOCK_GFH_SIZE 176 |
| static void *mediatek_find_hash(struct buffer *bootblock, struct vb2_hash *real_hash) |
| { |
| struct buffer buffer; |
| size_t data_size; |
| const char emmc_magic[] = "EMMC_BOOT"; |
| const char sf_magic[] = "SF_BOOT"; |
| const char brlyt_magic[] = "BRLYT"; |
| const size_t brlyt_offset = 512; |
| |
| buffer_clone(&buffer, bootblock); |
| |
| /* Doesn't seem to be MediaTek image */ |
| if (buffer_size(&buffer) < |
| MEDIATEK_BOOTBLOCK_HEADER_SIZE + MEDIATEK_BOOTBLOCK_GFH_SIZE) |
| return NULL; |
| |
| /* Check header magic */ |
| if (!buffer_check_magic(&buffer, emmc_magic, strlen(emmc_magic)) && |
| !buffer_check_magic(&buffer, sf_magic, strlen(sf_magic))) |
| return NULL; |
| |
| /* Check "BRLYT" */ |
| buffer_seek(&buffer, brlyt_offset); |
| if (!buffer_check_magic(&buffer, brlyt_magic, strlen(brlyt_magic))) |
| return NULL; |
| |
| buffer_seek(&buffer, MEDIATEK_BOOTBLOCK_HEADER_SIZE - brlyt_offset); |
| data_size = read_le32(buffer_get(&buffer) + 32); |
| if (data_size <= MEDIATEK_BOOTBLOCK_GFH_SIZE + VB2_SHA256_DIGEST_SIZE) { |
| ERROR("fixups: MediaTek: data size too small: %zu\n", data_size); |
| return NULL; |
| } |
| data_size -= MEDIATEK_BOOTBLOCK_GFH_SIZE + VB2_SHA256_DIGEST_SIZE; |
| |
| if (buffer_size(&buffer) < |
| MEDIATEK_BOOTBLOCK_GFH_SIZE + data_size + VB2_SHA256_DIGEST_SIZE) { |
| ERROR("fixups: MediaTek: not enough data: %zu\n", buffer_size(&buffer)); |
| return NULL; |
| } |
| |
| if (vb2_hash_calculate(false, buffer_get(&buffer), |
| MEDIATEK_BOOTBLOCK_GFH_SIZE + data_size, |
| VB2_HASH_SHA256, real_hash)) { |
| ERROR("fixups: MediaTek: vboot digest error\n"); |
| return NULL; |
| } |
| |
| buffer_seek(&buffer, MEDIATEK_BOOTBLOCK_GFH_SIZE + data_size); |
| return buffer_get(&buffer); |
| } |
| |
| static bool mediatek_probe(struct buffer *buffer) |
| { |
| struct vb2_hash real_hash; |
| void *hash = mediatek_find_hash(buffer, &real_hash); |
| if (!hash) |
| return false; |
| |
| if (memcmp(real_hash.raw, hash, VB2_SHA256_DIGEST_SIZE)) { |
| ERROR("fixups: Found MediaTek bootblock, but existing hash doesn't match!\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int mediatek_fixup(struct buffer *buffer, unused size_t offset) |
| { |
| struct vb2_hash real_hash; |
| void *hash = mediatek_find_hash(buffer, &real_hash); |
| if (!hash) { |
| ERROR("fixups: Cannot find MediaTek header anymore!\n"); |
| return -1; |
| } |
| |
| memcpy(hash, real_hash.raw, VB2_SHA256_DIGEST_SIZE); |
| INFO("fixups: Updated MediaTek bootblock hash.\n"); |
| return 0; |
| } |
| |
| platform_fixup_func platform_fixups_probe(struct buffer *buffer, size_t offset, |
| const char *region_name) |
| { |
| if (!strcmp(region_name, SECTION_NAME_BOOTBLOCK)) { |
| if (qualcomm_probe(buffer, offset)) |
| return qualcomm_fixup; |
| else if (mediatek_probe(buffer)) |
| return mediatek_fixup; |
| } else if (!strcmp(region_name, SECTION_NAME_PRIMARY_CBFS)) { |
| /* TODO: add fixups for primary CBFS bootblock platforms, if needed */ |
| } else { |
| ERROR("%s called for unexpected FMAP region %s!\n", __func__, region_name); |
| } |
| |
| return NULL; |
| } |