From 031e5cce385d3f96b1caa1d53495332a7eb03749 Mon Sep 17 00:00:00 2001 From: Steve McIntyre Date: Tue, 23 Mar 2021 23:49:46 +0000 Subject: New upstream version 15.3 --- shim.c | 1707 +++++++++++++++++++++------------------------------------------- 1 file changed, 542 insertions(+), 1165 deletions(-) (limited to 'shim.c') diff --git a/shim.c b/shim.c index f29f3921..117c8f42 100644 --- a/shim.c +++ b/shim.c @@ -1,32 +1,10 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent + /* * shim - trivial UEFI first-stage bootloader * - * Copyright 2012 Red Hat, Inc - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - * OF THE POSSIBILITY OF SUCH DAMAGE. + * Copyright Red Hat, Inc + * Author: Matthew Garrett * * Significant portions of this code are derived from Tianocore * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel @@ -34,8 +12,9 @@ */ #include "shim.h" - -#include +#if defined(ENABLE_SHIM_CERT) +#include "shim_cert.h" +#endif /* defined(ENABLE_SHIM_CERT) */ #include #include @@ -48,7 +27,7 @@ #include #include #include -#include +#include #include @@ -58,25 +37,35 @@ static EFI_SYSTEM_TABLE *systab; static EFI_HANDLE global_image_handle; +static EFI_LOADED_IMAGE *shim_li; +static EFI_LOADED_IMAGE shim_li_bak; static CHAR16 *second_stage; -static void *load_options; -static UINT32 load_options_size; +void *load_options; +UINT32 load_options_size; + +list_t sbat_var; /* * The vendor certificate used for validating the second stage loader */ extern struct { - UINT32 vendor_cert_size; - UINT32 vendor_dbx_size; - UINT32 vendor_cert_offset; - UINT32 vendor_dbx_offset; + UINT32 vendor_authorized_size; + UINT32 vendor_deauthorized_size; + UINT32 vendor_authorized_offset; + UINT32 vendor_deauthorized_offset; } cert_table; -UINT32 vendor_cert_size; -UINT32 vendor_dbx_size; -UINT8 *vendor_cert; -UINT8 *vendor_dbx; +UINT32 vendor_authorized_size = 0; +UINT8 *vendor_authorized = NULL; + +UINT32 vendor_deauthorized_size = 0; +UINT8 *vendor_deauthorized = NULL; + +#if defined(ENABLE_SHIM_CERT) +UINT32 build_cert_size; +UINT8 *build_cert; +#endif /* defined(ENABLE_SHIM_CERT) */ /* * indicator of how an image has been verified @@ -100,250 +89,6 @@ typedef struct { UINT8 *Mok; } MokListNode; -/* - * Perform basic bounds checking of the intra-image pointers - */ -static 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; -} - -/* 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(__arch64__) - 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; -} - -/* - * Perform the actual relocation - */ -static 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 + - Section->Misc.VirtualSize); - - 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; -} - static void drain_openssl_errors(void) { @@ -366,12 +111,18 @@ static BOOLEAN verify_x509(UINT8 *Cert, UINTN CertSize) * and 64KB. For convenience, assume the number of value bytes * is 2, i.e. the second byte is 0x82. */ - if (Cert[0] != 0x30 || Cert[1] != 0x82) + if (Cert[0] != 0x30 || Cert[1] != 0x82) { + dprint(L"cert[0:1] is [%02x%02x], should be [%02x%02x]\n", + Cert[0], Cert[1], 0x30, 0x82); return FALSE; + } length = Cert[2]<<8 | Cert[3]; - if (length != (CertSize - 4)) + if (length != (CertSize - 4)) { + dprint(L"Cert length is %ld, expecting %ld\n", + length, CertSize); return FALSE; + } return TRUE; } @@ -383,7 +134,9 @@ static BOOLEAN verify_eku(UINT8 *Cert, UINTN CertSize) EXTENDED_KEY_USAGE *eku; ASN1_OBJECT *module_signing; - module_signing = OBJ_nid2obj(OBJ_create(OID_EKU_MODSIGN, NULL, NULL)); + module_signing = OBJ_nid2obj(OBJ_create(OID_EKU_MODSIGN, + "modsign-eku", + "modsign-eku")); x509 = d2i_X509 (NULL, &Temp, (long) CertSize); if (x509 != NULL) { @@ -403,25 +156,9 @@ static BOOLEAN verify_eku(UINT8 *Cert, UINTN CertSize) X509_free(x509); } - return TRUE; -} + OBJ_cleanup(); -static void show_ca_warning() -{ - CHAR16 *text[9]; - - text[0] = L"WARNING!"; - text[1] = L""; - text[2] = L"The CA certificate used to verify this image doesn't "; - text[3] = L"contain the CA flag in Basic Constraints or KeyCertSign"; - text[4] = L"in KeyUsage. Such CA certificates will not be supported"; - text[5] = L"in the future. "; - text[6] = L""; - text[7] = L"Please contact the issuer to update the certificate. "; - text[8] = NULL; - - console_reset(); - console_print_box(text, -1); + return TRUE; } static CHECK_STATUS check_db_cert_in_ram(EFI_SIGNATURE_LIST *CertList, @@ -433,24 +170,24 @@ static CHECK_STATUS check_db_cert_in_ram(EFI_SIGNATURE_LIST *CertList, EFI_SIGNATURE_DATA *Cert; UINTN CertSize; BOOLEAN IsFound = FALSE; + int i = 0; while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { if (CompareGuid (&CertList->SignatureType, &EFI_CERT_TYPE_X509_GUID) == 0) { Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); CertSize = CertList->SignatureSize - sizeof(EFI_GUID); + dprint(L"trying to verify cert %d (%s)\n", i++, dbname); if (verify_x509(Cert->SignatureData, CertSize)) { if (verify_eku(Cert->SignatureData, CertSize)) { - clear_ca_warning(); + drain_openssl_errors(); IsFound = AuthenticodeVerify (data->CertData, data->Hdr.dwLength - sizeof(data->Hdr), Cert->SignatureData, CertSize, hash, SHA256_DIGEST_SIZE); if (IsFound) { - if (get_ca_warning()) { - show_ca_warning(); - } - tpm_measure_variable(dbname, guid, CertSize, Cert->SignatureData); + dprint(L"AuthenticodeVerify() succeeded: %d\n", IsFound); + tpm_measure_variable(dbname, guid, CertList->SignatureSize, Cert); drain_openssl_errors(); return DATA_FOUND; } else { @@ -458,7 +195,9 @@ static CHECK_STATUS check_db_cert_in_ram(EFI_SIGNATURE_LIST *CertList, } } } else if (verbose) { - console_notify(L"Not a DER encoding x.509 Certificate"); + console_print(L"Not a DER encoded x.509 Certificate"); + dprint(L"cert:\n"); + dhexdumpat(Cert->SignatureData, CertSize, 0); } } @@ -513,7 +252,7 @@ static CHECK_STATUS check_db_hash_in_ram(EFI_SIGNATURE_LIST *CertList, // Find the signature in database. // IsFound = TRUE; - tpm_measure_variable(dbname, guid, SignatureSize, data); + tpm_measure_variable(dbname, guid, CertList->SignatureSize, Cert); break; } @@ -562,27 +301,27 @@ static CHECK_STATUS check_db_hash(CHAR16 *dbname, EFI_GUID guid, UINT8 *data, /* * Check whether the binary signature or hash are present in dbx or the - * built-in blacklist + * built-in denylist */ -static EFI_STATUS check_blacklist (WIN_CERTIFICATE_EFI_PKCS *cert, - UINT8 *sha256hash, UINT8 *sha1hash) +static EFI_STATUS check_denylist (WIN_CERTIFICATE_EFI_PKCS *cert, + UINT8 *sha256hash, UINT8 *sha1hash) { - EFI_SIGNATURE_LIST *dbx = (EFI_SIGNATURE_LIST *)vendor_dbx; + EFI_SIGNATURE_LIST *dbx = (EFI_SIGNATURE_LIST *)vendor_deauthorized; - if (check_db_hash_in_ram(dbx, vendor_dbx_size, sha256hash, + if (check_db_hash_in_ram(dbx, vendor_deauthorized_size, sha256hash, SHA256_DIGEST_SIZE, EFI_CERT_SHA256_GUID, L"dbx", EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) { LogError(L"binary sha256hash found in vendor dbx\n"); return EFI_SECURITY_VIOLATION; } - if (check_db_hash_in_ram(dbx, vendor_dbx_size, sha1hash, + if (check_db_hash_in_ram(dbx, vendor_deauthorized_size, sha1hash, SHA1_DIGEST_SIZE, EFI_CERT_SHA1_GUID, L"dbx", EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) { LogError(L"binary sha1hash found in vendor dbx\n"); return EFI_SECURITY_VIOLATION; } if (cert && - check_db_cert_in_ram(dbx, vendor_dbx_size, cert, sha256hash, L"dbx", + check_db_cert_in_ram(dbx, vendor_deauthorized_size, cert, sha256hash, L"dbx", EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) { LogError(L"cert sha256hash found in vendor dbx\n"); return EFI_SECURITY_VIOLATION; @@ -628,7 +367,7 @@ static void update_verification_method(verification_method_t method) /* * Check whether the binary signature or hash are present in db or MokList */ -static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert, +static EFI_STATUS check_allowlist (WIN_CERTIFICATE_EFI_PKCS *cert, UINT8 *sha256hash, UINT8 *sha1hash) { if (!ignore_db) { @@ -652,11 +391,36 @@ static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert, verification_method = VERIFIED_BY_CERT; update_verification_method(VERIFIED_BY_CERT); return EFI_SUCCESS; - } else { + } else if (cert) { LogError(L"check_db_cert(db, sha256hash) != DATA_FOUND\n"); } } +#if defined(VENDOR_DB_FILE) + EFI_SIGNATURE_LIST *db = (EFI_SIGNATURE_LIST *)vendor_db; + + if (check_db_hash_in_ram(db, vendor_db_size, + sha256hash, SHA256_DIGEST_SIZE, + EFI_CERT_SHA256_GUID, L"vendor_db", + EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) { + verification_method = VERIFIED_BY_HASH; + update_verification_method(VERIFIED_BY_HASH); + return EFI_SUCCESS; + } else { + LogError(L"check_db_hash(vendor_db, sha256hash) != DATA_FOUND\n"); + } + if (cert && + check_db_cert_in_ram(db, vendor_db_size, + cert, sha256hash, L"vendor_db", + EFI_SECURE_BOOT_DB_GUID) == DATA_FOUND) { + verification_method = VERIFIED_BY_CERT; + update_verification_method(VERIFIED_BY_CERT); + return EFI_SUCCESS; + } else if (cert) { + LogError(L"check_db_cert(vendor_db, sha256hash) != DATA_FOUND\n"); + } +#endif + if (check_db_hash(L"MokList", SHIM_LOCK_GUID, sha256hash, SHA256_DIGEST_SIZE, EFI_CERT_SHA256_GUID) == DATA_FOUND) { @@ -671,19 +435,18 @@ static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert, verification_method = VERIFIED_BY_CERT; update_verification_method(VERIFIED_BY_CERT); return EFI_SUCCESS; - } else { + } else if (cert) { LogError(L"check_db_cert(MokList, sha256hash) != DATA_FOUND\n"); } update_verification_method(VERIFIED_BY_NOTHING); - return EFI_SECURITY_VIOLATION; + return EFI_NOT_FOUND; } /* * Check whether we're in Secure Boot and user mode */ - -static BOOLEAN secure_mode (void) +BOOLEAN secure_mode (void) { static int first = 1; if (user_insecure_mode) @@ -704,796 +467,250 @@ static BOOLEAN secure_mode (void) */ if (variable_is_setupmode(0) == 1) { if (verbose && !in_protocol && first) - console_notify(L"Platform is in setup mode"); - first = 0; - return FALSE; - } - - first = 0; - return TRUE; -} - -#define check_size_line(data, datasize_in, hashbase, hashsize, l) ({ \ - if ((unsigned long)hashbase > \ - (unsigned long)data + datasize_in) { \ - efi_status = EFI_INVALID_PARAMETER; \ - perror(L"shim.c:%d Invalid hash base 0x%016x\n", l, \ - hashbase); \ - goto done; \ - } \ - if ((unsigned long)hashbase + hashsize > \ - (unsigned long)data + datasize_in) { \ - efi_status = EFI_INVALID_PARAMETER; \ - perror(L"shim.c:%d Invalid hash size 0x%016x\n", l, \ - hashsize); \ - goto done; \ - } \ -}) -#define check_size(d,ds,h,hs) check_size_line(d,ds,h,hs,__LINE__) - -/* - * Calculate the SHA1 and SHA256 hashes of a binary - */ - -static EFI_STATUS generate_hash (char *data, unsigned int datasize_in, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - UINT8 *sha256hash, UINT8 *sha1hash) - -{ - unsigned int sha256ctxsize, sha1ctxsize; - unsigned int size = datasize_in; - void *sha256ctx = NULL, *sha1ctx = NULL; - char *hashbase; - unsigned int hashsize; - unsigned int SumOfBytesHashed, SumOfSectionBytes; - unsigned int index, pos; - unsigned int datasize; - EFI_IMAGE_SECTION_HEADER *Section; - EFI_IMAGE_SECTION_HEADER *SectionHeader = NULL; - EFI_STATUS efi_status = EFI_SUCCESS; - EFI_IMAGE_DOS_HEADER *DosHdr = (void *)data; - unsigned int PEHdr_offset = 0; - - size = datasize = datasize_in; - - if (datasize <= sizeof (*DosHdr) || - DosHdr->e_magic != EFI_IMAGE_DOS_SIGNATURE) { - perror(L"Invalid signature\n"); - return EFI_INVALID_PARAMETER; - } - PEHdr_offset = DosHdr->e_lfanew; - - sha256ctxsize = Sha256GetContextSize(); - sha256ctx = AllocatePool(sha256ctxsize); - - sha1ctxsize = Sha1GetContextSize(); - sha1ctx = AllocatePool(sha1ctxsize); - - if (!sha256ctx || !sha1ctx) { - perror(L"Unable to allocate memory for hash context\n"); - return EFI_OUT_OF_RESOURCES; - } - - if (!Sha256Init(sha256ctx) || !Sha1Init(sha1ctx)) { - perror(L"Unable to initialise hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - - /* Hash start to checksum */ - hashbase = data; - hashsize = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum - - hashbase; - check_size(data, datasize_in, hashbase, hashsize); - - if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || - !(Sha1Update(sha1ctx, hashbase, hashsize))) { - perror(L"Unable to generate hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - - /* Hash post-checksum to start of certificate table */ - hashbase = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum + - sizeof (int); - hashsize = (char *)context->SecDir - hashbase; - check_size(data, datasize_in, hashbase, hashsize); - - if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || - !(Sha1Update(sha1ctx, hashbase, hashsize))) { - perror(L"Unable to generate hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - - /* Hash end of certificate table to end of image header */ - EFI_IMAGE_DATA_DIRECTORY *dd = context->SecDir + 1; - hashbase = (char *)dd; - hashsize = context->SizeOfHeaders - (unsigned long)((char *)dd - data); - if (hashsize > datasize_in) { - perror(L"Data Directory size %d is invalid\n", hashsize); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - check_size(data, datasize_in, hashbase, hashsize); - - if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || - !(Sha1Update(sha1ctx, hashbase, hashsize))) { - perror(L"Unable to generate hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - - /* Sort sections */ - SumOfBytesHashed = context->SizeOfHeaders; - - /* Validate section locations and sizes */ - for (index = 0, SumOfSectionBytes = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) { - EFI_IMAGE_SECTION_HEADER *SectionPtr; - - /* Validate SectionPtr is within image */ - SectionPtr = ImageAddress(data, datasize, - PEHdr_offset + - sizeof (UINT32) + - sizeof (EFI_IMAGE_FILE_HEADER) + - context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + - (index * sizeof(*SectionPtr))); - if (!SectionPtr) { - perror(L"Malformed section %d\n", index); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - /* Validate section size is within image. */ - if (SectionPtr->SizeOfRawData > - datasize - SumOfBytesHashed - SumOfSectionBytes) { - perror(L"Malformed section %d size\n", index); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - SumOfSectionBytes += SectionPtr->SizeOfRawData; - } - - SectionHeader = (EFI_IMAGE_SECTION_HEADER *) AllocateZeroPool (sizeof (EFI_IMAGE_SECTION_HEADER) * context->PEHdr->Pe32.FileHeader.NumberOfSections); - if (SectionHeader == NULL) { - perror(L"Unable to allocate section header\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - - /* Already validated above */ - Section = ImageAddress(data, datasize, - PEHdr_offset + - sizeof (UINT32) + - sizeof (EFI_IMAGE_FILE_HEADER) + - context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader); - /* But check it again just for better error messaging, and so - * clang-analyzer doesn't get confused. */ - if (Section == NULL) { - uint64_t addr; - - addr = PEHdr_offset + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER) - + context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader; - perror(L"Malformed file header.\n"); - perror(L"Image address for Section 0 is 0x%016llx\n", addr); - perror(L"File size is 0x%016llx\n", datasize); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - - /* Sort the section headers */ - for (index = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) { - pos = index; - while ((pos > 0) && (Section->PointerToRawData < SectionHeader[pos - 1].PointerToRawData)) { - CopyMem (&SectionHeader[pos], &SectionHeader[pos - 1], sizeof (EFI_IMAGE_SECTION_HEADER)); - pos--; - } - CopyMem (&SectionHeader[pos], Section, sizeof (EFI_IMAGE_SECTION_HEADER)); - Section += 1; - } - - /* Hash the sections */ - for (index = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) { - Section = &SectionHeader[index]; - if (Section->SizeOfRawData == 0) { - continue; - } - hashbase = ImageAddress(data, size, Section->PointerToRawData); - - if (!hashbase) { - perror(L"Malformed section header\n"); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - - /* Verify hashsize within image. */ - if (Section->SizeOfRawData > - datasize - Section->PointerToRawData) { - perror(L"Malformed section raw size %d\n", index); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - hashsize = (unsigned int) Section->SizeOfRawData; - check_size(data, datasize_in, hashbase, hashsize); - - if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || - !(Sha1Update(sha1ctx, hashbase, hashsize))) { - perror(L"Unable to generate hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - SumOfBytesHashed += Section->SizeOfRawData; - } - - /* Hash all remaining data up to SecDir if SecDir->Size is not 0 */ - if (datasize > SumOfBytesHashed && context->SecDir->Size) { - hashbase = data + SumOfBytesHashed; - hashsize = datasize - context->SecDir->Size - SumOfBytesHashed; - - if ((datasize - SumOfBytesHashed < context->SecDir->Size) || - (SumOfBytesHashed + hashsize != context->SecDir->VirtualAddress)) { - perror(L"Malformed binary after Attribute Certificate Table\n"); - console_print(L"datasize: %u SumOfBytesHashed: %u SecDir->Size: %lu\n", - datasize, SumOfBytesHashed, context->SecDir->Size); - console_print(L"hashsize: %u SecDir->VirtualAddress: 0x%08lx\n", - hashsize, context->SecDir->VirtualAddress); - efi_status = EFI_INVALID_PARAMETER; - goto done; - } - check_size(data, datasize_in, hashbase, hashsize); - - if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || - !(Sha1Update(sha1ctx, hashbase, hashsize))) { - perror(L"Unable to generate hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - -#if 1 - } -#else // we have to migrate to doing this later :/ - SumOfBytesHashed += hashsize; - } - - /* Hash all remaining data */ - if (datasize > SumOfBytesHashed) { - hashbase = data + SumOfBytesHashed; - hashsize = datasize - SumOfBytesHashed; - - check_size(data, datasize_in, hashbase, hashsize); - - if (!(Sha256Update(sha256ctx, hashbase, hashsize)) || - !(Sha1Update(sha1ctx, hashbase, hashsize))) { - perror(L"Unable to generate hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - - SumOfBytesHashed += hashsize; - } -#endif - - if (!(Sha256Final(sha256ctx, sha256hash)) || - !(Sha1Final(sha1ctx, sha1hash))) { - perror(L"Unable to finalise hash\n"); - efi_status = EFI_OUT_OF_RESOURCES; - goto done; - } - -done: - if (SectionHeader) - FreePool(SectionHeader); - if (sha1ctx) - FreePool(sha1ctx); - if (sha256ctx) - FreePool(sha256ctx); - - return efi_status; -} - -/* - * Check that the signature is valid and matches the binary - */ -static EFI_STATUS verify_buffer (char *data, int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - UINT8 *sha256hash, UINT8 *sha1hash) -{ - EFI_STATUS efi_status = EFI_SECURITY_VIOLATION; - WIN_CERTIFICATE_EFI_PKCS *cert = NULL; - unsigned int size = datasize; - - if (datasize < 0) - return EFI_INVALID_PARAMETER; - - if (context->SecDir->Size != 0) { - if (context->SecDir->Size >= size) { - perror(L"Certificate Database size is too large\n"); - return EFI_INVALID_PARAMETER; - } - - cert = ImageAddress (data, size, - context->SecDir->VirtualAddress); - - if (!cert) { - perror(L"Certificate located outside the image\n"); - return EFI_INVALID_PARAMETER; - } - - if (cert->Hdr.dwLength > context->SecDir->Size) { - perror(L"Certificate list size is inconsistent with PE headers"); - return EFI_INVALID_PARAMETER; - } - - if (cert->Hdr.wCertificateType != - WIN_CERT_TYPE_PKCS_SIGNED_DATA) { - perror(L"Unsupported certificate type %x\n", - cert->Hdr.wCertificateType); - return EFI_UNSUPPORTED; - } - } - - /* - * Clear OpenSSL's error log, because we get some DSO unimplemented - * errors during its intialization, and we don't want those to look - * like they're the reason for validation failures. - */ - drain_openssl_errors(); - - efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash); - if (EFI_ERROR(efi_status)) { - LogError(L"generate_hash: %r\n", efi_status); - return efi_status; - } - - /* - * Ensure that the binary isn't blacklisted - */ - efi_status = check_blacklist(cert, sha256hash, sha1hash); - if (EFI_ERROR(efi_status)) { - perror(L"Binary is blacklisted\n"); - LogError(L"Binary is blacklisted: %r\n", efi_status); - return efi_status; - } - - /* - * Check whether the binary is whitelisted in any of the firmware - * databases - */ - efi_status = check_whitelist(cert, sha256hash, sha1hash); - if (EFI_ERROR(efi_status)) { - LogError(L"check_whitelist(): %r\n", efi_status); - } else { - drain_openssl_errors(); - return efi_status; - } - - if (cert) { -#if defined(ENABLE_SHIM_CERT) - /* - * Check against the shim build key - */ - clear_ca_warning(); - if (sizeof(shim_cert) && - AuthenticodeVerify(cert->CertData, - cert->Hdr.dwLength - sizeof(cert->Hdr), - shim_cert, sizeof(shim_cert), sha256hash, - SHA256_DIGEST_SIZE)) { - if (get_ca_warning()) { - show_ca_warning(); - } - update_verification_method(VERIFIED_BY_CERT); - tpm_measure_variable(L"Shim", SHIM_LOCK_GUID, - sizeof(shim_cert), shim_cert); - efi_status = EFI_SUCCESS; - drain_openssl_errors(); - return efi_status; - } else { - LogError(L"AuthenticodeVerify(shim_cert) failed\n"); - } -#endif /* defined(ENABLE_SHIM_CERT) */ - - /* - * And finally, check against shim's built-in key - */ - clear_ca_warning(); - if (vendor_cert_size && - AuthenticodeVerify(cert->CertData, - cert->Hdr.dwLength - sizeof(cert->Hdr), - vendor_cert, vendor_cert_size, - sha256hash, SHA256_DIGEST_SIZE)) { - if (get_ca_warning()) { - show_ca_warning(); - } - update_verification_method(VERIFIED_BY_CERT); - tpm_measure_variable(L"Shim", SHIM_LOCK_GUID, - vendor_cert_size, vendor_cert); - efi_status = EFI_SUCCESS; - drain_openssl_errors(); - return efi_status; - } else { - LogError(L"AuthenticodeVerify(vendor_cert) failed\n"); - } - } - - LogError(L"Binary is not whitelisted\n"); - crypterr(EFI_SECURITY_VIOLATION); - PrintErrors(); - efi_status = EFI_SECURITY_VIOLATION; - return efi_status; -} - -/* - * Read the binary header and grab appropriate information from it - */ -static 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; - - 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 small\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]; - } 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]; - } - - 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; + console_notify(L"Platform is in setup mode"); + first = 0; + return FALSE; } - 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; + first = 0; + return TRUE; } -/* - * Once the image has been loaded it needs to be validated and relocated - */ -static EFI_STATUS handle_image (void *data, unsigned int datasize, - EFI_LOADED_IMAGE *li, - EFI_IMAGE_ENTRY_POINT *entry_point, - EFI_PHYSICAL_ADDRESS *alloc_address, - UINTN *alloc_pages) +static EFI_STATUS +verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig, + UINT8 *sha256hash, UINT8 *sha1hash) { EFI_STATUS efi_status; - char *buffer; - int i; - EFI_IMAGE_SECTION_HEADER *Section; - char *base, *end; - PE_COFF_LOADER_IMAGE_CONTEXT context; - unsigned int alignment, alloc_size; - int found_entry_point = 0; - UINT8 sha1hash[SHA1_DIGEST_SIZE]; - UINT8 sha256hash[SHA256_DIGEST_SIZE]; /* - * The binary header contains relevant context and section pointers + * Ensure that the binary isn't forbidden */ - efi_status = read_header(data, datasize, &context); + drain_openssl_errors(); + efi_status = check_denylist(sig, sha256hash, sha1hash); if (EFI_ERROR(efi_status)) { - perror(L"Failed to read header: %r\n", efi_status); + perror(L"Binary is forbidden: %r\n", efi_status); + PrintErrors(); + ClearErrors(); + crypterr(efi_status); return efi_status; } /* - * We only need to verify the binary if we're in secure mode + * Check whether the binary is authorized in any of the firmware + * databases */ - 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, sha1hash, 4); -#ifdef REQUIRE_TPM - if (efi_status != EFI_SUCCESS) { + drain_openssl_errors(); + efi_status = check_allowlist(sig, sha256hash, sha1hash); + if (EFI_ERROR(efi_status)) { + if (efi_status != EFI_NOT_FOUND) { + dprint(L"check_allowlist(): %r\n", efi_status); + PrintErrors(); + ClearErrors(); + crypterr(efi_status); + } + } else { + drain_openssl_errors(); return efi_status; } -#endif - - if (secure_mode ()) { - efi_status = verify_buffer(data, datasize, &context, - sha256hash, sha1hash); - if (EFI_ERROR(efi_status)) { - console_error(L"Verification failed", efi_status); - return efi_status; - } else { - if (verbose) - console_print(L"Verification succeeded\n"); - } + efi_status = EFI_NOT_FOUND; +#if defined(ENABLE_SHIM_CERT) + /* + * Check against the shim build key + */ + drain_openssl_errors(); + if (build_cert && build_cert_size) { + dprint("verifying against shim cert\n"); + } + if (build_cert && build_cert_size && + AuthenticodeVerify(sig->CertData, + sig->Hdr.dwLength - sizeof(sig->Hdr), + build_cert, build_cert_size, sha256hash, + SHA256_DIGEST_SIZE)) { + dprint(L"AuthenticodeVerify(shim_cert) succeeded\n"); + update_verification_method(VERIFIED_BY_CERT); + tpm_measure_variable(L"Shim", SHIM_LOCK_GUID, + build_cert_size, build_cert); + efi_status = EFI_SUCCESS; + drain_openssl_errors(); + return efi_status; + } else { + dprint(L"AuthenticodeVerify(shim_cert) failed\n"); + PrintErrors(); + ClearErrors(); + crypterr(EFI_NOT_FOUND); } +#endif /* defined(ENABLE_SHIM_CERT) */ - /* The spec says, uselessly, of SectionAlignment: - * ===== - * The alignment (in bytes) of sections when they are loaded into - * memory. It must be greater than or equal to FileAlignment. The - * default is the page size for the architecture. - * ===== - * Which doesn't tell you whose responsibility it is to enforce the - * "default", or when. It implies that the value in the field must - * be > FileAlignment (also poorly defined), but it appears visual - * studio will happily write 512 for FileAlignment (its default) and - * 0 for SectionAlignment, intending to imply PAGE_SIZE. - * - * We only support one page size, so if it's zero, nerf it to 4096. +#if defined(VENDOR_CERT_FILE) + /* + * And finally, check against shim's built-in key */ - alignment = context.SectionAlignment; - if (!alignment) - alignment = 4096; - - alloc_size = ALIGN_VALUE(context.ImageSize + context.SectionAlignment, - PAGE_SIZE); - *alloc_pages = alloc_size / PAGE_SIZE; - - efi_status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderCode, - *alloc_pages, alloc_address); - if (EFI_ERROR(efi_status)) { - perror(L"Failed to allocate image buffer\n"); - return EFI_OUT_OF_RESOURCES; + drain_openssl_errors(); + if (vendor_cert_size) { + dprint("verifying against vendor_cert\n"); + } + if (vendor_cert_size && + AuthenticodeVerify(sig->CertData, + sig->Hdr.dwLength - sizeof(sig->Hdr), + vendor_cert, vendor_cert_size, + sha256hash, SHA256_DIGEST_SIZE)) { + dprint(L"AuthenticodeVerify(vendor_cert) succeeded\n"); + update_verification_method(VERIFIED_BY_CERT); + tpm_measure_variable(L"Shim", SHIM_LOCK_GUID, + vendor_cert_size, vendor_cert); + efi_status = EFI_SUCCESS; + drain_openssl_errors(); + return efi_status; + } else { + dprint(L"AuthenticodeVerify(vendor_cert) failed\n"); + PrintErrors(); + ClearErrors(); + crypterr(EFI_NOT_FOUND); } +#endif /* defined(VENDOR_CERT_FILE) */ - buffer = (void *)ALIGN_VALUE((unsigned long)*alloc_address, alignment); - - CopyMem(buffer, data, context.SizeOfHeaders); + return efi_status; +} - *entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint); - if (!*entry_point) { - perror(L"Entry point is invalid\n"); - gBS->FreePages(*alloc_address, *alloc_pages); - return EFI_UNSUPPORTED; - } +/* + * 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) +{ + EFI_STATUS ret_efi_status; + size_t size = datasize; + size_t offset = 0; + unsigned int i = 0; + if (datasize < 0) + return EFI_INVALID_PARAMETER; - char *RelocBase, *RelocBaseEnd; - /* - * These are relative virtual addresses, so we have to check them - * against the image size, not the data size. - */ - RelocBase = ImageAddress(buffer, context.ImageSize, - context.RelocDir->VirtualAddress); /* - * RelocBaseEnd here is the address of the last byte of the table + * Clear OpenSSL's error log, because we get some DSO unimplemented + * errors during its intialization, and we don't want those to look + * like they're the reason for validation failures. */ - RelocBaseEnd = ImageAddress(buffer, context.ImageSize, - context.RelocDir->VirtualAddress + - context.RelocDir->Size - 1); + drain_openssl_errors(); - EFI_IMAGE_SECTION_HEADER *RelocSection = NULL; + ret_efi_status = generate_hash(data, datasize, context, sha256hash, sha1hash); + if (EFI_ERROR(ret_efi_status)) { + dprint(L"generate_hash: %r\n", ret_efi_status); + PrintErrors(); + ClearErrors(); + crypterr(ret_efi_status); + return ret_efi_status; + } /* - * Copy the executable's sections to their desired offsets + * Ensure that the binary isn't forbidden by hash */ - Section = context.FirstSection; - for (i = 0; i < context.NumberOfSections; i++, Section++) { - base = ImageAddress (buffer, context.ImageSize, - Section->VirtualAddress); - end = ImageAddress (buffer, context.ImageSize, - Section->VirtualAddress - + Section->Misc.VirtualSize - 1); - - if (end < base) { - perror(L"Section %d has negative size\n", i); - gBS->FreePages(*alloc_address, *alloc_pages); - return EFI_UNSUPPORTED; - } + drain_openssl_errors(); + ret_efi_status = check_denylist(NULL, sha256hash, sha1hash); + if (EFI_ERROR(ret_efi_status)) { +// perror(L"Binary is forbidden\n"); +// dprint(L"Binary is forbidden: %r\n", ret_efi_status); + PrintErrors(); + ClearErrors(); + crypterr(ret_efi_status); + return ret_efi_status; + } - if (Section->VirtualAddress <= context.EntryPoint && - (Section->VirtualAddress + Section->SizeOfRawData - 1) - > context.EntryPoint) - found_entry_point++; - - /* We do want to process .reloc, but it's often marked - * discardable, so we don't want to memcpy it. */ - if (CompareMem(Section->Name, ".reloc\0\0", 8) == 0) { - if (RelocSection) { - perror(L"Image has multiple relocation sections\n"); - return EFI_UNSUPPORTED; - } - /* If it has nonzero sizes, and our bounds check - * made sense, and the VA and size match RelocDir's - * versions, then we believe in this section table. */ - if (Section->SizeOfRawData && - Section->Misc.VirtualSize && - base && end && - RelocBase == base && - RelocBaseEnd == end) { - RelocSection = Section; - } + /* + * Check whether the binary is authorized by hash in any of the + * firmware databases + */ + drain_openssl_errors(); + ret_efi_status = check_allowlist(NULL, sha256hash, sha1hash); + if (EFI_ERROR(ret_efi_status)) { + LogError(L"check_allowlist(): %r\n", ret_efi_status); + dprint(L"check_allowlist: %r\n", ret_efi_status); + if (ret_efi_status != EFI_NOT_FOUND) { + dprint(L"check_allowlist(): %r\n", ret_efi_status); + PrintErrors(); + ClearErrors(); + crypterr(ret_efi_status); + return ret_efi_status; } + } else { + drain_openssl_errors(); + return ret_efi_status; + } - if (Section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE) { - continue; - } + if (context->SecDir->Size == 0) { + dprint(L"No signatures found\n"); + return EFI_SECURITY_VIOLATION; + } - if (!base) { - perror(L"Section %d has invalid base address\n", i); - return EFI_UNSUPPORTED; - } - if (!end) { - perror(L"Section %d has zero size\n", i); - return EFI_UNSUPPORTED; - } + if (context->SecDir->Size >= size) { + perror(L"Certificate Database size is too large\n"); + return EFI_INVALID_PARAMETER; + } - 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", i); - return EFI_UNSUPPORTED; - } + ret_efi_status = EFI_NOT_FOUND; + do { + WIN_CERTIFICATE_EFI_PKCS *sig = NULL; + size_t sz; - if (Section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) { - ZeroMem(base, Section->Misc.VirtualSize); - } else { - if (Section->PointerToRawData < context.SizeOfHeaders) { - perror(L"Section %d is inside image headers\n", i); - return EFI_UNSUPPORTED; - } + sig = ImageAddress(data, size, + context->SecDir->VirtualAddress + offset); + if (!sig) + break; - if (Section->SizeOfRawData > 0) - CopyMem(base, data + Section->PointerToRawData, - Section->SizeOfRawData); + sz = offset + offsetof(WIN_CERTIFICATE_EFI_PKCS, Hdr.dwLength) + + sizeof(sig->Hdr.dwLength); + if (sz > context->SecDir->Size) { + perror(L"Certificate size is too large for secruity database"); + return EFI_INVALID_PARAMETER; + } - if (Section->SizeOfRawData < Section->Misc.VirtualSize) - ZeroMem(base + Section->SizeOfRawData, - Section->Misc.VirtualSize - Section->SizeOfRawData); + sz = sig->Hdr.dwLength; + if (sz > context->SecDir->Size - offset) { + perror(L"Certificate size is too large for secruity database"); + return EFI_INVALID_PARAMETER; } - } - if (context.NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) { - perror(L"Image has no relocation entry\n"); - FreePool(buffer); - return EFI_UNSUPPORTED; - } + if (sz < sizeof(sig->Hdr)) { + perror(L"Certificate size is too small for certificate data"); + return EFI_INVALID_PARAMETER; + } - if (context.RelocDir->Size && RelocSection) { - /* - * Run the relocation fixups - */ - efi_status = relocate_coff(&context, RelocSection, data, - buffer); + if (sig->Hdr.wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + EFI_STATUS efi_status; - if (EFI_ERROR(efi_status)) { - perror(L"Relocation failed: %r\n", efi_status); - FreePool(buffer); - return efi_status; - } - } + dprint(L"Attempting to verify signature %d:\n", i++); - /* - * grub needs to know its location and size in memory, so fix up - * the loaded image protocol values - */ - li->ImageBase = buffer; - li->ImageSize = context.ImageSize; + efi_status = verify_one_signature(sig, sha256hash, sha1hash); - /* Pass the load options to the second stage loader */ - if ( load_options ) { - li->LoadOptions = load_options; - li->LoadOptionsSize = load_options_size; - } + /* + * If we didn't get EFI_SECURITY_VIOLATION from + * checking the hashes above, then any dbx entries are + * for a certificate, not this individual binary. + * + * So don't clobber successes with security violation + * here; that just means it isn't a success. + */ + if (ret_efi_status != EFI_SUCCESS) + ret_efi_status = efi_status; + } else { + perror(L"Unsupported certificate type %x\n", + sig->Hdr.wCertificateType); + } + offset = ALIGN_VALUE(offset + sz, 8); + } while (offset < context->SecDir->Size); - if (!found_entry_point) { - perror(L"Entry point is not within sections\n"); - return EFI_UNSUPPORTED; - } - if (found_entry_point > 1) { - perror(L"%d sections contain entry point\n"); - return EFI_UNSUPPORTED; + if (ret_efi_status != EFI_SUCCESS) { + dprint(L"Binary is not authorized\n"); + PrintErrors(); + ClearErrors(); + crypterr(EFI_SECURITY_VIOLATION); + ret_efi_status = EFI_SECURITY_VIOLATION; } - - return EFI_SUCCESS; + drain_openssl_errors(); + return ret_efi_status; } static int @@ -1589,7 +806,7 @@ static EFI_STATUS generate_path_from_image_path(EFI_LOADED_IMAGE *li, /* * Suuuuper lazy technique here, but check and see if this is a full * path to something on the ESP. Backwards compatibility demands - * that we don't just use \\, becuase we (not particularly brightly) + * that we don't just use \\, because we (not particularly brightly) * used to require that the relative file path started with that. * * If it is a full path, don't try to merge it with the directory @@ -1640,9 +857,11 @@ static EFI_STATUS generate_path_from_image_path(EFI_LOADED_IMAGE *li, bootpath[j] = '\0'; } - while (*ImagePath == '\\') - ImagePath++; + for (i = 0, last = 0; i < StrLen(ImagePath); i++) + if (ImagePath[i] == '\\') + last = i + 1; + ImagePath = ImagePath + last; *PathName = AllocatePool(StrSize(bootpath) + StrSize(ImagePath)); if (!*PathName) { @@ -1677,6 +896,7 @@ static EFI_STATUS load_image (EFI_LOADED_IMAGE *li, void **data, device = li->DeviceHandle; + dprint(L"attempting to load %s\n", PathName); /* * Open the device */ @@ -1802,7 +1022,8 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) #ifdef REQUIRE_TPM efi_status = #endif - tpm_log_pe((EFI_PHYSICAL_ADDRESS)(UINTN)buffer, size, sha1hash, 4); + tpm_log_pe((EFI_PHYSICAL_ADDRESS)(UINTN)buffer, size, 0, NULL, + sha1hash, 4); #ifdef REQUIRE_TPM if (EFI_ERROR(efi_status)) goto done; @@ -1813,8 +1034,8 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) goto done; } - efi_status = verify_buffer(buffer, size, &context, - sha256hash, sha1hash); + efi_status = verify_buffer(buffer, size, + &context, sha256hash, sha1hash); done: in_protocol = 0; return efi_status; @@ -1849,13 +1070,24 @@ static EFI_STATUS shim_read_header(void *data, unsigned int datasize, return efi_status; } +VOID +restore_loaded_image(VOID) +{ + if (shim_li->FilePath) + FreePool(shim_li->FilePath); + + /* + * Restore our original loaded image values + */ + CopyMem(shim_li, &shim_li_bak, sizeof(shim_li_bak)); +} + /* * Load and run an EFI executable */ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) { EFI_STATUS efi_status; - EFI_LOADED_IMAGE *li, li_bak; EFI_IMAGE_ENTRY_POINT entry_point; EFI_PHYSICAL_ADDRESS alloc_address; UINTN alloc_pages; @@ -1863,14 +1095,14 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) void *sourcebuffer = NULL; UINT64 sourcesize = 0; void *data = NULL; - int datasize; + int datasize = 0; /* * We need to refer to the loaded image protocol on the running * binary in order to find our path */ efi_status = gBS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID, - (void **)&li); + (void **)&shim_li); if (EFI_ERROR(efi_status)) { perror(L"Unable to init protocol\n"); return efi_status; @@ -1879,14 +1111,14 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) /* * Build a new path from the existing one plus the executable name */ - efi_status = generate_path_from_image_path(li, ImagePath, &PathName); + efi_status = generate_path_from_image_path(shim_li, ImagePath, &PathName); if (EFI_ERROR(efi_status)) { perror(L"Unable to generate path %s: %r\n", ImagePath, efi_status); goto done; } - if (findNetboot(li->DeviceHandle)) { + if (findNetboot(shim_li->DeviceHandle)) { efi_status = parseNetbootinfo(image_handle); if (EFI_ERROR(efi_status)) { perror(L"Netboot parsing failed: %r\n", efi_status); @@ -1901,8 +1133,7 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) } data = sourcebuffer; datasize = sourcesize; -#if defined(ENABLE_HTTPBOOT) - } else if (find_httpboot(li->DeviceHandle)) { + } else if (find_httpboot(shim_li->DeviceHandle)) { efi_status = httpboot_fetch_buffer (image_handle, &sourcebuffer, &sourcesize); @@ -1913,12 +1144,11 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) } data = sourcebuffer; datasize = sourcesize; -#endif } else { /* * Read the new executable off disk */ - efi_status = load_image(li, &data, &datasize, PathName); + efi_status = load_image(shim_li, &data, &datasize, PathName); if (EFI_ERROR(efi_status)) { perror(L"Failed to load image %s: %r\n", PathName, efi_status); @@ -1937,19 +1167,28 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) * We need to modify the loaded image protocol entry before running * the new binary, so back it up */ - CopyMem(&li_bak, li, sizeof(li_bak)); + CopyMem(&shim_li_bak, shim_li, sizeof(shim_li_bak)); + + /* + * Update the loaded image with the second stage loader file path + */ + shim_li->FilePath = FileDevicePath(NULL, PathName); + if (!shim_li->FilePath) { + perror(L"Unable to update loaded image file path\n"); + efi_status = EFI_OUT_OF_RESOURCES; + goto restore; + } /* * Verify and, if appropriate, relocate and execute the executable */ - efi_status = handle_image(data, datasize, li, &entry_point, + efi_status = handle_image(data, datasize, shim_li, &entry_point, &alloc_address, &alloc_pages); if (EFI_ERROR(efi_status)) { perror(L"Failed to load image: %r\n", efi_status); PrintErrors(); ClearErrors(); - CopyMem(li, &li_bak, sizeof(li_bak)); - goto done; + goto restore; } loader_is_participating = 0; @@ -1959,10 +1198,8 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) */ efi_status = entry_point(image_handle, systab); - /* - * Restore our original loaded image values - */ - CopyMem(li, &li_bak, sizeof(li_bak)); +restore: + restore_loaded_image(); done: if (PathName) FreePool(PathName); @@ -2114,21 +1351,32 @@ get_load_option_optional_data(UINT8 *data, UINTN data_size, return EFI_SUCCESS; } -static int is_our_path(EFI_LOADED_IMAGE *li, CHAR16 *path, UINTN len) +static int is_our_path(EFI_LOADED_IMAGE *li, CHAR16 *path) { CHAR16 *dppath = NULL; + CHAR16 *PathName = NULL; + EFI_STATUS efi_status; int ret = 1; dppath = DevicePathToStr(li->FilePath); if (!dppath) return 0; + efi_status = generate_path_from_image_path(li, path, &PathName); + if (EFI_ERROR(efi_status)) { + perror(L"Unable to generate path %s: %r\n", path, + efi_status); + goto done; + } + dprint(L"dppath: %s\n", dppath); dprint(L"path: %s\n", path); - if (StrnCaseCmp(dppath, path, len)) + if (StrnCaseCmp(dppath, PathName, StrLen(dppath))) ret = 0; +done: FreePool(dppath); + FreePool(PathName); return ret; } @@ -2140,12 +1388,19 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) EFI_STATUS efi_status; EFI_LOADED_IMAGE *li = NULL; CHAR16 *start = NULL; - int remaining_size = 0; + UINTN remaining_size = 0; CHAR16 *loader_str = NULL; UINTN loader_len = 0; unsigned int i; + UINTN second_stage_len; - second_stage = DEFAULT_LOADER; + second_stage_len = (StrLen(DEFAULT_LOADER) + 1) * sizeof(CHAR16); + second_stage = AllocatePool(second_stage_len); + if (!second_stage) { + perror(L"Could not allocate %lu bytes\n", second_stage_len); + return EFI_OUT_OF_RESOURCES; + } + StrCpy(second_stage, DEFAULT_LOADER); load_options = NULL; load_options_size = 0; @@ -2156,6 +1411,10 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) return efi_status; } + /* Sanity check since we make several assumptions about the length */ + if (li->LoadOptionsSize % 2 != 0) + return EFI_INVALID_PARAMETER; + /* So, load options are a giant pain in the ass. If we're invoked * from the EFI shell, we get something like this: @@ -2202,6 +1461,12 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) * BDS will add that, but we ignore that here. */ + /* + * Maybe there just aren't any options... + */ + if (li->LoadOptionsSize == 0) + return EFI_SUCCESS; + /* * In either case, we've got to have at least a UCS2 NUL... */ @@ -2243,6 +1508,31 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) */ UINTN strings = count_ucs2_strings(li->LoadOptions, li->LoadOptionsSize); + + /* + * In some cases we get strings == 1 because BDS is using L' ' as the + * delimeter: + * 0000:74 00 65 00 73 00 74 00 2E 00 65 00 66 00 69 00 t.e.s.t...e.f.i. + * 0016:20 00 6F 00 6E 00 65 00 20 00 74 00 77 00 6F 00 ..o.n.e...t.w.o. + * 0032:20 00 74 00 68 00 72 00 65 00 65 00 00 00 ..t.h.r.e.e... + * + * If so replace it with NULs since the code already handles that + * case. + */ + if (strings == 1) { + UINT16 *cur = start = li->LoadOptions; + + /* replace L' ' with L'\0' if we find any */ + for (i = 0; i < li->LoadOptionsSize / 2; i++) { + if (cur[i] == L' ') + cur[i] = L'\0'; + } + + /* redo the string count */ + strings = count_ucs2_strings(li->LoadOptions, + li->LoadOptionsSize); + } + /* * If it's not string data, try it as an EFI_LOAD_OPTION. */ @@ -2262,70 +1552,41 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) } else if (strings >= 2) { /* * UEFI shell copies the whole line of the command into - * LoadOptions. We ignore the string before the first L' ', + * LoadOptions. We ignore the string before the first L'\0', * i.e. the name of this program. - * Counting by two bytes is safe, because we know the size is - * compatible with a UCS2-LE string. */ - UINT8 *cur = li->LoadOptions; - for (i = 0; i < li->LoadOptionsSize - 2; i += 2) { - CHAR16 c = (cur[i+1] << 8) | cur[i]; - if (c == L' ') { - start = (CHAR16 *)&cur[i+2]; - remaining_size = li->LoadOptionsSize - i - 2; - break; - } - } - - if (!start || remaining_size <= 0 || start[0] == L'\0') - return EFI_SUCCESS; - - for (i = 0; start[i] != '\0'; i++) { - if (start[i] == L' ') - start[i] = L'\0'; - if (start[i] == L'\0') { - loader_len = 2 * i + 2; + UINT16 *cur = li->LoadOptions; + for (i = 1; i < li->LoadOptionsSize / 2; i++) { + if (cur[i - 1] == L'\0') { + start = &cur[i]; + remaining_size = li->LoadOptionsSize - (i * 2); break; } } - if (loader_len) - remaining_size -= loader_len; - } else { - /* only find one string */ - start = li->LoadOptions; - loader_len = li->LoadOptionsSize; - } - - /* - * Just to be sure all that math is right... - */ - if (loader_len % 2 != 0) - return EFI_INVALID_PARAMETER; - - strings = count_ucs2_strings((UINT8 *)start, loader_len); - if (strings < 1) - return EFI_SUCCESS; - /* - * And then I found a version of BDS that gives us our own path in - * LoadOptions: + remaining_size -= i * 2 + 2; + } else if (strings == 1 && is_our_path(li, start)) { + /* + * And then I found a version of BDS that gives us our own path + * in LoadOptions: 77162C58 5c 00 45 00 46 00 49 00 |\.E.F.I.| 77162C60 5c 00 42 00 4f 00 4f 00 54 00 5c 00 42 00 4f 00 |\.B.O.O.T.\.B.O.| 77162C70 4f 00 54 00 58 00 36 00 34 00 2e 00 45 00 46 00 |O.T.X.6.4...E.F.| 77162C80 49 00 00 00 |I...| - * which is just cruel... So yeah, just don't use it. - */ - if (strings == 1 && is_our_path(li, start, loader_len)) + * which is just cruel... So yeah, just don't use it. + */ return EFI_SUCCESS; + } /* * Set up the name of the alternative loader and the LoadOptions for * the loader */ if (loader_len > 0) { - loader_str = AllocatePool(loader_len); + /* we might not always have a NULL at the end */ + loader_str = AllocatePool(loader_len + 2); if (!loader_str) { perror(L"Failed to allocate loader string\n"); return EFI_OUT_OF_RESOURCES; @@ -2333,7 +1594,7 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) for (i = 0; i < loader_len / 2; i++) loader_str[i] = start[i]; - loader_str[loader_len/2-1] = L'\0'; + loader_str[loader_len/2] = L'\0'; second_stage = loader_str; load_options = remaining_size ? start + (loader_len/2) : NULL; @@ -2344,13 +1605,13 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) } static void * -ossl_malloc(size_t num, const char *file, int line) +ossl_malloc(size_t num) { return AllocatePool(num); } static void -ossl_free(void *addr, const char *file, int line) +ossl_free(void *addr) { FreePool(addr); } @@ -2462,14 +1723,19 @@ uninstall_shim_protocols(void) EFI_STATUS shim_init(void) { - setup_verbosity(); + EFI_STATUS efi_status; + dprint(L"%a", shim_version); /* Set the second stage loader */ - set_second_stage (global_image_handle); + efi_status = set_second_stage(global_image_handle); + if (EFI_ERROR(efi_status)) { + perror(L"set_second_stage() failed: %r\n", efi_status); + return efi_status; + } if (secure_mode()) { - if (vendor_cert_size || vendor_dbx_size) { + if (vendor_authorized_size || vendor_deauthorized_size) { /* * If shim includes its own certificates then ensure * that anything it boots has performed some @@ -2479,15 +1745,23 @@ shim_init(void) loader_is_participating = 0; } - hook_exit(systab); } - return install_shim_protocols(); + hook_exit(systab); + + efi_status = install_shim_protocols(); + if (EFI_ERROR(efi_status)) + perror(L"install_shim_protocols() failed: %r\n", efi_status); + + return efi_status; } void shim_fini(void) { + if (secure_mode()) + cleanup_sbat_var(&sbat_var); + /* * Remove our protocols */ @@ -2499,9 +1773,10 @@ shim_fini(void) * Remove our hooks from system services. */ unhook_system_services(); - unhook_exit(); } + unhook_exit(); + /* * Free the space allocated for the alternative 2nd stage loader */ @@ -2521,13 +1796,20 @@ debug_hook(void) UINT8 *data = NULL; UINTN dataSize = 0; EFI_STATUS efi_status; - volatile register UINTN x = 0; + register volatile UINTN x = 0; extern char _text, _data; + const CHAR16 * const debug_var_name = +#ifdef ENABLE_SHIM_DEVEL + L"SHIM_DEVEL_DEBUG"; +#else + L"SHIM_DEBUG"; +#endif + if (x) return; - efi_status = get_variable(L"SHIM_DEBUG", &data, &dataSize, + efi_status = get_variable(debug_var_name, &data, &dataSize, SHIM_LOCK_GUID); if (EFI_ERROR(efi_status)) { return; @@ -2540,28 +1822,55 @@ debug_hook(void) &_text, &_data); console_print(L"Pausing for debugger attachment.\n"); - console_print(L"To disable this, remove the EFI variable SHIM_DEBUG-%g .\n", - &SHIM_LOCK_GUID); + console_print(L"To disable this, remove the EFI variable %s-%g .\n", + debug_var_name, &SHIM_LOCK_GUID); x = 1; while (x++) { /* Make this so it can't /totally/ DoS us. */ #if defined(__x86_64__) || defined(__i386__) || defined(__i686__) if (x > 4294967294ULL) break; - __asm__ __volatile__("pause"); #elif defined(__aarch64__) if (x > 1000) break; - __asm__ __volatile__("wfi"); #else if (x > 12000) break; - msleep(5000); #endif + pause(); } x = 1; } +typedef enum { + COLD_RESET, + EXIT_FAILURE, + EXIT_SUCCESS, // keep this one last +} devel_egress_action; + +void +devel_egress(devel_egress_action action UNUSED) +{ +#ifdef ENABLE_SHIM_DEVEL + char *reasons[] = { + [COLD_RESET] = "reset", + [EXIT_FAILURE] = "exit", + }; + if (action == EXIT_SUCCESS) + return; + + console_print(L"Waiting to %a...", reasons[action]); + for (size_t sleepcount = 0; sleepcount < 10; sleepcount++) { + console_print(L"%d...", 10 - sleepcount); + msleep(1000000); + } + console_print(L"\ndoing %a\n", action); + + if (action == COLD_RESET) + gRT->ResetSystem(EfiResetCold, EFI_SECURITY_VIOLATION, 0, NULL); +#endif +} + EFI_STATUS efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) { @@ -2570,17 +1879,32 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) verification_method = VERIFIED_BY_NOTHING; - vendor_cert_size = cert_table.vendor_cert_size; - vendor_dbx_size = cert_table.vendor_dbx_size; - vendor_cert = (UINT8 *)&cert_table + cert_table.vendor_cert_offset; - vendor_dbx = (UINT8 *)&cert_table + cert_table.vendor_dbx_offset; + vendor_authorized_size = cert_table.vendor_authorized_size; + vendor_authorized = (UINT8 *)&cert_table + cert_table.vendor_authorized_offset; + + vendor_deauthorized_size = cert_table.vendor_deauthorized_size; + vendor_deauthorized = (UINT8 *)&cert_table + cert_table.vendor_deauthorized_offset; + +#if defined(ENABLE_SHIM_CERT) + build_cert_size = sizeof(shim_cert); + build_cert = shim_cert; +#endif /* defined(ENABLE_SHIM_CERT) */ + CHAR16 *msgs[] = { - L"import_mok_state() failed\n", - L"shim_int() failed\n", + L"import_mok_state() failed", + L"shim_init() failed", + L"import of SBAT data failed", + L"SBAT self-check failed", + L"SBAT UEFI variable setting failed", NULL }; - int msg = 0; - + enum { + IMPORT_MOK_STATE, + SHIM_INIT, + IMPORT_SBAT, + SBAT_SELF_CHECK, + SET_SBAT, + } msg = IMPORT_MOK_STATE; /* * Set up the shim lock protocol so that grub and MokManager can @@ -2597,31 +1921,83 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) * Ensure that gnu-efi functions are available */ InitializeLib(image_handle, systab); + setup_verbosity(); - init_openssl(); + dprint(L"vendor_authorized:0x%08lx vendor_authorized_size:%lu\n", + vendor_authorized, vendor_authorized_size); + dprint(L"vendor_deauthorized:0x%08lx vendor_deauthorized_size:%lu\n", + vendor_deauthorized, vendor_deauthorized_size); /* * if SHIM_DEBUG is set, wait for a debugger to attach. */ debug_hook(); + efi_status = set_sbat_uefi_variable(); + if (EFI_ERROR(efi_status) && secure_mode()) { + perror(L"SBAT variable initialization failed\n"); + msg = SET_SBAT; + goto die; + } else if (EFI_ERROR(efi_status)) { + dprint(L"SBAT variable initialization failed: %r\n", + efi_status); + } + + if (secure_mode()) { + char *sbat_start = (char *)&_sbat; + char *sbat_end = (char *)&_esbat; + + INIT_LIST_HEAD(&sbat_var); + efi_status = parse_sbat_var(&sbat_var); + if (EFI_ERROR(efi_status)) { + perror(L"Parsing SBAT variable failed: %r\n", + efi_status); + msg = IMPORT_SBAT; + goto die; + } + + efi_status = handle_sbat(sbat_start, sbat_end - sbat_start); + if (EFI_ERROR(efi_status)) { + perror(L"Verifiying shim SBAT data failed: %r\n", + efi_status); + msg = SBAT_SELF_CHECK; + goto die; + } + } + + init_openssl(); + /* * Before we do anything else, validate our non-volatile, * boot-services-only state variables are what we think they are. */ efi_status = import_mok_state(image_handle); - if (EFI_ERROR(efi_status)) { + if (!secure_mode() && efi_status == EFI_INVALID_PARAMETER) { + /* + * Make copy failures fatal only if secure_mode is enabled, or + * the error was anything else than EFI_INVALID_PARAMETER. + * There are non-secureboot firmware implementations that don't + * reserve enough EFI variable memory to fit the variable. + */ + console_print(L"Importing MOK states has failed: %s: %r\n", + msgs[msg], efi_status); + console_print(L"Continuing boot since secure mode is disabled"); + } else if (EFI_ERROR(efi_status)) { die: console_print(L"Something has gone seriously wrong: %s: %r\n", msgs[msg], efi_status); +#if defined(ENABLE_SHIM_DEVEL) + devel_egress(COLD_RESET); +#else msleep(5000000); gRT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL); +#endif } efi_status = shim_init(); if (EFI_ERROR(efi_status)) { - msg = 1; + msg = SHIM_INIT; goto die; } @@ -2639,5 +2015,6 @@ die: efi_status = init_grub(image_handle); shim_fini(); + devel_egress(EFI_ERROR(efi_status) ? EXIT_FAILURE : EXIT_SUCCESS); return efi_status; } -- cgit v1.2.3