blob: 12a5ad737180ea33cd9beefdbcd699a1f02f39bd [file] [log] [blame]
Julius Werner76dab5f2020-03-19 21:09:35 -07001/* SPDX-License-Identifier: GPL-2.0-only */
2
Yu-Ping Wuf1a8dde2022-04-27 10:44:00 +08003#include <commonlib/endian.h>
4#include <string.h>
5
Julius Werner76dab5f2020-03-19 21:09:35 -07006#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 */
15static 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 Wernerd96ca242022-08-08 18:08:35 -070070 if (vb2_hash_calculate(false, buffer_get(&elf) + pelf.phdr[bb_segment].p_offset,
Julius Werner76dab5f2020-03-19 21:09:35 -070071 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
77destroy_elf:
78 parsed_elf_destroy(&pelf);
79 return bb_hash;
80}
81
82static 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
97static 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 Wuf1a8dde2022-04-27 10:44:00 +0800111/*
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
122static 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 Wernerd96ca242022-08-08 18:08:35 -0700162 if (vb2_hash_calculate(false, buffer_get(&buffer),
Yu-Ping Wuf1a8dde2022-04-27 10:44:00 +0800163 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
173static 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
188static 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 Werner76dab5f2020-03-19 21:09:35 -0700202platform_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 Wuf1a8dde2022-04-27 10:44:00 +0800208 else if (mediatek_probe(buffer))
209 return mediatek_fixup;
Julius Werner76dab5f2020-03-19 21:09:35 -0700210 } 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}