/ util / cbfstool / platform_fixups.c
platform_fixups.c
  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   */
 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  
 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  
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  
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  
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  
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  }