xref: /aosp_15_r20/external/coreboot/util/cbfstool/platform_fixups.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
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  */
qualcomm_find_hash(struct buffer * in,size_t bb_offset,struct vb2_hash * real_hash)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 
qualcomm_probe(struct buffer * buffer,size_t offset)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 
qualcomm_fixup(struct buffer * buffer,size_t offset)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
mediatek_find_hash(struct buffer * bootblock,struct vb2_hash * real_hash)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 
mediatek_probe(struct buffer * buffer)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 
mediatek_fixup(struct buffer * buffer,unused size_t offset)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 
platform_fixups_probe(struct buffer * buffer,size_t offset,const char * region_name)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 }
218