gblsigntool: Import from aosp/gbl-mainline Changes since last import: 1. aosp/3699107 2. aosp/3699126 Test: atest --host gblsigntool_test Change-Id: Ica89c98abf343fa304303868f058dc48b6ba404f Signed-off-by: Dmitrii Merkurev <dimorinny@google.com>
diff --git a/gbl/signtool/README.md b/gbl/signtool/README.md index 792f0f5..b03013e 100644 --- a/gbl/signtool/README.md +++ b/gbl/signtool/README.md
@@ -1,3 +1,9 @@ -GBL Signtool +# GBL Signtool -Commandline tool for signing GBL (Android bootloader) images. +Commandline tool for signing GBL(Android bootloader) images. + +## Code Formatting + +Code formatting is handled using [autopep8](https://github.com/hhatto/autopep8). +A `setup.cfg` file located in the root of the GBL project contains the relevant +configuration settings.
diff --git a/gbl/signtool/gblsigntool.py b/gbl/signtool/gblsigntool.py index 8ace5c5..b10595d 100644 --- a/gbl/signtool/gblsigntool.py +++ b/gbl/signtool/gblsigntool.py
@@ -26,7 +26,7 @@ import struct import tempfile -from avbtool import (AvbFooter, AvbTool) +from avbtool import AvbError, AvbFooter, AvbTool, AvbVBMetaHeader # Source of truth is winnt.h @@ -56,6 +56,28 @@ CERTIFICATE_TABLE_IDX = 4 +BLOCK_SIZE = 4096 + + +def align_up(num, align): + """Return num rounded up to the next multiple of align.""" + return (num + align - 1) // align * align + + +def get_avb_footer(buffer): + """Extract AVB footer from the end of the buffer.""" + if len(buffer) >= AvbFooter.SIZE: + try: + avb_footer = AvbFooter(buffer[-AvbFooter.SIZE:]) + vbmeta_header_start = avb_footer.vbmeta_offset + vbmeta_header_end = vbmeta_header_start + AvbVBMetaHeader.SIZE + # Check avb footer points to a valid vbmeta header. + AvbVBMetaHeader(buffer[vbmeta_header_start:vbmeta_header_end]) + return avb_footer + except (AvbError, LookupError, struct.error): + pass + return None + def unpack_word(buf, off): """Unpacks a little-endian 2-byte word.""" @@ -142,6 +164,24 @@ return unpack_int(self._buf, OFFSET_OPTIONAL_HDR_SIZE_OF_HEADERS) +class SectionHeaders: + """Helper class for PE32/PE32+ section headers.""" + + def __init__(self, buf, number_of_sections): + self._buf = buf + self.number_of_sections = number_of_sections + + def sections_data_offsets(self): + """Yields (start, end) for each section.""" + for idx in range(self.number_of_sections): + off = idx * 40 + data = unpack_int( + self._buf, off + OFFSET_SECTION_HEADER_POINTER_TO_RAW_DATA) + size = unpack_int( + self._buf, off + OFFSET_SECTION_HEADER_SIZE_OF_RAW_DATA) + yield (data, data + size) + + class PEImage: """PE file parser.""" @@ -153,7 +193,7 @@ self._optional_header_offset = ( self._pe_header_offset + SIZEOF_IMAGE_PE_HEADER ) - self._pe_header = PEHeader(self._buf[self._pe_header_offset :]) + self._pe_header = PEHeader(self._buf[self._pe_header_offset:]) self._checksum_offset = ( self._optional_header_offset + OFFSET_OPTIONAL_HDR_CHECKSUM ) @@ -161,8 +201,15 @@ self._optional_header_offset + self._pe_header.size_of_optional_header() ) self._optional_header = OptionalHeader( - self._buf[self._optional_header_offset :] + self._buf[self._optional_header_offset:] ) + self._section_headers = SectionHeaders( + self._buf[self._section_headers_offset:], + self._pe_header.number_of_sections()) + + # Ensure AVB footer can be found even if not at the image end + self.avb_footer = self._get_avb_footer_and_shrink() + if self._optional_header.number_of_data_entries() < 5: raise PEError('PE Optional header data directories table is too small') @@ -172,7 +219,7 @@ self._optional_header_offset + self._optional_header.data_entry_offset(CERTIFICATE_TABLE_IDX) ) - self._buf[certificate_table_offset : certificate_table_offset + 8] = ( + self._buf[certificate_table_offset:certificate_table_offset + 8] = ( 8 * b'\x00' ) if offset or size: @@ -185,15 +232,7 @@ print('Erased existing SecureBoot certificates') def erase_checksum(self): - self._buf[self._checksum_offset : self._checksum_offset + 4] = 4 * b'\x00' - - def get_avb_footer(self): - if len(self._buf) >= AvbFooter.SIZE: - try: - return AvbFooter(self._buf[-AvbFooter.SIZE :]) - except (LookupError, struct.error): - pass - return None + self._buf[self._checksum_offset:self._checksum_offset + 4] = 4 * b'\x00' def authenticode_digest(self): data_directory_certificate_table_offset = ( @@ -212,13 +251,7 @@ self._optional_header.size_of_headers(), ), ] - for idx in range(self._pe_header.number_of_sections()): - off = self._section_headers_offset + idx * 40 - size = unpack_int(self._buf, off + OFFSET_SECTION_HEADER_SIZE_OF_RAW_DATA) - data = unpack_int( - self._buf, off + OFFSET_SECTION_HEADER_POINTER_TO_RAW_DATA - ) - regions.append((data, data + size)) + regions.extend(self._section_headers.sections_data_offsets()) regions.sort(key=lambda e: e[0]) # End junk @@ -229,6 +262,45 @@ hasher.update(self._buf[begin:end]) return hasher.hexdigest() + def _pe_end_offset(self): + """Returns the end offset of a PE file. + + The end of PE file is detected as the maximum of: + - The end of the last section. + - The end of the certificate table (if present). + """ + cert_offset, cert_size = self._optional_header.data_entry( + CERTIFICATE_TABLE_IDX) + certs_end_offset = cert_offset + cert_size + sections_end_offset = max( + end for _, end in self._section_headers.sections_data_offsets()) + return max(certs_end_offset, sections_end_offset) + + def _get_avb_footer_and_shrink(self): + """Locates an AVB footer and shrinks the buffer accordingly. + + Attempts to find an AVB footer at the end of the buffer or within trailing + data after the PE image. If a footer is found, the buffer is shrunk to + handle cases where extra data was appended after flashing. + """ + # Check AVB footer at the end of the image first + footer = get_avb_footer(self._buf) + if footer is not None: + return footer + + # Avoid scanning inside the PE binary + pe_end_offset_aligned = align_up(self._pe_end_offset(), BLOCK_SIZE) + # AVB footer must be at the end of a block-aligned slice + for offset in range(pe_end_offset_aligned, len(self._buf) + 1, BLOCK_SIZE): + footer = get_avb_footer(self._buf[:offset]) + if footer is not None: + footer_offset = offset - AvbFooter.SIZE + print(f'WARNING: AVB footer found at offset: {footer_offset}. ' + 'Expected at the end') + self._buf = self._buf[:offset] + return footer + return None + def gbl_info(args): """Shows info about a GBL image.""" @@ -239,15 +311,14 @@ gbl_image.erase_checksum() print('Authenticode digest (sha256):', gbl_image.authenticode_digest()) - avb_footer = gbl_image.get_avb_footer() - if not avb_footer: + if not gbl_image.avb_footer: raise ValueError('No AVB footer found, image is unsigned') with tempfile.TemporaryDirectory() as temp_dir: gbl_efi = os.path.join(temp_dir, 'gbl.efi') with open(gbl_efi, 'wb') as f: f.write(gbl_image._buf) - gbl_image._buf = gbl_image._buf[: avb_footer.original_image_size] + gbl_image._buf = gbl_image._buf[:gbl_image.avb_footer.original_image_size] print( 'Authenticode digest (without AVB footer):', gbl_image.authenticode_digest(), @@ -263,9 +334,8 @@ gbl_image = PEImage(gbl_bytes) gbl_image.erase_existing_win_certificates() gbl_image.erase_checksum() - avb_footer = gbl_image.get_avb_footer() - if avb_footer: - gbl_image._buf = gbl_image._buf[: avb_footer.original_image_size] + if gbl_image.avb_footer: + gbl_image._buf = gbl_image._buf[:gbl_image.avb_footer.original_image_size] print('Erased existing AVB footer') digest = gbl_image.authenticode_digest() @@ -351,9 +421,8 @@ gbl_image = PEImage(gbl_bytes) gbl_image.erase_existing_win_certificates() gbl_image.erase_checksum() - avb_footer = gbl_image.get_avb_footer() - if avb_footer: - gbl_image._buf = gbl_image._buf[: avb_footer.original_image_size] + if gbl_image.avb_footer: + gbl_image._buf = gbl_image._buf[:gbl_image.avb_footer.original_image_size] print('Erased existing AVB footer') with open(args.output, 'wb') as f: f.write(gbl_image._buf)
diff --git a/gbl/signtool/gblsigntool_test.py b/gbl/signtool/gblsigntool_test.py index 8b00cd7..15612c5 100644 --- a/gbl/signtool/gblsigntool_test.py +++ b/gbl/signtool/gblsigntool_test.py
@@ -14,7 +14,7 @@ # limitations under the License. # - +from contextlib import contextmanager from pathlib import Path import shutil import subprocess @@ -44,6 +44,10 @@ def testInfo_success(self): Gblsigntool('info', TEST_EFI_PATH) + def testInfo_withTail_success(self): + with tmp_appended_tail(TEST_EFI_PATH) as test_efi_with_tail: + Gblsigntool('info', test_efi_with_tail) + def testInfo_notPEImage_failure(self): with tempfile.TemporaryDirectory() as temp_dir: regular_file = Path(temp_dir).joinpath('regular_file') @@ -74,6 +78,10 @@ def testVerify_success(self): Gblsigntool('verify', TEST_EFI_PATH) + def testVerify_withTail_success(self): + with tmp_appended_tail(TEST_EFI_PATH) as test_efi_with_tail: + Gblsigntool('verify', test_efi_with_tail) + def testVerify_pubkeyMismatch_failure(self): with self.assertRaises(subprocess.CalledProcessError): Gblsigntool('verify', TEST_EFI_PATH, '--key', TEST_PUBKEY_PATH) @@ -144,5 +152,15 @@ Gblsigntool('verify', signed_efi, '--key', TEST_PUBKEY_PATH) +@contextmanager +def tmp_appended_tail(path): + with tempfile.TemporaryDirectory() as temp_dir: + appended_path = Path(temp_dir) / path.name + shutil.copy(path, appended_path) + with open(appended_path, 'ab') as f: + f.write(b'\x00' * 4 * 4096) + yield appended_path + + if __name__ == '__main__': unittest.main(verbosity=2)