Julius Werner | 76dab5f | 2020-03-19 21:09:35 -0700 | [diff] [blame] | 1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
| 2 | |
| 3 | #include "cbfs.h" |
| 4 | #include "cbfs_sections.h" |
| 5 | #include "elfparsing.h" |
| 6 | |
| 7 | /* |
| 8 | * NOTE: This currently only implements support for MBN version 6 (as used by sc7180). Support |
| 9 | * for other MBN versions could probably be added but may require more parsing to tell them |
| 10 | * apart, and minor modifications (e.g. different hash algorithm). Add later as needed. |
| 11 | */ |
| 12 | static void *qualcomm_find_hash(struct buffer *in, size_t bb_offset, struct vb2_hash *real_hash) |
| 13 | { |
| 14 | struct buffer elf; |
| 15 | buffer_clone(&elf, in); |
| 16 | |
| 17 | /* When buffer_size(&elf) becomes this small, we know we've searched through 32KiB (or |
| 18 | the whole bootblock) without finding anything, so we know we can stop looking. */ |
| 19 | size_t search_end_size = MIN(0, buffer_size(in) - 32 * KiB); |
| 20 | |
| 21 | /* To identify a Qualcomm image, first we find the GPT header... */ |
| 22 | while (buffer_size(&elf) > search_end_size && |
| 23 | !buffer_check_magic(&elf, "EFI PART", 8)) |
| 24 | buffer_seek(&elf, 512); |
| 25 | |
| 26 | /* ...then shortly afterwards there's an ELF header... */ |
| 27 | while (buffer_size(&elf) > search_end_size && |
| 28 | !buffer_check_magic(&elf, ELFMAG, 4)) |
| 29 | buffer_seek(&elf, 512); |
| 30 | |
| 31 | if (buffer_size(&elf) <= search_end_size) |
| 32 | return NULL; /* Doesn't seem to be a Qualcomm image. */ |
| 33 | |
| 34 | struct parsed_elf pelf; |
| 35 | if (parse_elf(&elf, &pelf, ELF_PARSE_PHDR)) |
| 36 | return NULL; /* Not an ELF -- guess not a Qualcomm MBN after all? */ |
| 37 | |
| 38 | /* Qualcomm stores an array of SHA-384 hashes in a special ELF segment. One special one |
| 39 | to start with, and then one for each segment in order. */ |
| 40 | void *bb_hash = NULL; |
| 41 | void *hashtable = NULL; |
| 42 | int i; |
| 43 | int bb_segment = -1; |
| 44 | for (i = 0; i < pelf.ehdr.e_phnum; i++) { |
| 45 | Elf64_Phdr *ph = &pelf.phdr[i]; |
| 46 | if ((ph->p_flags & PF_QC_SG_MASK) == PF_QC_SG_HASH) { |
| 47 | if ((int)ph->p_filesz != |
| 48 | (pelf.ehdr.e_phnum + 1) * VB2_SHA384_DIGEST_SIZE) { |
| 49 | ERROR("fixups: Qualcomm hash segment has wrong size!\n"); |
| 50 | goto destroy_elf; |
| 51 | } /* Found the table with the hashes -- store its address. */ |
| 52 | hashtable = buffer_get(&elf) + ph->p_offset; |
| 53 | } else if (bb_segment < 0 && ph->p_offset + ph->p_filesz < buffer_size(&elf) && |
| 54 | buffer_offset(&elf) + ph->p_offset <= bb_offset && |
| 55 | buffer_offset(&elf) + ph->p_offset + ph->p_filesz > bb_offset) { |
| 56 | bb_segment = i; /* Found the bootblock segment -- store its index. */ |
| 57 | } |
| 58 | } |
| 59 | if (!hashtable) /* ELF but no special QC hash segment -- guess not QC after all? */ |
| 60 | goto destroy_elf; |
| 61 | if (bb_segment < 0) { /* Can assume it's QC if we found the special segment. */ |
| 62 | ERROR("fixups: Cannot find bootblock code in Qualcomm MBN!\n"); |
| 63 | goto destroy_elf; |
| 64 | } |
| 65 | |
| 66 | /* Pass out the actual hash of the current bootblock segment in |real_hash|. */ |
| 67 | if (vb2_hash_calculate(buffer_get(&elf) + pelf.phdr[bb_segment].p_offset, |
| 68 | pelf.phdr[bb_segment].p_filesz, VB2_HASH_SHA384, real_hash)) { |
| 69 | ERROR("fixups: vboot digest error\n"); |
| 70 | goto destroy_elf; |
| 71 | } /* Return pointer to where the bootblock hash needs to go in Qualcomm's table. */ |
| 72 | bb_hash = hashtable + (bb_segment + 1) * VB2_SHA384_DIGEST_SIZE; |
| 73 | |
| 74 | destroy_elf: |
| 75 | parsed_elf_destroy(&pelf); |
| 76 | return bb_hash; |
| 77 | } |
| 78 | |
| 79 | static bool qualcomm_probe(struct buffer *buffer, size_t offset) |
| 80 | { |
| 81 | struct vb2_hash real_hash; |
| 82 | void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash); |
| 83 | if (!table_hash) |
| 84 | return false; |
| 85 | |
| 86 | if (memcmp(real_hash.raw, table_hash, VB2_SHA384_DIGEST_SIZE)) { |
| 87 | ERROR("fixups: Identified Qualcomm MBN, but existing hash doesn't match!\n"); |
| 88 | return false; |
| 89 | } |
| 90 | |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | static int qualcomm_fixup(struct buffer *buffer, size_t offset) |
| 95 | { |
| 96 | struct vb2_hash real_hash; |
| 97 | void *table_hash = qualcomm_find_hash(buffer, offset, &real_hash); |
| 98 | if (!table_hash) { |
| 99 | ERROR("fixups: Cannot find Qualcomm MBN headers anymore!\n"); |
| 100 | return -1; |
| 101 | } |
| 102 | |
| 103 | memcpy(table_hash, real_hash.raw, VB2_SHA384_DIGEST_SIZE); |
| 104 | INFO("fixups: Updated Qualcomm MBN header bootblock hash.\n"); |
| 105 | return 0; |
| 106 | } |
| 107 | |
| 108 | platform_fixup_func platform_fixups_probe(struct buffer *buffer, size_t offset, |
| 109 | const char *region_name) |
| 110 | { |
| 111 | if (!strcmp(region_name, SECTION_NAME_BOOTBLOCK)) { |
| 112 | if (qualcomm_probe(buffer, offset)) |
| 113 | return qualcomm_fixup; |
| 114 | } else if (!strcmp(region_name, SECTION_NAME_PRIMARY_CBFS)) { |
| 115 | /* TODO: add fixups for primary CBFS bootblock platforms, if needed */ |
| 116 | } else { |
| 117 | ERROR("%s called for unexpected FMAP region %s!\n", __func__, region_name); |
| 118 | } |
| 119 | |
| 120 | return NULL; |
| 121 | } |