From 26998367eb6153cd24b6e82949d5f7874a036372 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Fri, 3 Sep 2021 22:38:40 +0200 Subject: pe: simplify generate_hash() Copying the value of datasize_in to two further variables and then using all three randomly in the code makes it hard to read. datasize_in is never changed in generate_hash() so we can do with this parameter alone. Rename it to datasize. Signed-off-by: Heinrich Schuchardt --- include/pe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/pe.h') diff --git a/include/pe.h b/include/pe.h index 79bf440c..43727f5e 100644 --- a/include/pe.h +++ b/include/pe.h @@ -25,7 +25,7 @@ handle_image (void *data, unsigned int datasize, UINTN *alloc_pages); EFI_STATUS -generate_hash (char *data, unsigned int datasize_in, +generate_hash (char *data, unsigned int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash, UINT8 *sha1hash); -- cgit v1.2.3 From a2da05fcb8972628bec08e4adfc13abbafc319ad Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Mon, 28 Feb 2022 21:29:16 +0000 Subject: shim: implement SBAT verification for the shim_lock protocol This implements SBAT verification via the shim_lock protocol by moving verification inside the existing verify_buffer() function that is shared by both shim_verify() and handle_image(). The .sbat section is optional for code verified via the shim_lock protocol, unlike for code that is verified and executed directly by shim. For executables that don't have a .sbat section, verification is skipped when using the protocol. A vendor can enforce SBAT verification for code verified via the shim_lock protocol by revoking all pre-SBAT binaries via a dbx update or by using vendor_dbx and then only signing binaries that have a .sbat section from that point. Signed-off-by: Chris Coulson --- include/pe.h | 2 +- pe.c | 46 ++++++++------------------------------ shim.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 79 insertions(+), 42 deletions(-) (limited to 'include/pe.h') diff --git a/include/pe.h b/include/pe.h index 43727f5e..b86e1b3a 100644 --- a/include/pe.h +++ b/include/pe.h @@ -15,7 +15,7 @@ read_header(void *data, unsigned int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context); EFI_STATUS -handle_sbat(char *SBATBase, size_t SBATSize); +verify_sbat_section(char *SBATBase, size_t SBATSize); EFI_STATUS handle_image (void *data, unsigned int datasize, diff --git a/pe.c b/pe.c index 92c2804b..554e77cf 100644 --- a/pe.c +++ b/pe.c @@ -820,7 +820,7 @@ read_header(void *data, unsigned int datasize, } EFI_STATUS -handle_sbat(char *SBATBase, size_t SBATSize) +verify_sbat_section(char *SBATBase, size_t SBATSize) { unsigned int i; EFI_STATUS efi_status; @@ -834,7 +834,12 @@ handle_sbat(char *SBATBase, size_t SBATSize) if (SBATBase == NULL || SBATSize == 0) { dprint(L"No .sbat section data\n"); - return EFI_SECURITY_VIOLATION; + /* + * SBAT is mandatory for binaries loaded by shim, but optional + * for binaries loaded outside of shim but verified via the + * protocol. + */ + return in_protocol ? EFI_SUCCESS : EFI_SECURITY_VIOLATION; } sbat_size = SBATSize + 1; @@ -980,9 +985,6 @@ handle_image (void *data, unsigned int datasize, EFI_IMAGE_SECTION_HEADER *RelocSection = NULL; - char *SBATBase = NULL; - size_t SBATSize = 0; - /* * Copy the executable's sections to their desired offsets */ @@ -1027,33 +1029,6 @@ handle_image (void *data, unsigned int datasize, RelocBaseEnd == end) { RelocSection = Section; } - } else if (CompareMem(Section->Name, ".sbat\0\0\0", 8) == 0) { - if (SBATBase || SBATSize) { - perror(L"Image has multiple SBAT sections\n"); - return EFI_UNSUPPORTED; - } - - if (Section->NumberOfRelocations != 0 || - Section->PointerToRelocations != 0) { - perror(L"SBAT section has relocations\n"); - return EFI_UNSUPPORTED; - } - - /* The virtual size corresponds to the size of the SBAT - * metadata and isn't necessarily a multiple of the file - * alignment. The on-disk size is a multiple of the file - * alignment and is zero padded. Make sure that the - * on-disk size is at least as large as virtual size, - * and ignore the section if it isn't. */ - if (Section->SizeOfRawData && - Section->SizeOfRawData >= Section->Misc.VirtualSize && - base && end) { - SBATBase = base; - /* +1 because of size vs last byte location */ - SBATSize = end - base + 1; - dprint(L"sbat section base:0x%lx size:0x%lx\n", - SBATBase, SBATSize); - } } if (Section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE) { @@ -1095,11 +1070,8 @@ handle_image (void *data, unsigned int datasize, } if (secure_mode ()) { - efi_status = handle_sbat(SBATBase, SBATSize); - - if (!EFI_ERROR(efi_status)) - efi_status = verify_buffer(data, datasize, - &context, sha256hash, sha1hash); + efi_status = verify_buffer(data, datasize, &context, sha256hash, + sha1hash); if (EFI_ERROR(efi_status)) { if (verbose) diff --git a/shim.c b/shim.c index 604c0db1..6d6b1e52 100644 --- a/shim.c +++ b/shim.c @@ -559,9 +559,9 @@ verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig, * Check that the signature is valid and matches the binary */ EFI_STATUS -verify_buffer (char *data, int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - UINT8 *sha256hash, UINT8 *sha1hash) +verify_buffer_authenticode (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash) { EFI_STATUS ret_efi_status; size_t size = datasize; @@ -695,6 +695,71 @@ verify_buffer (char *data, int datasize, return ret_efi_status; } +/* + * Check that the binary is permitted to load by SBAT. + */ +EFI_STATUS +verify_buffer_sbat (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context) +{ + int i; + EFI_IMAGE_SECTION_HEADER *Section; + char *SBATBase = NULL; + size_t SBATSize = 0; + + Section = context->FirstSection; + for (i = 0; i < context->NumberOfSections; i++, Section++) { + if (CompareMem(Section->Name, ".sbat\0\0\0", 8) != 0) + continue; + + if (SBATBase || SBATSize) { + perror(L"Image has multiple SBAT sections\n"); + return EFI_UNSUPPORTED; + } + + if (Section->NumberOfRelocations != 0 || + Section->PointerToRelocations != 0) { + perror(L"SBAT section has relocations\n"); + return EFI_UNSUPPORTED; + } + + /* The virtual size corresponds to the size of the SBAT + * metadata and isn't necessarily a multiple of the file + * alignment. The on-disk size is a multiple of the file + * alignment and is zero padded. Make sure that the + * on-disk size is at least as large as virtual size, + * and ignore the section if it isn't. */ + if (Section->SizeOfRawData && + Section->SizeOfRawData >= Section->Misc.VirtualSize) { + SBATBase = ImageAddress(data, datasize, + Section->PointerToRawData); + SBATSize = Section->SizeOfRawData; + dprint(L"sbat section base:0x%lx size:0x%lx\n", + SBATBase, SBATSize); + } + } + + return verify_sbat_section(SBATBase, SBATSize); +} + +/* + * Check that the signature is valid and matches the binary and that + * the binary is permitted to load by SBAT. + */ +EFI_STATUS +verify_buffer (char *data, int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + UINT8 *sha256hash, UINT8 *sha1hash) +{ + EFI_STATUS efi_status; + + efi_status = verify_buffer_sbat(data, datasize, context); + if (EFI_ERROR(efi_status)) + return efi_status; + + return verify_buffer_authenticode(data, datasize, context, sha256hash, sha1hash); +} + static int is_removable_media_path(EFI_LOADED_IMAGE *li) { @@ -1542,7 +1607,7 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) goto die; } - efi_status = handle_sbat(sbat_start, sbat_end - sbat_start - 1); + efi_status = verify_sbat_section(sbat_start, sbat_end - sbat_start - 1); if (EFI_ERROR(efi_status)) { perror(L"Verifiying shim SBAT data failed: %r\n", efi_status); -- cgit v1.2.3 From bb4b60e800823c1e48220935e3b9180c8c82e1a7 Mon Sep 17 00:00:00 2001 From: Eric Snowberg Date: Thu, 27 Jan 2022 17:33:01 -0500 Subject: Add verify_image In the future we will want to examine binaries without wanting to execute them. Create verify_image based off existing handle_image code. Signed-off-by: Eric Snowberg --- include/pe.h | 4 ++++ pe.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) (limited to 'include/pe.h') diff --git a/include/pe.h b/include/pe.h index b86e1b3a..ccc8798b 100644 --- a/include/pe.h +++ b/include/pe.h @@ -14,6 +14,10 @@ EFI_STATUS read_header(void *data, unsigned int datasize, PE_COFF_LOADER_IMAGE_CONTEXT *context); +EFI_STATUS verify_image(void *data, unsigned int datasize, + EFI_LOADED_IMAGE *li, + PE_COFF_LOADER_IMAGE_CONTEXT *context); + EFI_STATUS verify_sbat_section(char *SBATBase, size_t SBATSize); diff --git a/pe.c b/pe.c index 554e77cf..535d463a 100644 --- a/pe.c +++ b/pe.c @@ -878,6 +878,60 @@ err: return efi_status; } +EFI_STATUS verify_image(void *data, unsigned int datasize, + EFI_LOADED_IMAGE *li, + PE_COFF_LOADER_IMAGE_CONTEXT *context) +{ + EFI_STATUS efi_status; + UINT8 sha1hash[SHA1_DIGEST_SIZE]; + UINT8 sha256hash[SHA256_DIGEST_SIZE]; + + /* + * The binary header contains relevant context and section pointers + */ + efi_status = read_header(data, datasize, context); + if (EFI_ERROR(efi_status)) { + perror(L"Failed to read header: %r\n", efi_status); + return efi_status; + } + + /* + * We only need to verify the binary if we're in secure mode + */ + efi_status = generate_hash(data, datasize, context, sha256hash, + sha1hash); + if (EFI_ERROR(efi_status)) + return efi_status; + + /* Measure the binary into the TPM */ +#ifdef REQUIRE_TPM + efi_status = +#endif + tpm_log_pe((EFI_PHYSICAL_ADDRESS)(UINTN)data, datasize, + (EFI_PHYSICAL_ADDRESS)(UINTN)context->ImageAddress, + li->FilePath, sha1hash, 4); +#ifdef REQUIRE_TPM + if (efi_status != EFI_SUCCESS) { + return efi_status; + } +#endif + + if (secure_mode()) { + efi_status = verify_buffer(data, datasize, + context, sha256hash, sha1hash); + if (EFI_ERROR(efi_status)) { + if (verbose) + console_print(L"Verification failed: %r\n", efi_status); + else + console_error(L"Verification failed", efi_status); + return efi_status; + } else if (verbose) + console_print(L"Verification succeeded\n"); + } + + return EFI_SUCCESS; +} + /* * Once the image has been loaded it needs to be validated and relocated */ -- cgit v1.2.3 From 996496065e9231dc51ca99b903615df8640bb797 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Fri, 28 Apr 2023 14:44:34 -0400 Subject: Split pe.c up even more. This moves the parts of pe.c that *don't* depend on Cryptlib into pe-relocate.c, so we can write test cases for them without having to make a second openssl build without EFI support. Signed-off-by: Peter Jones --- Makefile | 4 +- include/pe.h | 14 ++ pe-relocate.c | 506 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ pe.c | 496 -------------------------------------------------------- 4 files changed, 522 insertions(+), 498 deletions(-) create mode 100644 pe-relocate.c (limited to 'include/pe.h') diff --git a/Makefile b/Makefile index 61077bdf..ee8d37e2 100644 --- a/Makefile +++ b/Makefile @@ -38,9 +38,9 @@ CFLAGS += -DENABLE_SHIM_CERT else TARGETS += $(MMNAME) $(FBNAME) endif -OBJS = shim.o globals.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o sbat_data.o sbat_var.o pe.o httpboot.o csv.o load-options.o +OBJS = shim.o globals.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o sbat_data.o sbat_var.o pe.o pe-relocate.o httpboot.o csv.o load-options.o KEYS = shim_cert.h ocsp.* ca.* shim.crt shim.csr shim.p12 shim.pem shim.key shim.cer -ORIG_SOURCES = shim.c globals.c mok.c netboot.c replacements.c tpm.c errlog.c sbat.c pe.c httpboot.c shim.h version.h $(wildcard include/*.h) cert.S sbat_var.S +ORIG_SOURCES = shim.c globals.c mok.c netboot.c replacements.c tpm.c errlog.c sbat.c pe.c pe-relocate.c httpboot.c shim.h version.h $(wildcard include/*.h) cert.S sbat_var.S MOK_OBJS = MokManager.o PasswordCrypt.o crypt_blowfish.o errlog.o sbat_data.o globals.o ORIG_MOK_SOURCES = MokManager.c PasswordCrypt.c crypt_blowfish.c shim.h $(wildcard include/*.h) FALLBACK_OBJS = fallback.o tpm.o errlog.o sbat_data.o globals.o diff --git a/include/pe.h b/include/pe.h index ccc8798b..9ea9eb44 100644 --- a/include/pe.h +++ b/include/pe.h @@ -21,6 +21,20 @@ EFI_STATUS verify_image(void *data, unsigned int datasize, EFI_STATUS verify_sbat_section(char *SBATBase, size_t SBATSize); +EFI_STATUS +get_section_vma (UINTN section_num, + char *buffer, size_t bufsz UNUSED, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp); + +EFI_STATUS +get_section_vma_by_name (char *name, size_t namesz, + char *buffer, size_t bufsz, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp); + EFI_STATUS handle_image (void *data, unsigned int datasize, EFI_LOADED_IMAGE *li, diff --git a/pe-relocate.c b/pe-relocate.c new file mode 100644 index 00000000..ee5a6153 --- /dev/null +++ b/pe-relocate.c @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * pe-relocate.c - our PE relocation/loading (but not verification) code + * Copyright Peter Jones + */ + +#include "shim.h" + +/* + * Perform basic bounds checking of the intra-image pointers + */ +void * +ImageAddress (void *image, uint64_t size, uint64_t address) +{ + /* ensure our local pointer isn't bigger than our size */ + if (address > size) + return NULL; + + /* Insure our math won't overflow */ + if (UINT64_MAX - address < (uint64_t)(intptr_t)image) + return NULL; + + /* return the absolute pointer */ + return image + address; +} + +/* + * Perform the actual relocation + */ +EFI_STATUS +relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, + EFI_IMAGE_SECTION_HEADER *Section, + void *orig, void *data) +{ + EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd; + UINT64 Adjust; + UINT16 *Reloc, *RelocEnd; + char *Fixup, *FixupBase; + UINT16 *Fixup16; + UINT32 *Fixup32; + UINT64 *Fixup64; + int size = context->ImageSize; + void *ImageEnd = (char *)orig + size; + int n = 0; + + /* Alright, so here's how this works: + * + * context->RelocDir gives us two things: + * - the VA the table of base relocation blocks are (maybe) to be + * mapped at (RelocDir->VirtualAddress) + * - the virtual size (RelocDir->Size) + * + * The .reloc section (Section here) gives us some other things: + * - the name! kind of. (Section->Name) + * - the virtual size (Section->VirtualSize), which should be the same + * as RelocDir->Size + * - the virtual address (Section->VirtualAddress) + * - the file section size (Section->SizeOfRawData), which is + * a multiple of OptHdr->FileAlignment. Only useful for image + * validation, not really useful for iteration bounds. + * - the file address (Section->PointerToRawData) + * - a bunch of stuff we don't use that's 0 in our binaries usually + * - Flags (Section->Characteristics) + * + * and then the thing that's actually at the file address is an array + * of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind + * them. The SizeOfBlock field of this structure includes the + * structure itself, and adding it to that structure's address will + * yield the next entry in the array. + */ + RelocBase = ImageAddress(orig, size, Section->PointerToRawData); + /* RelocBaseEnd here is the address of the first entry /past/ the + * table. */ + RelocBaseEnd = ImageAddress(orig, size, Section->PointerToRawData + + context->RelocDir->Size); + + if (!RelocBase && !RelocBaseEnd) + return EFI_SUCCESS; + + if (!RelocBase || !RelocBaseEnd) { + perror(L"Reloc table overflows binary\n"); + return EFI_UNSUPPORTED; + } + + Adjust = (UINTN)data - context->ImageAddress; + + if (Adjust == 0) + return EFI_SUCCESS; + + while (RelocBase < RelocBaseEnd) { + Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION)); + + if (RelocBase->SizeOfBlock == 0) { + perror(L"Reloc %d block size 0 is invalid\n", n); + return EFI_UNSUPPORTED; + } else if (RelocBase->SizeOfBlock > context->RelocDir->Size) { + perror(L"Reloc %d block size %d greater than reloc dir" + "size %d, which is invalid\n", n, + RelocBase->SizeOfBlock, + context->RelocDir->Size); + return EFI_UNSUPPORTED; + } + + RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock); + if ((void *)RelocEnd < orig || (void *)RelocEnd > ImageEnd) { + perror(L"Reloc %d entry overflows binary\n", n); + return EFI_UNSUPPORTED; + } + + FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress); + if (!FixupBase) { + perror(L"Reloc %d Invalid fixupbase\n", n); + return EFI_UNSUPPORTED; + } + + while (Reloc < RelocEnd) { + Fixup = FixupBase + (*Reloc & 0xFFF); + switch ((*Reloc) >> 12) { + case EFI_IMAGE_REL_BASED_ABSOLUTE: + break; + + case EFI_IMAGE_REL_BASED_HIGH: + Fixup16 = (UINT16 *) Fixup; + *Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16))); + break; + + case EFI_IMAGE_REL_BASED_LOW: + Fixup16 = (UINT16 *) Fixup; + *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust); + break; + + case EFI_IMAGE_REL_BASED_HIGHLOW: + Fixup32 = (UINT32 *) Fixup; + *Fixup32 = *Fixup32 + (UINT32) Adjust; + break; + + case EFI_IMAGE_REL_BASED_DIR64: + Fixup64 = (UINT64 *) Fixup; + *Fixup64 = *Fixup64 + (UINT64) Adjust; + break; + + default: + perror(L"Reloc %d Unknown relocation\n", n); + return EFI_UNSUPPORTED; + } + Reloc += 1; + } + RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd; + n++; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +get_section_vma (UINTN section_num, + char *buffer, size_t bufsz UNUSED, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp) +{ + EFI_IMAGE_SECTION_HEADER *sections = context->FirstSection; + EFI_IMAGE_SECTION_HEADER *section; + char *base = NULL, *end = NULL; + + if (section_num >= context->NumberOfSections) + return EFI_NOT_FOUND; + + if (context->FirstSection == NULL) { + perror(L"Invalid section %d requested\n", section_num); + return EFI_UNSUPPORTED; + } + + section = §ions[section_num]; + + base = ImageAddress (buffer, context->ImageSize, section->VirtualAddress); + end = ImageAddress (buffer, context->ImageSize, + section->VirtualAddress + section->Misc.VirtualSize - 1); + + if (!(section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE)) { + if (!base) { + perror(L"Section %d has invalid base address\n", section_num); + return EFI_UNSUPPORTED; + } + if (!end) { + perror(L"Section %d has zero size\n", section_num); + return EFI_UNSUPPORTED; + } + } + + if (!(section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) && + (section->VirtualAddress < context->SizeOfHeaders || + section->PointerToRawData < context->SizeOfHeaders)) { + perror(L"Section %d is inside image headers\n", section_num); + return EFI_UNSUPPORTED; + } + + if (end < base) { + perror(L"Section %d has negative size\n", section_num); + return EFI_UNSUPPORTED; + } + + *basep = base; + *sizep = end - base; + *sectionp = section; + return EFI_SUCCESS; +} + +EFI_STATUS +get_section_vma_by_name (char *name, size_t namesz, + char *buffer, size_t bufsz, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp) +{ + UINTN i; + char namebuf[9]; + + if (!name || namesz == 0 || !buffer || bufsz < namesz || !context + || !basep || !sizep || !sectionp) + return EFI_INVALID_PARAMETER; + + /* + * This code currently is only used for ".reloc\0\0" and + * ".sbat\0\0\0", and it doesn't know how to look up longer section + * names. + */ + if (namesz > 8) + return EFI_UNSUPPORTED; + + SetMem(namebuf, sizeof(namebuf), 0); + CopyMem(namebuf, name, MIN(namesz, 8)); + + /* + * Copy the executable's sections to their desired offsets + */ + for (i = 0; i < context->NumberOfSections; i++) { + EFI_STATUS status; + EFI_IMAGE_SECTION_HEADER *section = NULL; + char *base = NULL; + size_t size = 0; + + status = get_section_vma(i, buffer, bufsz, context, &base, &size, §ion); + if (!EFI_ERROR(status)) { + if (CompareMem(section->Name, namebuf, 8) == 0) { + *basep = base; + *sizep = size; + *sectionp = section; + return EFI_SUCCESS; + } + continue; + } + + switch(status) { + case EFI_NOT_FOUND: + break; + } + } + + return EFI_NOT_FOUND; +} + +/* here's a chart: + * i686 x86_64 aarch64 + * 64-on-64: nyet yes yes + * 64-on-32: nyet yes nyet + * 32-on-32: yes yes no + */ +static int +allow_64_bit(void) +{ +#if defined(__x86_64__) || defined(__aarch64__) + return 1; +#elif defined(__i386__) || defined(__i686__) + /* Right now blindly assuming the kernel will correctly detect this + * and /halt the system/ if you're not really on a 64-bit cpu */ + if (in_protocol) + return 1; + return 0; +#else /* assuming everything else is 32-bit... */ + return 0; +#endif +} + +static int +allow_32_bit(void) +{ +#if defined(__x86_64__) +#if defined(ALLOW_32BIT_KERNEL_ON_X64) + if (in_protocol) + return 1; + return 0; +#else + return 0; +#endif +#elif defined(__i386__) || defined(__i686__) + return 1; +#elif defined(__aarch64__) + return 0; +#else /* assuming everything else is 32-bit... */ + return 1; +#endif +} + +static int +image_is_64_bit(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) +{ + /* .Magic is the same offset in all cases */ + if (PEHdr->Pe32Plus.OptionalHeader.Magic + == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return 1; + return 0; +} + +static const UINT16 machine_type = +#if defined(__x86_64__) + IMAGE_FILE_MACHINE_X64; +#elif defined(__aarch64__) + IMAGE_FILE_MACHINE_ARM64; +#elif defined(__arm__) + IMAGE_FILE_MACHINE_ARMTHUMB_MIXED; +#elif defined(__i386__) || defined(__i486__) || defined(__i686__) + IMAGE_FILE_MACHINE_I386; +#elif defined(__ia64__) + IMAGE_FILE_MACHINE_IA64; +#else +#error this architecture is not supported by shim +#endif + +static int +image_is_loadable(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) +{ + /* If the machine type doesn't match the binary, bail, unless + * we're in an allowed 64-on-32 scenario */ + if (PEHdr->Pe32.FileHeader.Machine != machine_type) { + if (!(machine_type == IMAGE_FILE_MACHINE_I386 && + PEHdr->Pe32.FileHeader.Machine == IMAGE_FILE_MACHINE_X64 && + allow_64_bit())) { + return 0; + } + } + + /* If it's not a header type we recognize at all, bail */ + switch (PEHdr->Pe32Plus.OptionalHeader.Magic) { + case EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC: + case EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: + break; + default: + return 0; + } + + /* and now just check for general 64-vs-32 compatibility */ + if (image_is_64_bit(PEHdr)) { + if (allow_64_bit()) + return 1; + } else { + if (allow_32_bit()) + return 1; + } + return 0; +} + +/* + * Read the binary header and grab appropriate information from it + */ +EFI_STATUS +read_header(void *data, unsigned int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context) +{ + EFI_IMAGE_DOS_HEADER *DosHdr = data; + EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; + unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize; + unsigned long FileAlignment = 0; + UINT16 DllFlags; + + if (datasize < sizeof (PEHdr->Pe32)) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) + PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); + + if (!image_is_loadable(PEHdr)) { + perror(L"Platform does not support this image\n"); + return EFI_UNSUPPORTED; + } + + if (image_is_64_bit(PEHdr)) { + context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; + context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; + context->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage; + context->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment; + FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment; + OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64); + } else { + context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes; + context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders; + context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage; + context->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment; + FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment; + OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32); + } + + if (FileAlignment % 2 != 0) { + perror(L"File Alignment is invalid (%d)\n", FileAlignment); + return EFI_UNSUPPORTED; + } + if (FileAlignment == 0) + FileAlignment = 0x200; + if (context->SectionAlignment == 0) + context->SectionAlignment = PAGE_SIZE; + if (context->SectionAlignment < FileAlignment) + context->SectionAlignment = FileAlignment; + + context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections; + + if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) { + perror(L"Image header too large\n"); + return EFI_UNSUPPORTED; + } + + HeaderWithoutDataDir = OptHeaderSize + - sizeof (EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES; + if (((UINT32)PEHdr->Pe32.FileHeader.SizeOfOptionalHeader - HeaderWithoutDataDir) != + context->NumberOfRvaAndSizes * sizeof (EFI_IMAGE_DATA_DIRECTORY)) { + perror(L"Image header overflows data directory\n"); + return EFI_UNSUPPORTED; + } + + SectionHeaderOffset = DosHdr->e_lfanew + + sizeof (UINT32) + + sizeof (EFI_IMAGE_FILE_HEADER) + + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader; + if (((UINT32)context->ImageSize - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER + <= context->NumberOfSections) { + perror(L"Image sections overflow image size\n"); + return EFI_UNSUPPORTED; + } + + if ((context->SizeOfHeaders - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER + < (UINT32)context->NumberOfSections) { + perror(L"Image sections overflow section headers\n"); + return EFI_UNSUPPORTED; + } + + if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { + perror(L"Unsupported image type\n"); + return EFI_UNSUPPORTED; + } + + if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { + perror(L"Unsupported image - Relocations have been stripped\n"); + return EFI_UNSUPPORTED; + } + + context->PEHdr = PEHdr; + + if (image_is_64_bit(PEHdr)) { + context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase; + context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint; + context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; + context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + DllFlags = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; + } else { + context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase; + context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint; + context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; + context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + DllFlags = PEHdr->Pe32.OptionalHeader.DllCharacteristics; + } + + if ((mok_policy & MOK_POLICY_REQUIRE_NX) && + !(DllFlags & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) { + perror(L"Policy requires NX, but image does not support NX\n"); + return EFI_UNSUPPORTED; + } + + context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)); + + if (context->ImageSize < context->SizeOfHeaders) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if ((unsigned long)((UINT8 *)context->SecDir - (UINT8 *)data) > + (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (context->SecDir->VirtualAddress > datasize || + (context->SecDir->VirtualAddress == datasize && + context->SecDir->Size > 0)) { + perror(L"Malformed security header\n"); + return EFI_INVALID_PARAMETER; + } + return EFI_SUCCESS; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/pe.c b/pe.c index 18f3e8fc..37753d23 100644 --- a/pe.c +++ b/pe.c @@ -21,152 +21,6 @@ #include -/* - * Perform basic bounds checking of the intra-image pointers - */ -void * -ImageAddress (void *image, uint64_t size, uint64_t address) -{ - /* ensure our local pointer isn't bigger than our size */ - if (address > size) - return NULL; - - /* Insure our math won't overflow */ - if (UINT64_MAX - address < (uint64_t)(intptr_t)image) - return NULL; - - /* return the absolute pointer */ - return image + address; -} - -/* - * Perform the actual relocation - */ -EFI_STATUS -relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, - EFI_IMAGE_SECTION_HEADER *Section, - void *orig, void *data) -{ - EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd; - UINT64 Adjust; - UINT16 *Reloc, *RelocEnd; - char *Fixup, *FixupBase; - UINT16 *Fixup16; - UINT32 *Fixup32; - UINT64 *Fixup64; - int size = context->ImageSize; - void *ImageEnd = (char *)orig + size; - int n = 0; - - /* Alright, so here's how this works: - * - * context->RelocDir gives us two things: - * - the VA the table of base relocation blocks are (maybe) to be - * mapped at (RelocDir->VirtualAddress) - * - the virtual size (RelocDir->Size) - * - * The .reloc section (Section here) gives us some other things: - * - the name! kind of. (Section->Name) - * - the virtual size (Section->VirtualSize), which should be the same - * as RelocDir->Size - * - the virtual address (Section->VirtualAddress) - * - the file section size (Section->SizeOfRawData), which is - * a multiple of OptHdr->FileAlignment. Only useful for image - * validation, not really useful for iteration bounds. - * - the file address (Section->PointerToRawData) - * - a bunch of stuff we don't use that's 0 in our binaries usually - * - Flags (Section->Characteristics) - * - * and then the thing that's actually at the file address is an array - * of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind - * them. The SizeOfBlock field of this structure includes the - * structure itself, and adding it to that structure's address will - * yield the next entry in the array. - */ - RelocBase = ImageAddress(orig, size, Section->PointerToRawData); - /* RelocBaseEnd here is the address of the first entry /past/ the - * table. */ - RelocBaseEnd = ImageAddress(orig, size, Section->PointerToRawData + - context->RelocDir->Size); - - if (!RelocBase && !RelocBaseEnd) - return EFI_SUCCESS; - - if (!RelocBase || !RelocBaseEnd) { - perror(L"Reloc table overflows binary\n"); - return EFI_UNSUPPORTED; - } - - Adjust = (UINTN)data - context->ImageAddress; - - if (Adjust == 0) - return EFI_SUCCESS; - - while (RelocBase < RelocBaseEnd) { - Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION)); - - if (RelocBase->SizeOfBlock == 0) { - perror(L"Reloc %d block size 0 is invalid\n", n); - return EFI_UNSUPPORTED; - } else if (RelocBase->SizeOfBlock > context->RelocDir->Size) { - perror(L"Reloc %d block size %d greater than reloc dir" - "size %d, which is invalid\n", n, - RelocBase->SizeOfBlock, - context->RelocDir->Size); - return EFI_UNSUPPORTED; - } - - RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock); - if ((void *)RelocEnd < orig || (void *)RelocEnd > ImageEnd) { - perror(L"Reloc %d entry overflows binary\n", n); - return EFI_UNSUPPORTED; - } - - FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress); - if (!FixupBase) { - perror(L"Reloc %d Invalid fixupbase\n", n); - return EFI_UNSUPPORTED; - } - - while (Reloc < RelocEnd) { - Fixup = FixupBase + (*Reloc & 0xFFF); - switch ((*Reloc) >> 12) { - case EFI_IMAGE_REL_BASED_ABSOLUTE: - break; - - case EFI_IMAGE_REL_BASED_HIGH: - Fixup16 = (UINT16 *) Fixup; - *Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16))); - break; - - case EFI_IMAGE_REL_BASED_LOW: - Fixup16 = (UINT16 *) Fixup; - *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust); - break; - - case EFI_IMAGE_REL_BASED_HIGHLOW: - Fixup32 = (UINT32 *) Fixup; - *Fixup32 = *Fixup32 + (UINT32) Adjust; - break; - - case EFI_IMAGE_REL_BASED_DIR64: - Fixup64 = (UINT64 *) Fixup; - *Fixup64 = *Fixup64 + (UINT64) Adjust; - break; - - default: - perror(L"Reloc %d Unknown relocation\n", n); - return EFI_UNSUPPORTED; - } - Reloc += 1; - } - RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd; - n++; - } - - return EFI_SUCCESS; -} - #define check_size_line(data, datasize_in, hashbase, hashsize, l) ({ \ if ((unsigned long)hashbase > \ (unsigned long)data + datasize_in) { \ @@ -185,113 +39,6 @@ relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, }) #define check_size(d, ds, h, hs) check_size_line(d, ds, h, hs, __LINE__) -EFI_STATUS -get_section_vma (UINTN section_num, - char *buffer, size_t bufsz UNUSED, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - char **basep, size_t *sizep, - EFI_IMAGE_SECTION_HEADER **sectionp) -{ - EFI_IMAGE_SECTION_HEADER *sections = context->FirstSection; - EFI_IMAGE_SECTION_HEADER *section; - char *base = NULL, *end = NULL; - - if (section_num >= context->NumberOfSections) - return EFI_NOT_FOUND; - - if (context->FirstSection == NULL) { - perror(L"Invalid section %d requested\n", section_num); - return EFI_UNSUPPORTED; - } - - section = §ions[section_num]; - - base = ImageAddress (buffer, context->ImageSize, section->VirtualAddress); - end = ImageAddress (buffer, context->ImageSize, - section->VirtualAddress + section->Misc.VirtualSize - 1); - - if (!(section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE)) { - if (!base) { - perror(L"Section %d has invalid base address\n", section_num); - return EFI_UNSUPPORTED; - } - if (!end) { - perror(L"Section %d has zero size\n", section_num); - return EFI_UNSUPPORTED; - } - } - - if (!(section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) && - (section->VirtualAddress < context->SizeOfHeaders || - section->PointerToRawData < context->SizeOfHeaders)) { - perror(L"Section %d is inside image headers\n", section_num); - return EFI_UNSUPPORTED; - } - - if (end < base) { - perror(L"Section %d has negative size\n", section_num); - return EFI_UNSUPPORTED; - } - - *basep = base; - *sizep = end - base; - *sectionp = section; - return EFI_SUCCESS; -} - -EFI_STATUS -get_section_vma_by_name (char *name, size_t namesz, - char *buffer, size_t bufsz, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - char **basep, size_t *sizep, - EFI_IMAGE_SECTION_HEADER **sectionp) -{ - UINTN i; - char namebuf[9]; - - if (!name || namesz == 0 || !buffer || bufsz < namesz || !context - || !basep || !sizep || !sectionp) - return EFI_INVALID_PARAMETER; - - /* - * This code currently is only used for ".reloc\0\0" and - * ".sbat\0\0\0", and it doesn't know how to look up longer section - * names. - */ - if (namesz > 8) - return EFI_UNSUPPORTED; - - SetMem(namebuf, sizeof(namebuf), 0); - CopyMem(namebuf, name, MIN(namesz, 8)); - - /* - * Copy the executable's sections to their desired offsets - */ - for (i = 0; i < context->NumberOfSections; i++) { - EFI_STATUS status; - EFI_IMAGE_SECTION_HEADER *section = NULL; - char *base = NULL; - size_t size = 0; - - status = get_section_vma(i, buffer, bufsz, context, &base, &size, §ion); - if (!EFI_ERROR(status)) { - if (CompareMem(section->Name, namebuf, 8) == 0) { - *basep = base; - *sizep = size; - *sectionp = section; - return EFI_SUCCESS; - } - continue; - } - - switch(status) { - case EFI_NOT_FOUND: - break; - } - } - - return EFI_NOT_FOUND; -} /* * Calculate the SHA1 and SHA256 hashes of a binary @@ -585,249 +332,6 @@ done: return efi_status; } -/* here's a chart: - * i686 x86_64 aarch64 - * 64-on-64: nyet yes yes - * 64-on-32: nyet yes nyet - * 32-on-32: yes yes no - */ -static int -allow_64_bit(void) -{ -#if defined(__x86_64__) || defined(__aarch64__) - return 1; -#elif defined(__i386__) || defined(__i686__) - /* Right now blindly assuming the kernel will correctly detect this - * and /halt the system/ if you're not really on a 64-bit cpu */ - if (in_protocol) - return 1; - return 0; -#else /* assuming everything else is 32-bit... */ - return 0; -#endif -} - -static int -allow_32_bit(void) -{ -#if defined(__x86_64__) -#if defined(ALLOW_32BIT_KERNEL_ON_X64) - if (in_protocol) - return 1; - return 0; -#else - return 0; -#endif -#elif defined(__i386__) || defined(__i686__) - return 1; -#elif defined(__aarch64__) - return 0; -#else /* assuming everything else is 32-bit... */ - return 1; -#endif -} - -static int -image_is_64_bit(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) -{ - /* .Magic is the same offset in all cases */ - if (PEHdr->Pe32Plus.OptionalHeader.Magic - == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) - return 1; - return 0; -} - -static const UINT16 machine_type = -#if defined(__x86_64__) - IMAGE_FILE_MACHINE_X64; -#elif defined(__aarch64__) - IMAGE_FILE_MACHINE_ARM64; -#elif defined(__arm__) - IMAGE_FILE_MACHINE_ARMTHUMB_MIXED; -#elif defined(__i386__) || defined(__i486__) || defined(__i686__) - IMAGE_FILE_MACHINE_I386; -#elif defined(__ia64__) - IMAGE_FILE_MACHINE_IA64; -#else -#error this architecture is not supported by shim -#endif - -static int -image_is_loadable(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) -{ - /* If the machine type doesn't match the binary, bail, unless - * we're in an allowed 64-on-32 scenario */ - if (PEHdr->Pe32.FileHeader.Machine != machine_type) { - if (!(machine_type == IMAGE_FILE_MACHINE_I386 && - PEHdr->Pe32.FileHeader.Machine == IMAGE_FILE_MACHINE_X64 && - allow_64_bit())) { - return 0; - } - } - - /* If it's not a header type we recognize at all, bail */ - switch (PEHdr->Pe32Plus.OptionalHeader.Magic) { - case EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC: - case EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: - break; - default: - return 0; - } - - /* and now just check for general 64-vs-32 compatibility */ - if (image_is_64_bit(PEHdr)) { - if (allow_64_bit()) - return 1; - } else { - if (allow_32_bit()) - return 1; - } - return 0; -} - -/* - * Read the binary header and grab appropriate information from it - */ -EFI_STATUS -read_header(void *data, unsigned int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context) -{ - EFI_IMAGE_DOS_HEADER *DosHdr = data; - EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; - unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize; - unsigned long FileAlignment = 0; - UINT16 DllFlags; - - if (datasize < sizeof (PEHdr->Pe32)) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) - PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); - - if (!image_is_loadable(PEHdr)) { - perror(L"Platform does not support this image\n"); - return EFI_UNSUPPORTED; - } - - if (image_is_64_bit(PEHdr)) { - context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; - context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; - context->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage; - context->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment; - FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment; - OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64); - } else { - context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes; - context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders; - context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage; - context->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment; - FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment; - OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32); - } - - if (FileAlignment % 2 != 0) { - perror(L"File Alignment is invalid (%d)\n", FileAlignment); - return EFI_UNSUPPORTED; - } - if (FileAlignment == 0) - FileAlignment = 0x200; - if (context->SectionAlignment == 0) - context->SectionAlignment = PAGE_SIZE; - if (context->SectionAlignment < FileAlignment) - context->SectionAlignment = FileAlignment; - - context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections; - - if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) { - perror(L"Image header too large\n"); - return EFI_UNSUPPORTED; - } - - HeaderWithoutDataDir = OptHeaderSize - - sizeof (EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES; - if (((UINT32)PEHdr->Pe32.FileHeader.SizeOfOptionalHeader - HeaderWithoutDataDir) != - context->NumberOfRvaAndSizes * sizeof (EFI_IMAGE_DATA_DIRECTORY)) { - perror(L"Image header overflows data directory\n"); - return EFI_UNSUPPORTED; - } - - SectionHeaderOffset = DosHdr->e_lfanew - + sizeof (UINT32) - + sizeof (EFI_IMAGE_FILE_HEADER) - + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader; - if (((UINT32)context->ImageSize - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER - <= context->NumberOfSections) { - perror(L"Image sections overflow image size\n"); - return EFI_UNSUPPORTED; - } - - if ((context->SizeOfHeaders - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER - < (UINT32)context->NumberOfSections) { - perror(L"Image sections overflow section headers\n"); - return EFI_UNSUPPORTED; - } - - if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { - perror(L"Unsupported image type\n"); - return EFI_UNSUPPORTED; - } - - if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { - perror(L"Unsupported image - Relocations have been stripped\n"); - return EFI_UNSUPPORTED; - } - - context->PEHdr = PEHdr; - - if (image_is_64_bit(PEHdr)) { - context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase; - context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint; - context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; - context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - DllFlags = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; - } else { - context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase; - context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint; - context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; - context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - DllFlags = PEHdr->Pe32.OptionalHeader.DllCharacteristics; - } - - if ((mok_policy & MOK_POLICY_REQUIRE_NX) && - !(DllFlags & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) { - perror(L"Policy requires NX, but image does not support NX\n"); - return EFI_UNSUPPORTED; - } - - context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)); - - if (context->ImageSize < context->SizeOfHeaders) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if ((unsigned long)((UINT8 *)context->SecDir - (UINT8 *)data) > - (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if (context->SecDir->VirtualAddress > datasize || - (context->SecDir->VirtualAddress == datasize && - context->SecDir->Size > 0)) { - perror(L"Malformed security header\n"); - return EFI_INVALID_PARAMETER; - } - return EFI_SUCCESS; -} - EFI_STATUS verify_sbat_section(char *SBATBase, size_t SBATSize) { -- cgit v1.2.3 From 3bce11831343ba6e67740f23ab3a6c6f09bc0bca Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Tue, 25 Feb 2025 11:44:11 -0500 Subject: pe: read_header(): allow skipping SecDir content validation When we're parsing the PE header of shim itself from the Loaded Image object, the signatures aren't present, but the Certificate Table entry in the Data Directory has not been cleared, so it'll fail verification. We know when we're doing that, so this patch makes that test optional. Signed-off-by: Peter Jones --- fuzz-pe-relocate.c | 2 +- include/pe.h | 3 ++- pe-relocate.c | 12 ++++++++---- pe.c | 4 ++-- shim.c | 4 ++-- 5 files changed, 15 insertions(+), 10 deletions(-) (limited to 'include/pe.h') diff --git a/fuzz-pe-relocate.c b/fuzz-pe-relocate.c index 1f62234d..09d38331 100644 --- a/fuzz-pe-relocate.c +++ b/fuzz-pe-relocate.c @@ -28,7 +28,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) memcpy(data_copy, data, size); data_copy[size] = 0; - status = read_header(data_copy, size, &context); + status = read_header(data_copy, size, &context, true); free(data_copy); diff --git a/include/pe.h b/include/pe.h index 9ea9eb44..a1eb8853 100644 --- a/include/pe.h +++ b/include/pe.h @@ -12,7 +12,8 @@ ImageAddress (void *image, uint64_t size, uint64_t address); EFI_STATUS read_header(void *data, unsigned int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context); + PE_COFF_LOADER_IMAGE_CONTEXT *context, + bool check_secdir); EFI_STATUS verify_image(void *data, unsigned int datasize, EFI_LOADED_IMAGE *li, diff --git a/pe-relocate.c b/pe-relocate.c index bde71729..b436d3ec 100644 --- a/pe-relocate.c +++ b/pe-relocate.c @@ -368,7 +368,8 @@ image_is_loadable(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) */ EFI_STATUS read_header(void *data, unsigned int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context) + PE_COFF_LOADER_IMAGE_CONTEXT *context, + bool check_secdir) { EFI_IMAGE_DOS_HEADER *DosHdr = data; EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; @@ -542,9 +543,12 @@ read_header(void *data, unsigned int datasize, return EFI_UNSUPPORTED; } - if (context->SecDir->VirtualAddress > datasize || - (context->SecDir->VirtualAddress == datasize && - context->SecDir->Size > 0)) { + if (check_secdir && + (context->SecDir->VirtualAddress > datasize || + (context->SecDir->VirtualAddress == datasize && + context->SecDir->Size > 0))) { + dprint(L"context->SecDir->VirtualAddress:0x%llx context->SecDir->Size:0x%llx datasize:0x%llx\n", + context->SecDir->VirtualAddress, context->SecDir->Size, datasize); perror(L"Malformed security header\n"); return EFI_INVALID_PARAMETER; } diff --git a/pe.c b/pe.c index 40812bb7..d785c44e 100644 --- a/pe.c +++ b/pe.c @@ -406,7 +406,7 @@ EFI_STATUS verify_image(void *data, unsigned int datasize, /* * The binary header contains relevant context and section pointers */ - efi_status = read_header(data, datasize, context); + efi_status = read_header(data, datasize, context, true); if (EFI_ERROR(efi_status)) { perror(L"Failed to read header: %r\n", efi_status); return efi_status; @@ -482,7 +482,7 @@ handle_image (void *data, unsigned int datasize, /* * The binary header contains relevant context and section pointers */ - efi_status = read_header(data, datasize, &context); + efi_status = read_header(data, datasize, &context, true); if (EFI_ERROR(efi_status)) { perror(L"Failed to read header: %r\n", efi_status); return efi_status; diff --git a/shim.c b/shim.c index 6afb35c5..9efbde11 100644 --- a/shim.c +++ b/shim.c @@ -961,7 +961,7 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) in_protocol = 1; - efi_status = read_header(buffer, size, &context); + efi_status = read_header(buffer, size, &context, true); if (EFI_ERROR(efi_status)) goto done; @@ -1016,7 +1016,7 @@ static EFI_STATUS shim_read_header(void *data, unsigned int datasize, EFI_STATUS efi_status; in_protocol = 1; - efi_status = read_header(data, datasize, context); + efi_status = read_header(data, datasize, context, true); in_protocol = 0; return efi_status; -- cgit v1.2.3 From 89e615081af5fbafefeae5b09def3a003e467838 Mon Sep 17 00:00:00 2001 From: Peter Jones Date: Thu, 20 Feb 2025 19:20:47 -0500 Subject: Add shim's current NX_COMPAT status to HSIStatus hughsie asked me to also make it observable at runtime whether the shim binary that was used to boot was set as NX_COMPAT or not. This adds that into the HSIStatus data as "shim-has-nx-compat-set". Signed-off-by: Peter Jones --- MokVars.txt | 2 ++ include/mok.h | 2 ++ include/pe.h | 3 +++ include/test-data-efivars-1.h | 1 + memattrs.c | 1 + mok.c | 5 ++++- pe-relocate.c | 37 +++++++++++++++++++++++++++++++++---- shim.c | 2 ++ 8 files changed, 48 insertions(+), 5 deletions(-) (limited to 'include/pe.h') diff --git a/MokVars.txt b/MokVars.txt index 0ab81ff6..6ad8ce70 100644 --- a/MokVars.txt +++ b/MokVars.txt @@ -109,3 +109,5 @@ HSIStatus: Status of various security features: 1: platform's DST does populate GetMemorySpaceDescriptor has-set-memory-space-descriptor: 0: platform's DST does not populate SetMemorySpaceDescriptor 1: platform's DST does populate SetMemorySpaceDescriptor + shim-has-nx-compat-set: 0: the running shim binary does not have NX_COMPAT bit set + 1: the running shim binary does have the NX_COMPAT bit set diff --git a/include/mok.h b/include/mok.h index cea4c997..89edf9de 100644 --- a/include/mok.h +++ b/include/mok.h @@ -147,6 +147,8 @@ extern UINTN hsi_status; #define SHIM_HSI_STATUS_HASDSTGMSD 0x00000020ULL /* platform has DST->SetMemorySpaceAttributes */ #define SHIM_HSI_STATUS_HASDSTSMSA 0x00000040ULL +/* This shim has the NX_COMPAT bit set */ +#define SHIM_HSI_STATUS_NX 0x00000100ULL #endif /* !SHIM_MOK_H_ */ // vim:fenc=utf-8:tw=75:noet diff --git a/include/pe.h b/include/pe.h index a1eb8853..ea40184b 100644 --- a/include/pe.h +++ b/include/pe.h @@ -53,5 +53,8 @@ relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, EFI_IMAGE_SECTION_HEADER *Section, void *orig, void *data); +void +get_shim_nx_capability(EFI_HANDLE image_handle); + #endif /* !PE_H_ */ // vim:fenc=utf-8:tw=75:noet diff --git a/include/test-data-efivars-1.h b/include/test-data-efivars-1.h index 7a34ea70..259558e0 100644 --- a/include/test-data-efivars-1.h +++ b/include/test-data-efivars-1.h @@ -114,6 +114,7 @@ static const unsigned char test_data_efivars_1_HSIStatus[] = "has-dxe-services-table: 0\n" "has-get-memory-space-descriptor: 0\n" "has-set-memory-space-attributes: 0\n" + "shim-has-nx-compat-set: 0\n" ; #endif /* !TEST_DATA_EFIVARS_1_H_ */ diff --git a/memattrs.c b/memattrs.c index bc571641..ed8a3ae2 100644 --- a/memattrs.c +++ b/memattrs.c @@ -414,6 +414,7 @@ decode_hsi_bits(UINTN hsi) {.bit = SHIM_HSI_STATUS_HASDST, .name = "HASDST"}, {.bit = SHIM_HSI_STATUS_HASDSTGMSD, .name = "HASDSTGMSD"}, {.bit = SHIM_HSI_STATUS_HASDSTSMSA, .name = "HASDSTSMSA"}, + {.bit = SHIM_HSI_STATUS_NX, .name = "NX"}, {.bit = 0, .name = ""}, }; static int x = 0; diff --git a/mok.c b/mok.c index cb70e7e2..fb4c1489 100644 --- a/mok.c +++ b/mok.c @@ -45,6 +45,7 @@ format_hsi_status(UINT8 *buf, size_t sz, const char hasdxeservices[] = "\nhas-dxe-services-table: "; const char hasdsgmsd[] = "\nhas-get-memory-space-descriptor: "; const char hasdssmsa[] = "\nhas-set-memory-space-attributes: "; + const char shimhasnx[] = "\nshim-has-nx-compat-set: "; const char finale[] = "\n"; char *pos; @@ -55,7 +56,7 @@ format_hsi_status(UINT8 *buf, size_t sz, UINTN ret = sizeof(heapx) + sizeof(stackx) + sizeof(row) + sizeof(hasmap) + sizeof(hasdxeservices) + sizeof(hasdsgmsd) + - sizeof(hasdssmsa) + + sizeof(hasdssmsa) + sizeof(shimhasnx) + sizeof(finale); if (buf == 0 || sz < ret) { @@ -78,6 +79,8 @@ format_hsi_status(UINT8 *buf, size_t sz, pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASDSTGMSD) ? "1" : "0"); pos = stpcpy(pos, hasdssmsa); pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASDSTSMSA) ? "1" : "0"); + pos = stpcpy(pos, shimhasnx); + pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_NX) ? "1" : "0"); stpcpy(pos, finale); return ret; diff --git a/pe-relocate.c b/pe-relocate.c index b436d3ec..4991dbe1 100644 --- a/pe-relocate.c +++ b/pe-relocate.c @@ -375,7 +375,6 @@ read_header(void *data, unsigned int datasize, EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize; unsigned long FileAlignment = 0; - UINT16 DllFlags; size_t dos_sz = 0; size_t tmpsz0, tmpsz1; @@ -504,17 +503,17 @@ read_header(void *data, unsigned int datasize, context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint; context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - DllFlags = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; + context->DllCharacteristics = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; } else { context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase; context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint; context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - DllFlags = PEHdr->Pe32.OptionalHeader.DllCharacteristics; + context->DllCharacteristics = PEHdr->Pe32.OptionalHeader.DllCharacteristics; } if ((mok_policy & MOK_POLICY_REQUIRE_NX) && - !(DllFlags & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) { + !(context->DllCharacteristics & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) { perror(L"Policy requires NX, but image does not support NX\n"); return EFI_UNSUPPORTED; } @@ -555,4 +554,34 @@ read_header(void *data, unsigned int datasize, return EFI_SUCCESS; } +void +get_shim_nx_capability(EFI_HANDLE image_handle) +{ + EFI_LOADED_IMAGE_PROTOCOL*li = NULL; + EFI_STATUS efi_status; + PE_COFF_LOADER_IMAGE_CONTEXT context; + + efi_status = BS->HandleProtocol(image_handle, &gEfiLoadedImageProtocolGuid, (void **)&li); + if (EFI_ERROR(efi_status) || !li) { + dprint(L"Could not get loaded image protocol: %r\n", efi_status); + return; + } + + ZeroMem(&context, sizeof(context)); + efi_status = read_header(li->ImageBase, li->ImageSize, &context, false); + if (EFI_ERROR(efi_status)) { + dprint(L"Couldn't parse image header: %r\n", efi_status); + return; + } + + dprint(L"DllCharacteristics:0x%lx\n", context.DllCharacteristics); + if (context.DllCharacteristics & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT) { + dprint(L"Setting HSI from %a to %a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_NX)); + hsi_status |= SHIM_HSI_STATUS_NX; + } +} + + // vim:fenc=utf-8:tw=75:noet diff --git a/shim.c b/shim.c index 9efbde11..f85ce00e 100644 --- a/shim.c +++ b/shim.c @@ -1993,6 +1993,8 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) */ debug_hook(); + get_shim_nx_capability(image_handle); + efi_status = set_sbat_uefi_variable_internal(); if (EFI_ERROR(efi_status) && secure_mode()) { perror(L"%s variable initialization failed\n", SBAT_VAR_NAME); -- cgit v1.2.3