diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 7a80738..6027d4d 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -22,66 +22,70 @@ jobs: efiarch: aa64 gccarch: aarch64 makearch: aarch64 - distro: f36 + distro: f41 - arch: amd64 efiarch: aa64 gccarch: aarch64 makearch: aarch64 - distro: f35 + distro: f40 - arch: amd64 efiarch: arm gccarch: arm makearch: arm - distro: f36 + distro: f41 - arch: amd64 efiarch: arm gccarch: arm makearch: arm - distro: f35 + distro: f40 - arch: amd64 efiarch: arm gccarch: arm makearch: arm - distro: f34 + distro: f39 - arch: amd64 efiarch: x64 gccarch: x86_64 makearch: x86_64 - distro: f36 + distro: f41 - arch: amd64 efiarch: x64 gccarch: x86_64 makearch: x86_64 - distro: f35 + distro: f40 - arch: amd64 efiarch: x64 gccarch: x86_64 makearch: x86_64 - distro: f34 + distro: f39 - arch: amd64 efiarch: ia32 gccarch: x86_64 makearch: ia32 - distro: f36 + distro: f41 - arch: amd64 efiarch: ia32 gccarch: x86_64 makearch: ia32 - distro: f35 + distro: f40 - arch: amd64 efiarch: ia32 gccarch: x86_64 makearch: ia32 - distro: f34 + distro: f39 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: # otherwise we are testing target branch instead of the PR branch (see pull_request_target trigger) ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 submodules: recursive + - name: Work around directory ownership issue + id: ignore-ownership + run: | + git config --global --add safe.directory /__w/shim/shim - name: Update submodules on ${{ matrix.distro }} for ${{ matrix.efiarch }} id: update-submodules run: | @@ -118,15 +122,15 @@ jobs: - arch: amd64 efiarch: x64 makearch: x86_64 - distro: f36 + distro: f41 - arch: amd64 efiarch: x64 makearch: x86_64 - distro: f35 + distro: f40 - arch: amd64 efiarch: x64 makearch: x86_64 - distro: f34 + distro: f39 - arch: amd64 efiarch: x64 makearch: x86_64 @@ -138,15 +142,7 @@ jobs: - arch: amd64 efiarch: ia32 makearch: ia32 - distro: f36 - - arch: amd64 - efiarch: ia32 - makearch: ia32 - distro: f35 - - arch: amd64 - efiarch: ia32 - makearch: ia32 - distro: f34 + distro: f39 - arch: amd64 efiarch: ia32 makearch: ia32 @@ -154,12 +150,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: # otherwise we are testing target branch instead of the PR branch (see pull_request_target trigger) ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 submodules: recursive + - name: Work around directory ownership issue + id: ignore-ownership + run: | + git config --global --add safe.directory /__w/shim/shim - name: Update submodules on ${{ matrix.distro }} for ${{ matrix.efiarch }} id: update-submodules run: | @@ -182,3 +182,41 @@ jobs: make ARCH=${{ matrix.makearch }} PREFIX=/usr DESTDIR=/destdir EFIDIR=test ENABLE_SHIM_HASH=true install echo 'results:' find /destdir -type f + + build-pull-request-intel-compile-commands-json: + runs-on: ubuntu-20.04 + container: vathpela/efi-ci:${{ matrix.distro }}-x64 + name: ${{ matrix.distro }} ${{ matrix.efiarch }} build compile_commands.json + + strategy: + matrix: + include: + - arch: amd64 + efiarch: x64 + makearch: x86_64 + distro: f41 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + # otherwise we are testing target branch instead of the PR branch (see pull_request_target trigger) + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + submodules: recursive + - name: Work around directory ownership issue + id: ignore-ownership + run: | + git config --global --add safe.directory /__w/shim/shim + - name: Update submodules on ${{ matrix.distro }} for ${{ matrix.efiarch }} + id: update-submodules + run: | + make update + - name: Do 'make clean' on ${{ matrix.distro }} for ${{ matrix.efiarch }} + id: clean + run: | + make ARCH=${{ matrix.makearch }} PREFIX=/usr DESTDIR=/destdir EFIDIR=test ENABLE_SHIM_HASH=true clean + - name: Build compile_commands.json on ${{ matrix.distro }} for ${{ matrix.efiarch }} + id: compile_commands + run: | + make ARCH=${{ matrix.makearch }} PREFIX=/usr DESTDIR=/destdir EFIDIR=test ENABLE_SHIM_HASH=true compile_commands.json diff --git a/.gitignore b/.gitignore index 9085e5a..d829669 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,8 @@ Make.local /crash-* /fuzz-* !/fuzz-*.c +/generate_sbat_var_defs +/generated_sbat_var_defs.h /leak-* /post-process-pe /random.bin diff --git a/.gitmodules b/.gitmodules index d0f1973..41842dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "gnu-efi"] path = gnu-efi url = https://github.com/rhboot/gnu-efi.git - branch = shim-15.8 + branch = shim-16.0 diff --git a/BUILDING b/BUILDING index 17cd98d..5005868 100644 --- a/BUILDING +++ b/BUILDING @@ -37,15 +37,6 @@ Variables you could set to customize the build: debugger only on the development branch and not the OS you need to boot to scp in a new development build. Likewise, we look for SHIM_DEVEL_VERBOSE rather than SHIM_VERBOSE. -- DISABLE_EBS_PROTECTION - On systems where a second stage bootloader is not used, and the Linux - Kernel is embedded in the same EFI image as shim and booted directly - from shim, shim's ExitBootServices() hook can cause problems as the - kernel never calls the shim's verification protocol. In this case - calling the shim verification protocol is unnecessary and redundant as - shim has already verified the kernel when shim loaded the kernel as the - second stage loader. In such a case, and only in this case, you should - use DISABLE_EBS_PROTECTION=y to build. - DISABLE_REMOVABLE_LOAD_OPTIONS Do not parse load options when invoked as boot*.efi. This prevents boot failures because of unexpected data in boot entries automatically generated @@ -81,6 +72,9 @@ Variables you could set to customize the build: - POST_PROCESS_PE_FLAGS This allows you to add flags to the invocation of "post-process-pe", for example to disable the NX compatibility flag. +- ENABLE_CODESIGN_EKU + This changes the certificate validation logic to require Extended Key + Usage 1.3.6.1.5.5.7.3.3 ("Code Signing"). Vendor SBAT data: It will sometimes be requested by reviewers that a build includes extra diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 59a2c94..2144aed 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -rharwood AT redhat DOT com. +contact AT linux-uefi DOT org. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/Cryptlib/InternalCryptLib.h b/Cryptlib/InternalCryptLib.h index dc1a95e..0ad2ef7 100644 --- a/Cryptlib/InternalCryptLib.h +++ b/Cryptlib/InternalCryptLib.h @@ -32,5 +32,38 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #define OBJ_length(o) ((o)->length) #endif -#endif +#if defined(ENABLE_CODESIGN_EKU) +/** + Check input P7Data is a wrapped ContentInfo structure or not. If not construct + a new structure to wrap P7Data. + Caution: This function may receive untrusted input. + UEFI Authenticated Variable is external input, so this function will do basic + check for PKCS#7 data structure. + + @param[in] P7Data Pointer to the PKCS#7 message to verify. + @param[in] P7Length Length of the PKCS#7 message in bytes. + @param[out] WrapFlag If TRUE P7Data is a ContentInfo structure, otherwise + return FALSE. + @param[out] WrapData If return status of this function is TRUE: + 1) when WrapFlag is TRUE, pointer to P7Data. + 2) when WrapFlag is FALSE, pointer to a new ContentInfo + structure. It's caller's responsibility to free this + buffer. + @param[out] WrapDataSize Length of ContentInfo structure in bytes. + + @retval TRUE The operation is finished successfully. + @retval FALSE The operation is failed due to lack of resources. + +**/ +BOOLEAN +WrapPkcs7Data ( + IN CONST UINT8 *P7Data, + IN UINTN P7Length, + OUT BOOLEAN *WrapFlag, + OUT UINT8 **WrapData, + OUT UINTN *WrapDataSize + ); + +#endif +#endif diff --git a/Cryptlib/Library/BaseCryptLib.h b/Cryptlib/Library/BaseCryptLib.h index 2df8bd2..439f051 100644 --- a/Cryptlib/Library/BaseCryptLib.h +++ b/Cryptlib/Library/BaseCryptLib.h @@ -2403,6 +2403,48 @@ Pkcs7Verify ( IN UINTN DataLength ); +#if defined(ENABLE_CODESIGN_EKU) +/** + This function receives a PKCS#7 formatted signature blob, + looks for the EKU SEQUENCE blob, and if found then looks + for all the required EKUs. This function was created so that + the Surface team can cut down on the number of Certificate + Authorities (CA's) by checking EKU's on leaf signers for + a specific product. This prevents one product's certificate + from signing another product's firmware or unlock blobs. + + Note that this function does not validate the certificate chain. + That needs to be done before using this function. + + @param[in] Pkcs7Signature The PKCS#7 signed information content block. An array + containing the content block with both the signature, + the signer's certificate, and any necessary intermediate + certificates. + @param[in] Pkcs7SignatureSize Number of bytes in Pkcs7Signature. + @param[in] RequiredEKUs Array of null-terminated strings listing OIDs of + required EKUs that must be present in the signature. + @param[in] RequiredEKUsSize Number of elements in the RequiredEKUs string array. + @param[in] RequireAllPresent If this is TRUE, then all of the specified EKU's + must be present in the leaf signer. If it is + FALSE, then we will succeed if we find any + of the specified EKU's. + + @retval EFI_SUCCESS The required EKUs were found in the signature. + @retval EFI_INVALID_PARAMETER A parameter was invalid. + @retval EFI_NOT_FOUND One or more EKU's were not found in the signature. + +**/ +EFI_STATUS +EFIAPI +VerifyEKUsInPkcs7Signature ( + IN CONST UINT8 *Pkcs7Signature, + IN CONST UINT32 SignatureSize, + IN CONST CHAR8 *RequiredEKUs[], + IN CONST UINT32 RequiredEKUsSize, + IN BOOLEAN RequireAllPresent + ); +#endif + /** Extracts the attached content from a PKCS#7 signed data if existed. The input signed data could be wrapped in a ContentInfo structure. diff --git a/Cryptlib/Makefile b/Cryptlib/Makefile index 626788c..68a9395 100644 --- a/Cryptlib/Makefile +++ b/Cryptlib/Makefile @@ -14,6 +14,9 @@ INCLUDES = -I$(CRYPTDIR) -I$(CRYPTDIR)/Include \ WARNFLAGS += -Wno-unused-parameter \ -Wno-unused-but-set-variable +WERRFLAGS += -Wno-error=unused-but-set-variable \ + -Wno-error=unused-parameter + CFLAGS = $(FEATUREFLAGS) \ $(OPTIMIZATIONS) \ $(WARNFLAGS) \ @@ -37,6 +40,9 @@ endif ifeq ($(ARCH),arm) DEFINES += -DMDE_CPU_ARM endif +ifeq ($(ENABLE_CODESIGN_EKU),1) +DEFINES += -DENABLE_CODESIGN_EKU +endif LDFLAGS = -nostdlib -znocombreloc @@ -67,6 +73,10 @@ OBJS = Hash/CryptMd4Null.o \ SysCall/BaseMemAllocation.o \ SysCall/BaseStrings.o +ifeq ($(ENABLE_CODESIGN_EKU),1) + OBJS += Pk/CryptPkcs7VerifyEku.o +endif + all: $(TARGET) libcryptlib.a: $(OBJS) diff --git a/Cryptlib/OpenSSL/Makefile b/Cryptlib/OpenSSL/Makefile index d59c5d7..e517bcf 100644 --- a/Cryptlib/OpenSSL/Makefile +++ b/Cryptlib/OpenSSL/Makefile @@ -26,6 +26,9 @@ WARNFLAGS += -Wno-empty-body \ -Wno-unused-but-set-variable \ -Wno-unused-parameter +WERRFLAGS += -Wno-error=unused-but-set-variable \ + -Wno-error=unused-parameter + CFLAGS = $(FEATUREFLAGS) \ $(OPTIMIZATIONS) \ $(WARNFLAGS) \ diff --git a/Cryptlib/Pk/CryptPkcs7Verify.c b/Cryptlib/Pk/CryptPkcs7Verify.c index c189384..640b01d 100644 --- a/Cryptlib/Pk/CryptPkcs7Verify.c +++ b/Cryptlib/Pk/CryptPkcs7Verify.c @@ -29,6 +29,10 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include UINT8 mOidValue[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 }; +#if defined(ENABLE_CODESIGN_EKU) +/* EKU CodeSign */ +CHAR8 mOidCodeSign[] = "1.3.6.1.5.5.7.3.3"; +#endif #if 1 #if OPENSSL_VERSION_NUMBER < 0x10100000L @@ -846,6 +850,10 @@ Pkcs7Verify ( CONST UINT8 *Temp; UINTN SignedDataSize; BOOLEAN Wrapped; +#if defined(ENABLE_CODESIGN_EKU) + CONST CHAR8 *Ekus[1]; + EFI_STATUS EFI_Status; +#endif // // Check input parameters. @@ -859,6 +867,9 @@ Pkcs7Verify ( DataBio = NULL; Cert = NULL; CertStore = NULL; +#if defined(ENABLE_CODESIGN_EKU) + Ekus[0] = mOidCodeSign; +#endif // // Register & Initialize necessary digest algorithms for PKCS#7 Handling @@ -958,6 +969,13 @@ Pkcs7Verify ( // X509_STORE_set_purpose (CertStore, X509_PURPOSE_ANY); +#if defined(ENABLE_CODESIGN_EKU) + EFI_Status = VerifyEKUsInPkcs7Signature(P7Data, P7Length, Ekus, 1, TRUE); + if (EFI_Status != EFI_SUCCESS) { + goto _Exit; + } +#endif + // // Verifies the PKCS#7 signedData structure // diff --git a/Cryptlib/Pk/CryptPkcs7VerifyEku.c b/Cryptlib/Pk/CryptPkcs7VerifyEku.c new file mode 100644 index 0000000..f277f1d --- /dev/null +++ b/Cryptlib/Pk/CryptPkcs7VerifyEku.c @@ -0,0 +1,516 @@ +/** @file + This module verifies that Enhanced Key Usages (EKU's) are present within + a PKCS7 signature blob using OpenSSL. + + Copyright (C) Microsoft Corporation. All Rights Reserved. + Copyright (c) 2019, Intel Corporation. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include "InternalCryptLib.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + This function will return the leaf signer certificate in a chain. This is + required because certificate chains are not guaranteed to have the + certificates in the order that they were issued. + + A typical certificate chain looks like this: + + + ---------------------------- + | Root | + ---------------------------- + ^ + | + ---------------------------- + | Policy CA | <-- Typical Trust Anchor. + ---------------------------- + ^ + | + ---------------------------- + | Issuing CA | + ---------------------------- + ^ + | + ----------------------------- + / End-Entity (leaf) signer / <-- Bottom certificate. + ----------------------------- EKU: "1.3.6.1.4.1.311.76.9.21.1" + (Firmware Signing) + + + @param[in] CertChain Certificate chain. + + @param[out] SignerCert Last certificate in the chain. For PKCS7 signatures, + this will be the end-entity (leaf) signer cert. + + @retval EFI_SUCCESS The required EKUs were found in the signature. + @retval EFI_INVALID_PARAMETER A parameter was invalid. + @retval EFI_NOT_FOUND The number of signers found was not 1. + +**/ +EFI_STATUS +GetSignerCertificate ( + IN CONST PKCS7 *CertChain, + OUT X509 **SignerCert + ) +{ + EFI_STATUS Status; + STACK_OF(X509) *Signers; + INT32 NumberSigners; + + Status = EFI_SUCCESS; + Signers = NULL; + NumberSigners = 0; + + if (CertChain == NULL || SignerCert == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // Get the signers from the chain. + // + Signers = PKCS7_get0_signers ((PKCS7*) CertChain, NULL, PKCS7_BINARY); + if (Signers == NULL) { + // + // Fail to get signers form PKCS7 + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // There should only be one signer in the PKCS7 stack. + // + NumberSigners = sk_X509_num (Signers); + if (NumberSigners != 1) { + // + // The number of singers should have been 1 + // + Status = EFI_NOT_FOUND; + goto Exit; + } + + *SignerCert = sk_X509_value (Signers, 0); + +Exit: + // + // Release Resources + // + if (Signers != NULL) { + sk_X509_free (Signers); + } + + return Status; +} + + +/** + Determines if the specified EKU represented in ASN1 form is present + in a given certificate. + + @param[in] Cert The certificate to check. + + @param[in] Asn1ToFind The EKU to look for. + + @retval EFI_SUCCESS We successfully identified the signing type. + @retval EFI_INVALID_PARAMETER A parameter was invalid. + @retval EFI_NOT_FOUND One or more EKU's were not found in the signature. + +**/ +EFI_STATUS +IsEkuInCertificate ( + IN CONST X509 *Cert, + IN ASN1_OBJECT *Asn1ToFind + ) +{ + EFI_STATUS Status; + X509 *ClonedCert; + X509_EXTENSION *Extension; + EXTENDED_KEY_USAGE *Eku; + INT32 ExtensionIndex; + INTN NumExtensions; + ASN1_OBJECT *Asn1InCert; + INTN Index; + + Status = EFI_NOT_FOUND; + ClonedCert = NULL; + Extension = NULL; + Eku = NULL; + ExtensionIndex = -1; + NumExtensions = 0; + Asn1InCert = NULL; + + if (Cert == NULL || Asn1ToFind == NULL) { + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // Clone the certificate. This is required because the Extension API's + // only work once per instance of an X509 object. + // + ClonedCert = X509_dup ((X509*)Cert); + if (ClonedCert == NULL) { + // + // Fail to duplicate cert. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // Look for the extended key usage. + // + ExtensionIndex = X509_get_ext_by_NID (ClonedCert, NID_ext_key_usage, -1); + + if (ExtensionIndex < 0) { + // + // Fail to find 'NID_ext_key_usage' in Cert. + // + goto Exit; + } + + Extension = X509_get_ext (ClonedCert, ExtensionIndex); + if (Extension == NULL) { + // + // Fail to get Extension form cert. + // + goto Exit; + } + + Eku = (EXTENDED_KEY_USAGE*)X509V3_EXT_d2i (Extension); + if (Eku == NULL) { + // + // Fail to get Eku from extension. + // + goto Exit; + } + + NumExtensions = sk_ASN1_OBJECT_num (Eku); + + // + // Now loop through the extensions, looking for the specified Eku. + // + for (Index = 0; Index < NumExtensions; Index++) { + Asn1InCert = sk_ASN1_OBJECT_value (Eku, (INT32)Index); + if (Asn1InCert == NULL) { + // + // Fail to get ASN object from Eku. + // + goto Exit; + } + + if (OBJ_cmp(Asn1InCert, Asn1ToFind) == 0) { + // + // Found Eku in certificate. + // + Status = EFI_SUCCESS; + goto Exit; + } + } + +Exit: + + // + // Release Resources + // + if (ClonedCert != NULL) { + X509_free (ClonedCert); + } + + if (Eku != NULL) { + sk_ASN1_OBJECT_pop_free (Eku, ASN1_OBJECT_free); + } + + return Status; +} + + +/** + Determines if the specified EKUs are present in a signing certificate. + + @param[in] SignerCert The certificate to check. + @param[in] RequiredEKUs The EKUs to look for. + @param[in] RequiredEKUsSize The number of EKUs + @param[in] RequireAllPresent If TRUE, then all the specified EKUs + must be present in the certificate. + + @retval EFI_SUCCESS We successfully identified the signing type. + @retval EFI_INVALID_PARAMETER A parameter was invalid. + @retval EFI_NOT_FOUND One or more EKU's were not found in the signature. +**/ +EFI_STATUS +CheckEKUs( + IN CONST X509 *SignerCert, + IN CONST CHAR8 *RequiredEKUs[], + IN CONST UINT32 RequiredEKUsSize, + IN BOOLEAN RequireAllPresent + ) +{ + EFI_STATUS Status; + ASN1_OBJECT *Asn1ToFind; + UINT32 NumEkusFound; + UINT32 Index; + + Status = EFI_NOT_FOUND; + Asn1ToFind = NULL; + NumEkusFound = 0; + + if (SignerCert == NULL || RequiredEKUs == NULL || RequiredEKUsSize == 0) { + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + for (Index = 0; Index < RequiredEKUsSize; Index++) { + // + // Finding required EKU in cert. + // + if (Asn1ToFind != NULL) { + ASN1_OBJECT_free(Asn1ToFind); + Asn1ToFind = NULL; + } + + Asn1ToFind = OBJ_txt2obj (RequiredEKUs[Index], 0); + if (Asn1ToFind == NULL) { + // + // Fail to convert required EKU to ASN1. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + Status = IsEkuInCertificate (SignerCert, Asn1ToFind); + if (Status == EFI_SUCCESS) { + NumEkusFound++; + if (!RequireAllPresent) { + // + // Found at least one, so we are done. + // + goto Exit; + } + } else { + // + // Fail to find Eku in cert + break; + } + } + +Exit: + + if (Asn1ToFind != NULL) { + ASN1_OBJECT_free(Asn1ToFind); + } + + if (RequireAllPresent && + NumEkusFound == RequiredEKUsSize) { + // + // Found all required EKUs in certificate. + // + Status = EFI_SUCCESS; + } + + return Status; +} + +/** + This function receives a PKCS#7 formatted signature blob, + looks for the EKU SEQUENCE blob, and if found then looks + for all the required EKUs. This function was created so that + the Surface team can cut down on the number of Certificate + Authorities (CA's) by checking EKU's on leaf signers for + a specific product. This prevents one product's certificate + from signing another product's firmware or unlock blobs. + + Note that this function does not validate the certificate chain. + That needs to be done before using this function. + + @param[in] Pkcs7Signature The PKCS#7 signed information content block. An array + containing the content block with both the signature, + the signer's certificate, and any necessary intermediate + certificates. + @param[in] Pkcs7SignatureSize Number of bytes in Pkcs7Signature. + @param[in] RequiredEKUs Array of null-terminated strings listing OIDs of + required EKUs that must be present in the signature. + @param[in] RequiredEKUsSize Number of elements in the RequiredEKUs string array. + @param[in] RequireAllPresent If this is TRUE, then all of the specified EKU's + must be present in the leaf signer. If it is + FALSE, then we will succeed if we find any + of the specified EKU's. + + @retval EFI_SUCCESS The required EKUs were found in the signature. + @retval EFI_INVALID_PARAMETER A parameter was invalid. + @retval EFI_NOT_FOUND One or more EKU's were not found in the signature. + +**/ +EFI_STATUS +EFIAPI +VerifyEKUsInPkcs7Signature ( + IN CONST UINT8 *Pkcs7Signature, + IN CONST UINT32 SignatureSize, + IN CONST CHAR8 *RequiredEKUs[], + IN CONST UINT32 RequiredEKUsSize, + IN BOOLEAN RequireAllPresent + ) +{ + EFI_STATUS Status; + PKCS7 *Pkcs7; + STACK_OF(X509) *CertChain; + INT32 SignatureType; + INT32 NumberCertsInSignature; + X509 *SignerCert; + UINT8 *SignedData; + UINT8 *Temp; + UINTN SignedDataSize; + BOOLEAN IsWrapped; + BOOLEAN Ok; + + Status = EFI_SUCCESS; + Pkcs7 = NULL; + CertChain = NULL; + SignatureType = 0; + NumberCertsInSignature = 0; + SignerCert = NULL; + SignedData = NULL; + SignedDataSize = 0; + IsWrapped = FALSE; + Ok = FALSE; + + // + //Validate the input parameters. + // + if (Pkcs7Signature == NULL || + SignatureSize == 0 || + RequiredEKUs == NULL || + RequiredEKUsSize == 0) { + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + if (RequiredEKUsSize == 1) { + RequireAllPresent = TRUE; + } + + // + // Wrap the PKCS7 data if needed. + // + Ok = WrapPkcs7Data (Pkcs7Signature, + SignatureSize, + &IsWrapped, + &SignedData, + &SignedDataSize); + if (!Ok) { + // + // Fail to Wrap the PKCS7 data. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + Temp = SignedData; + + // + // Create the PKCS7 object. + // + Pkcs7 = d2i_PKCS7 (NULL, (const unsigned char **)&Temp, (INT32)SignedDataSize); + if (Pkcs7 == NULL) { + // + // Fail to read PKCS7 data. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // Get the certificate chain. + // + SignatureType = OBJ_obj2nid (Pkcs7->type); + switch (SignatureType) { + case NID_pkcs7_signed: + if (Pkcs7->d.sign != NULL) { + CertChain = Pkcs7->d.sign->cert; + } + break; + case NID_pkcs7_signedAndEnveloped: + if (Pkcs7->d.signed_and_enveloped != NULL) { + CertChain = Pkcs7->d.signed_and_enveloped->cert; + } + break; + default: + break; + } + + // + // Ensure we have a certificate stack + // + if (CertChain == NULL) { + // + // Fail to get the certificate stack from signature. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // Find out how many certificates were in the PKCS7 signature. + // + NumberCertsInSignature = sk_X509_num (CertChain); + + if (NumberCertsInSignature == 0) { + // + // Fail to find any certificates in signature. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + // + // Get the leaf signer. + // + Status = GetSignerCertificate (Pkcs7, &SignerCert); + if (Status != EFI_SUCCESS || SignerCert == NULL) { + // + // Fail to get the end-entity leaf signer certificate. + // + Status = EFI_INVALID_PARAMETER; + goto Exit; + } + + Status = CheckEKUs (SignerCert, RequiredEKUs, RequiredEKUsSize, RequireAllPresent); + if (Status != EFI_SUCCESS) { + goto Exit; + } + +Exit: + + // + // Release Resources + // + // If the signature was not wrapped, then the call to WrapData() will allocate + // the data and add a header to it + // + if (!IsWrapped && SignedData) { + free (SignedData); + } + + if (Pkcs7 != NULL) { + PKCS7_free (Pkcs7); + } + + return Status; +} + diff --git a/Delivering_Sbat_Revocations.md b/Delivering_Sbat_Revocations.md new file mode 100644 index 0000000..d3e5060 --- /dev/null +++ b/Delivering_Sbat_Revocations.md @@ -0,0 +1,29 @@ +When new sbat based revocations become public they are added to +https://github.com/rhboot/shim/blob/main/SbatLevel_Variable.txt They +are identified by their year, month, day, counter YYYYMMDDCC field in +the header. + +If secure boot is disabled, shim will always clear the applied +revocations. + +shim binaries will include the opt-in latest revocation payload +available at the time that they are built. This can be applied by +running mokutil --set-sbat-policy latest and rebooting with the new +shim binary in place. A shim build can also specify a +-DSBAT_AUTOMATIC_DATE=YYYYMMDDCC on the command line which will +include and automatically apply that revocation. shim will never +downgrade a revocation. The only way to roll back is to disable secure +boot, load shim to clear the revocations and then re-apply the desired +level. + +In addition to building revocation levels into shim, they can also be +delivered via a revocations_sbat.efi binary. These binaries can be +created from the https://github.com/rhboot/certwrapper +repository. This repository uses the same +https://github.com/rhboot/shim/blob/main/SbatLevel_Variable.txt file +as the source of the revocation metadata. Both +SBAT_LATEST_DATE=YYYYMMDDCC and SBAT_AUTOMATIC_DATE=YYYYMMDDCC can be +specified there. These files need to be signed with a certificate that +your shim trusts. These files can be created without the need to +deliver a new shim and can be set to have shim automatically apply a +new revocations whey they are delivered into the system partition. diff --git a/Make.defaults b/Make.defaults index e75cd3c..c5fa32b 100644 --- a/Make.defaults +++ b/Make.defaults @@ -109,7 +109,7 @@ INCLUDES = -nostdinc \ override DEFAULT_FEATUREFLAGS = \ -std=gnu11 \ - -ggdb \ + -ggdb -gdwarf-4 -gstrict-dwarf \ -ffreestanding \ $(shell $(CC) -fmacro-prefix-map=./=./ -E -x c /dev/null >/dev/null 2>&1 && echo -fmacro-prefix-map='$(TOPDIR)/=$(DEBUGSRC)') \ -fno-stack-protector \ @@ -149,10 +149,6 @@ ifneq ($(origin REQUIRE_TPM), undefined) DEFINES += -DREQUIRE_TPM endif -ifneq ($(origin DISABLE_EBS_PROTECTION), undefined) - DEFINES += -DDISABLE_EBS_PROTECTION -endif - ifneq ($(origin DISABLE_REMOVABLE_LOAD_OPTIONS), undefined) DEFINES += -DDISABLE_REMOVABLE_LOAD_OPTIONS endif @@ -201,4 +197,6 @@ ifneq ($(VERBOSE),) export VERBOSE endif +export DEFINES + # vim:filetype=make diff --git a/Makefile b/Makefile index 8283d56..d394439 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ default : all NAME = shim -VERSION = 15.8 +VERSION = 16.0 ifneq ($(origin RELEASE),undefined) DASHRELEASE ?= -$(RELEASE) else @@ -38,12 +38,12 @@ 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 pe-relocate.o httpboot.o csv.o load-options.o +OBJS = shim.o globals.o memattrs.o mok.o netboot.o cert.o dp.o loader-proto.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 utils.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 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_SOURCES = shim.c globals.c memattrs.c mok.c netboot.c dp.c loader-proto.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 dp.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 +FALLBACK_OBJS = fallback.o tpm.o errlog.o sbat_data.o globals.o utils.o ORIG_FALLBACK_SRCS = fallback.c SBATPATH = $(TOPDIR)/data/sbat.csv @@ -69,16 +69,24 @@ ifneq ($(origin FALLBACK_VERBOSE_WAIT), undefined) CFLAGS += -DFALLBACK_VERBOSE_WAIT=$(FALLBACK_VERBOSE_WAIT) endif -all: confcheck $(TARGETS) +all: confcheck certcheck $(TARGETS) confcheck: ifneq ($(origin EFI_PATH),undefined) $(error EFI_PATH is no longer supported, you must build using the supplied copy of gnu-efi) endif +certcheck: +ifneq ($(origin VENDOR_CERT_FILE), undefined) + @if grep -q "BEGIN" $(VENDOR_CERT_FILE); then \ + echo "$(VENDOR_CERT_FILE) is PEM-format, convert to DER!"; \ + exit 1; \ + fi +endif + compile_commands.json : Makefile Make.rules Make.defaults make clean - bear -- make COMPILER=clang test all + bear -- make COMPILER=clang WARNFLAGS+="-Wno-#warnings" test all sed -i \ -e 's/"-maccumulate-outgoing-args",//g' \ $@ @@ -92,6 +100,7 @@ shim.crt: shim.cer: shim.crt $(OPENSSL) x509 -outform der -in $< -out $@ + .NOTPARALLEL: shim_cert.h shim_cert.h: shim.cer echo "static UINT8 shim_cert[] __attribute__((__unused__)) = {" > $@ @@ -113,8 +122,12 @@ shim.o: $(SOURCES) ifneq ($(origin ENABLE_SHIM_CERT),undefined) shim.o: shim_cert.h endif +# Both of these need to be here so that when TOPDIR is unset, make isn't trying +# to match against ./sbat_var.S, which isn't a target it will ever try to build. +$(TOPDIR)/sbat_var.S sbat_var.S: generated_sbat_var_defs.h shim.o: $(wildcard $(TOPDIR)/*.h) + sbat.%.csv : data/sbat.%.csv $(DOS2UNIX) $(D2UFLAGS) $< $@ tail -c1 $@ | read -r _ || echo >> $@ # ensure a trailing newline @@ -180,6 +193,13 @@ lib/lib.a: | $(TOPDIR)/lib/Makefile $(wildcard $(TOPDIR)/include/*.[ch]) post-process-pe : $(TOPDIR)/post-process-pe.c $(HOSTCC) -std=gnu11 -Og -g3 -Wall -Wextra -Wno-missing-field-initializers -Werror -o $@ $< +generate_sbat_var_defs: $(TOPDIR)/generate_sbat_var_defs.c + $(HOSTCC) -std=gnu11 -Og -g3 -Wall -Wextra -Wno-missing-field-initializers -Werror -o $@ $< + +.NOTPARALLEL: generated_sbat_var_defs.h +generated_sbat_var_defs.h: generate_sbat_var_defs + ./generate_sbat_var_defs $(TOPDIR) > $@ + buildid : $(TOPDIR)/buildid.c $(HOSTCC) -I/usr/include -Og -g3 -Wall -Werror -Wextra -o $@ $< -lelf @@ -261,6 +281,7 @@ endif -j .dynamic -j .rodata -j .rel* \ -j .rela* -j .dyn -j .reloc -j .eh_frame \ -j .vendor_cert -j .sbat -j .sbatlevel \ + --file-alignment 0x1000 \ $(FORMAT) $< $@ ./post-process-pe -vv $(POST_PROCESS_PE_FLAGS) $@ @@ -280,6 +301,7 @@ endif -j .debug_info -j .debug_abbrev -j .debug_aranges \ -j .debug_line -j .debug_str -j .debug_ranges \ -j .note.gnu.build-id \ + --file-alignment 0x1000 \ $< $@ ifneq ($(origin ENABLE_SBSIGN),undefined) @@ -302,7 +324,7 @@ fuzz fuzz-clean fuzz-coverage fuzz-lto : EFI_INCLUDES="$(EFI_INCLUDES)" \ fuzz-clean $@ -test test-clean test-coverage test-lto : +test test-clean test-coverage test-lto : generated_sbat_var_defs.h @make -f $(TOPDIR)/include/test.mk \ COMPILER="$(COMPILER)" \ CROSS_COMPILE="$(CROSS_COMPILE)" \ @@ -346,6 +368,7 @@ clean-lib-objs: clean-shim-objs: @rm -rvf $(TARGET) *.o $(SHIM_OBJS) $(MOK_OBJS) $(FALLBACK_OBJS) $(KEYS) certdb $(BOOTCSVNAME) @rm -vf *.debug *.so *.efi *.efi.* *.tar.* version.c buildid post-process-pe compile_commands.json + @rm -vf generate_sbat_var_defs generated_sbat_var_defs.h @rm -vf Cryptlib/*.[oa] Cryptlib/*/*.[oa] @if [ -d .git ] ; then git clean -f -d -e 'Cryptlib/OpenSSL/*'; fi @@ -361,7 +384,7 @@ clean-cryptlib-objs: clean: clean-shim-objs clean-fuzz-objs clean-test-objs clean-gnu-efi clean-openssl-objs clean-cryptlib-objs clean-lib-objs -GITTAG = $(VERSION) +GITTAG = $(shell echo $(VERSION) | sed 's/~/-/g') test-archive: @./make-archive $(if $(call get-config,shim.origin),--origin "$(call get-config,shim.origin)") --test "$(VERSION)" diff --git a/MokManager.c b/MokManager.c index ffcd6a6..52f5c0a 100644 --- a/MokManager.c +++ b/MokManager.c @@ -142,17 +142,14 @@ static UINT32 count_keys(void *Data, UINTN DataSize) void *end = Data + DataSize; while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { - /* Use ptr arithmetics to ensure bounded access. Do not allow 0 - * SignatureListSize that will cause endless loop. */ - if ((void *)(CertList + 1) > end - || CertList->SignatureListSize == 0) { + /* Use ptr arithmetics to ensure bounded access. */ + if ((void *)(CertList + 1) > end) { console_notify (L"Invalid MOK detected! Ignoring MOK List."); return 0; } - if (CertList->SignatureListSize == 0 || - CertList->SignatureListSize <= CertList->SignatureSize) { + if (CertList->SignatureListSize <= CertList->SignatureSize) { console_errorbox(L"Corrupted signature list"); return 0; } diff --git a/MokVars.txt b/MokVars.txt index baf8db9..6ad8ce7 100644 --- a/MokVars.txt +++ b/MokVars.txt @@ -63,28 +63,51 @@ State variables: MokList: A list of authorized keys and hashes. An EFI_SIGNATURE_LIST as described in the UEFI specification. BS,NV -MokListRT: A copy of MokList made available to the kernel at runtime. RT +MokListRT: A copy of MokList made available to the kernel at runtime. BS,RT MokListX: A list of forbidden keys and hashes. An EFI_SIGNATURE_LIST as described in the UEFI specification. BS,NV -MokListXRT: A copy of MokListX made available to the kernel at runtime. RT +MokListXRT: A copy of MokListX made available to the kernel at runtime. BS,RT MokSBState: An 8-bit unsigned integer. If 1, shim will switch to insecure mode. BS,NV +MokSBStateRT: A copy of MokSBState made available to the kernel at runtime. +This allows the OS to query the shim secure mode setting for its own +verification purposes. BS,RT + MokDBState: An 8-bit unsigned integer. If 1, shim will not use db for verification. BS,NV -MokIgnoreDB: An 8-bit unsigned integer. This allows the OS to query whether -or not to import DB certs for its own verification purposes. +MokIgnoreDB: A copy of MokDBState made available to the kernel at runtime. +This allows the OS to query whether or not to import DB certs for its own +verification purposes. BS,RT MokPWStore: A SHA-256 representation of the password set by the user via MokPW. The user will be prompted to enter this password in order -to interact with MokManager. +to interact with MokManager. BS,NV MokListTrusted: An 8-bit unsigned integer. If 1, it signifies to Linux to trust CA keys in the MokList. BS,NV MokListTrustedRT: A copy of MokListTrusted made available to the kernel -at runtime. RT +at runtime. BS,RT + +HSIStatus: Status of various security features: + heap-is-executable: 0: heap allocations are not executable by default + 1: heap allocations are executable + stack-is-executable: 0: UEFI stack is not executable + 1: UEFI stack is executable + ro-sections-are-writable: 0: read-only sections are not writable + 1: read-only sections are writable + has-memory-attribute-protocol: 0: platform does not provide the EFI Memory Attribute Protocol + 1: platform does provide the EFI Memory Attribute Protocol + has-dxe-services-table: 0: platform does not provide the DXE Services Table + 1: platform does provide the DXE Services Table + has-get-memory-space-descriptor: 0: platform's DST does not populate GetMemorySpaceDescriptor + 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/README.tpm b/README.tpm index 9e830b7..e768dc0 100644 --- a/README.tpm +++ b/README.tpm @@ -13,14 +13,20 @@ PCR7: - MokListX - the Mok denylist, logged as "MokListX" - vendor_dbx - shim's built-in vendor denylist, logged as "dbx" - DB - the system allowlist, logged as "db" - - vendor_db - shim's built-in vendor allowlist, logged as "db" - - MokList the Mok allowlist, logged as "MokList" + - vendor_db - shim's built-in vendor allowlist, logged as "vendor_db" + - MokListRT the runtime Mok allowlist, logged as "MokListRT" - vendor_cert - shim's built-in vendor allowlist, logged as "Shim" - shim_cert - shim's build-time generated allowlist, logged as "Shim" - MokSBState will be extended into PCR7 if it is set, logged as "MokSBState". - SBAT will be extended into PCR7 if it is set, logged as "SBAT" +Note: In the past this document called out that vendor_db was logged as + "db", when in fact the code didn't do that. Since changing the code + risks breaking recorded logs, the documentation is update to reflect + reality. vendor_dbx is in fact logged as "dbx". + + PCR8: - If you're using the grub2 TPM patchset we cary in Fedora, the kernel command line and all grub commands (including all of grub.cfg that gets run) are diff --git a/SBAT.md b/SBAT.md index 998f753..81e27aa 100644 --- a/SBAT.md +++ b/SBAT.md @@ -170,7 +170,7 @@ specific product's component, vendors may ask for a product-specific generation number to be published for one of their product's components. This avoids triggering an industry wide re-publishing of otherwise safe components. -In the example above, 1 is sbat's minimum global generation number. +In the example above, 1 in `sbat,1` is sbat's minimum global generation number. A **product-specific minimum generation number** only applies to the instance of that component that is signed with that product name. Another product's @@ -184,7 +184,8 @@ entire industry that uses that component re-release, just that product's minimum generation number would be incremented and that product's component re-released along with a UEFI variable update specifying that requirement. -In the example above, 1 is grub.acme's product-specific minimum generation number. +In the example above, 1 in `grub.acme,1` is grub.acme's product-specific minimum +generation number. The global and product-specific generation number name spaces are not tied to each other. The global number is managed externally, and the vast majority of @@ -213,68 +214,92 @@ product. Setting a product-specific generation number for such an event eliminates the need for other vendors to have to re-release the binaries for their products with an incremented global number. -However, once the global number is bumped for the next upstream CVE fix there -will be no further need to carry that product-specific generation number. -Satisfying the check of the global number will also exclude any of the older -product-specific binaries. +Both generation numbers should only ever go up; they should never be reset. -For example: There is a global CVE disclosure and all vendors coordinate to -release fixed components on the disclosure date. This release bumps the global -generation number for GRUB to 4. +#### Example: a vendor forking a global project -SBAT revocation data would then require a GRUB with a global generation number -of 4. +Let's imagine a fictional company named "Vendor C" having an active fork of the +upstream GNU GRUB2. Therefore, Vendor C provides their own product-specific +generation number. This is happening at the point in time, when the upstream +product's entry starts with `grub,3`, hence why Vendor C's product ships with +entries similar to: + +``` +sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md +grub,3,Free Software Foundation,[...] +grub.vendorc,1,Vendor C,[...] +``` + +Suddenly there is a global CVE disclosure and all vendors coordinate +to release fixed components on the disclosure date. This release bumps the +global generation number for GRUB from 3 to 4. Vendor C's product's binaries +are now shipped with the entries: + +``` +sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md +grub,4,Free Software Foundation,[...] +grub.vendorc,1,Vendor C,[...] +``` + +After this, the UEFI SBAT revocation variable (named *SbatLevel*) would be +updated to raise the mandatory minimal global generation number for GRUB to 4. However, Vendor C mis-merges the patches into one of their products and does not become aware of the fact that this mis-merge created an additional -vulnerability until after they have published a signed binary in that, -vulnerable, state. +vulnerability until after they have published a signed binary in that vulnerable +state. Vendor C's GRUB binary can now be abused to compromise their systems. -Vendor C's GRUB binary can now be used to compromise anyone's system. +To remedy this, Vendor C will release a security-fixed binary with the same +global generation number and an updated product-specific generation number (set +to 2): -To remedy this, Vendor C will release a fixed binary with the same global -generation number and the product-specific generation number set to 1. +``` +sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md +grub,4,Free Software Foundation,[...] +grub.vendorc,2,Vendor C,[...] +``` -SBAT revocation data would then require a GRUB with a global generation number -of 4, as well as a product-specific generation number of 1 for the product that -had the vulnerable binary. +Again, in the perfect scenario, to provide the perfect security, the UEFI SBAT +revocation variable would then be set, so that GRUB with a global generation +number of only 4 or higher would be able to be booted, as well as Vendor C's +products with their number of only 2 or higher. See for yourself, how this looks +like, in the [SbatLevel_Variable.txt](./SbatLevel_Variable.txt) file. -If and when there is another upstream fix for a CVE that would bump the global -number, this product-specific number can be dropped from the UEFI revocation -variable. +If and when there is another upstream fix for a CVE that would bump the GRUB +global number to 5, this product-specific number can be dropped from the UEFI +*SbatLevel* variable (because the binaries starting with upstream's `grub,4` +entry would get denylisted anyway), but with the current consensus it's +important to keep the product-specific number shipped with the product's binary, +like in the case of Vendor C: -If this same Vendor C has a similar event after the global number is -incremented, they would again set their product-specific or **version-specific -number** to 1. If they have a second event on the same component, they would -set their product-specific or version-specific number to 2. +``` +sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md +grub,5,Free Software Foundation,[...] +grub.vendorc,2,Vendor C,[...] +``` -In such an event, a vendor would set the product-specific or version-specific -generation number based on whether the mis-merge occurred in all of their -branches or in just a subset of them. The goal is generally to limit end -customer impact with as few re-releases as possible, while not creating an -unnecessarily large UEFI revocation variable payload. +The goal is generally to limit end user impact with as few +re-releases as possible, while not creating an unnecessarily large UEFI +revocation variable payload. -| | prior to
disclosure\* | after
disclosure | after Vendor C's
first update | after Vendor C's
second update | after next global
disclosure | -|--------------------------------------------------------------------------------------|--------------------------|---------------------|----------------------------------|-----------------------------------|---------------------------------| -| GRUB global
generation number in
artifacts .sbat section | 3 | 4 | 4 | 4 | 5 | -| Vendor C's product-specific
generation number in artifact's
.sbat section | 1 | 1 | 2 | 3 | 1 | -| GRUB global
generation number in
UEFI SBAT revocation variable | 3 | 4 | 4 | 4 | 5 | -| Vendor C's product-specific
generation number in
UEFI SBAT revocation variable | not set | not set | 2 | 3 | not set | +| | prior to GRUB
first disclosure\* | after GRUB
first disclosure\* | after Vendor C's
first update | after Vendor C's
second update | after GRUB
second disclosure\* | +|--------------------------------------------------------------------------------------|-------------------------------------|----------------------------------|----------------------------------|-----------------------------------|-----------------------------------| +| GRUB global
generation number in
artifacts .sbat section | 3 | 4 | 4 | 4 | 5 | +| Vendor C's product
generation number in
artifact's .sbat section | 1 | 1 | 2 | 3 | 3 | +| GRUB global
generation number in
UEFI *SbatLevel* variable | 3 | 4 | 4 | 4 | 5 | +| Vendor C's product
generation number in
UEFI *SbatLevel* variable | not set | not set | 2 | 3 | not set | \* A disclosure is the event/date where a CVE and fixes for it are made public. -The product-specific generation number does not reset and continues to -monotonically increase over the course of these non-global events. Continuity of more -specific generation numbers must be maintained in this way in order to satisfy -checks against older revocation data. +The product-specific generation number does not reset and continues to increase +over the course of these non-global events. Continuity of more specific +generation numbers must be maintained in this way in order to satisfy checks +against older revocation data. -The variable payload will be stored publicly in the shim source base and -identify the global generation associated with a product or version-specific -one. The payload is also built into shim to additionally limit exposure. - -At this time of writing, all version-numbers are set to 1. Presumably at some point, -updated numbers will be published on the respective websites of the associated vendors -and components. +The UEFI *SbatLevel* variable payload will be stored publicly in the shim source +base and identify the global generation associated with a product or +version-specific one. The payload is also built into shim to additionally limit +exposure. #### Retiring Signed Releases diff --git a/SbatLevel_Variable.txt b/SbatLevel_Variable.txt index 42a388e..7afdcd0 100644 --- a/SbatLevel_Variable.txt +++ b/SbatLevel_Variable.txt @@ -1,6 +1,15 @@ -In order to apply SBAT based revocations on systems that will never -run shim, code running in boot services context needs to set the -following variable: +This file is the single source for SbatLevel revocations the format +follows the variable payload and should not have any leading or +trailing whitespace on the same line. + +Short descriptions of the revocations as well as CVE assignments (when +available) should be provided when an entry is added. + +On systems that run shim, shim will manage these revocations. Sytems +that never run shim, primarily Windows, but this applies to any OS +that supports UEFI Secure Boot under the UEFI CA without shim can +apply SBAT based revocations by setting the following variable +from code running in boot services context. Name: SbatLevel Attributes: (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS) @@ -97,12 +106,22 @@ shim,4 grub,3 grub.debian,4 -Since http boot shim CVE is considerably more serious than then GRUB -ntfs CVEs shim is delivering the shim revocation without the updated -GRUB revocation as a latest payload. -To revoke both the impacted shim and impacted GRUB binaries: +Revocations for: + - January 2024 shim CVEs + - October 2023 grub CVEs + - Debian/Ubuntu (peimage) CVE-2024-2312 -sbat,1,2024 +sbat,1,2024040900 shim,4 grub,4 +grub.peimage,2 + + +Revocations for: + - Februady 2025 GRUB CVEs + +sbat,1,2025021800 +shim,4 +grub,5 + diff --git a/commit b/commit new file mode 100644 index 0000000..ef14204 --- /dev/null +++ b/commit @@ -0,0 +1 @@ +18d98bfb34be583a5fe2987542e4b15e0db9cb61 \ No newline at end of file diff --git a/dp.c b/dp.c new file mode 100644 index 0000000..3fc46f8 --- /dev/null +++ b/dp.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * dp.c - device path helpers + * Copyright Peter Jones + */ + +#include "shim.h" + +int +is_removable_media_path(EFI_LOADED_IMAGE *li) +{ + unsigned int pathlen = 0; + CHAR16 *bootpath = NULL; + int ret = 0; + + bootpath = DevicePathToStr(li->FilePath); + + /* Check the beginning of the string and the end, to avoid + * caring about which arch this is. */ + /* I really don't know why, but sometimes bootpath gives us + * L"\\EFI\\BOOT\\/BOOTX64.EFI". So just handle that here... + */ + if (StrnCaseCmp(bootpath, L"\\EFI\\BOOT\\BOOT", 14) && + StrnCaseCmp(bootpath, L"\\EFI\\BOOT\\/BOOT", 15) && + StrnCaseCmp(bootpath, L"EFI\\BOOT\\BOOT", 13) && + StrnCaseCmp(bootpath, L"EFI\\BOOT\\/BOOT", 14)) + goto error; + + pathlen = StrLen(bootpath); + if (pathlen < 5 || StrCaseCmp(bootpath + pathlen - 4, L".EFI")) + goto error; + + ret = 1; + +error: + if (bootpath) + FreePool(bootpath); + + return ret; +} + + +// vim:fenc=utf-8:tw=75:noet diff --git a/errlog.c b/errlog.c index 3c5e0af..b43a4bc 100644 --- a/errlog.c +++ b/errlog.c @@ -99,4 +99,185 @@ ClearErrors(VOID) errs = NULL; } +static size_t +format_error_log(UINT8 *dest, size_t dest_sz) +{ + size_t err_log_sz = 0; + size_t pos = 0; + + for (UINTN i = 0; i < nerrs; i++) + err_log_sz += StrSize(errs[i]); + + if (!dest || dest_sz < err_log_sz) + return err_log_sz; + + ZeroMem(dest, err_log_sz); + for (UINTN i = 0; i < nerrs; i++) { + UINTN sz = StrSize(errs[i]); + CopyMem(&dest[pos], errs[i], sz); + pos += sz; + } + + return err_log_sz; +} + +static UINT8 *debug_log = NULL; +static size_t debug_log_sz = 0; +static size_t debug_log_alloc = 0; + +UINTN EFIAPI +log_debug_print(const CHAR16 *fmt, ...) +{ + ms_va_list args; + CHAR16 *buf; + size_t buf_sz; + UINTN ret = 0; + + ms_va_start(args, fmt); + buf = VPoolPrint(fmt, args); + if (!buf) + return 0; + ms_va_end(args); + + ret = StrLen(buf); + buf_sz = StrSize(buf); + if (debug_log_sz + buf_sz > debug_log_alloc) { + size_t new_alloc_sz = debug_log_alloc; + CHAR16 *new_debug_log; + + new_alloc_sz += buf_sz; + new_alloc_sz = ALIGN_UP(new_alloc_sz, EFI_PAGE_SIZE); + + new_debug_log = ReallocatePool(debug_log, debug_log_alloc, new_alloc_sz); + if (!new_debug_log) + return 0; + debug_log = (UINT8 *)new_debug_log; + debug_log_alloc = new_alloc_sz; + } + + CopyMem(&debug_log[debug_log_sz], buf, buf_sz); + debug_log_sz += buf_sz; + FreePool(buf); + return ret; +} + +static size_t +format_debug_log(UINT8 *dest, size_t dest_sz) +{ + if (!dest || dest_sz < debug_log_sz) + return debug_log_sz; + + ZeroMem(dest, debug_log_sz); + CopyMem(dest, debug_log, debug_log_sz); + return debug_log_sz; +} + +void +replace_config_table(EFI_CONFIGURATION_TABLE *CT, EFI_PHYSICAL_ADDRESS new_table, UINTN new_table_pages) +{ + EFI_GUID bogus_guid = { 0x29f2f0db, 0xd025, 0x4aa6, { 0x99, 0x58, 0xa0, 0x21, 0x8b, 0x1d, 0xec, 0x0e }}; + EFI_STATUS efi_status; + + if (CT) { + CopyMem(&CT->VendorGuid, &bogus_guid, sizeof(bogus_guid)); + if (CT->VendorTable && + CT->VendorTable == (void *)(uintptr_t)mok_config_table) { + BS->FreePages(mok_config_table, mok_config_table_pages); + CT->VendorTable = NULL; + } + } + + efi_status = BS->InstallConfigurationTable(&MOK_VARIABLE_STORE, + (void *)(uintptr_t)new_table); + if (EFI_ERROR(efi_status)) { + console_print(L"Could not re-install MoK configuration table: %r\n", efi_status); + } else { + mok_config_table = new_table; + mok_config_table_pages = new_table_pages; + } +} + +void +save_logs(void) +{ + struct mok_variable_config_entry *cfg_table = NULL; + struct mok_variable_config_entry *new_table = NULL; + struct mok_variable_config_entry *entry = NULL; + EFI_PHYSICAL_ADDRESS physaddr = 0; + UINTN new_table_pages = 0; + size_t new_table_sz; + UINTN pos = 0; + EFI_STATUS efi_status; + size_t errlog_sz, dbglog_sz; + + errlog_sz = format_error_log(NULL, 0); + dbglog_sz = format_debug_log(NULL, 0); + + if (errlog_sz == 0 && dbglog_sz == 0) { + console_print(L"No console or debug log?!?!?\n"); + return; + } + + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *CT; + CT = &ST->ConfigurationTable[i]; + + if (CompareGuid(&MOK_VARIABLE_STORE, &CT->VendorGuid) == 0) { + cfg_table = CT->VendorTable; + break; + } + CT = NULL; + } + + entry = cfg_table; + while (entry && entry->name[0] != 0) { + size_t entry_sz; + entry = (struct mok_variable_config_entry *)((uintptr_t)cfg_table + pos); + + if (entry->name[0] != 0) { + entry_sz = sizeof(*entry); + entry_sz += entry->data_size; + pos += entry_sz; + } + } + + new_table_sz = pos + + (errlog_sz ? sizeof(*entry) + errlog_sz : 0) + + (dbglog_sz ? sizeof(*entry) + dbglog_sz : 0) + + sizeof(*entry); + new_table = NULL; + new_table_pages = ALIGN_UP(new_table_sz + 4*EFI_PAGE_SIZE, EFI_PAGE_SIZE) / EFI_PAGE_SIZE; + efi_status = BS->AllocatePages(AllocateAnyPages, EfiRuntimeServicesData, new_table_pages, &physaddr); + if (EFI_ERROR(efi_status)) { + perror(L"Couldn't allocate %llu pages\n", new_table_pages); + return; + } + new_table = (void *)(uintptr_t)physaddr; + if (!new_table) + return; + ZeroMem(new_table, new_table_pages * EFI_PAGE_SIZE); + CopyMem(new_table, cfg_table, pos); + + entry = (struct mok_variable_config_entry *)((uintptr_t)new_table + pos); + if (errlog_sz) { + strcpy(entry->name, "shim-err.txt"); + entry->data_size = errlog_sz; + format_error_log(&entry->data[0], errlog_sz); + + pos += sizeof(*entry) + errlog_sz; + entry = (struct mok_variable_config_entry *)((uintptr_t)new_table + pos); + } + if (dbglog_sz) { + strcpy(entry->name, "shim-dbg.txt"); + entry->data_size = dbglog_sz; + format_debug_log(&entry->data[0], dbglog_sz); + + pos += sizeof(*entry) + dbglog_sz; + + entry = (struct mok_variable_config_entry *)((uintptr_t)new_table + pos); + } + + replace_config_table((EFI_CONFIGURATION_TABLE *)cfg_table, physaddr, new_table_pages); +} + // vim:fenc=utf-8:tw=75 diff --git a/fallback.c b/fallback.c index 600cc7a..86ebe23 100644 --- a/fallback.c +++ b/fallback.c @@ -94,89 +94,6 @@ FindSubDevicePath(EFI_DEVICE_PATH *In, UINT8 Type, UINT8 SubType, return EFI_NOT_FOUND; } -static EFI_STATUS -get_file_size(EFI_FILE_HANDLE fh, UINTN *retsize) -{ - EFI_STATUS efi_status; - void *buffer = NULL; - UINTN bs = 0; - - /* The API here is "Call it once with bs=0, it fills in bs, - * then allocate a buffer and ask again to get it filled. */ - efi_status = fh->GetInfo(fh, &EFI_FILE_INFO_GUID, &bs, NULL); - if (EFI_ERROR(efi_status) && efi_status != EFI_BUFFER_TOO_SMALL) - return efi_status; - if (bs == 0) - return EFI_SUCCESS; - - buffer = AllocateZeroPool(bs); - if (!buffer) { - console_print(L"Could not allocate memory\n"); - return EFI_OUT_OF_RESOURCES; - } - efi_status = fh->GetInfo(fh, &EFI_FILE_INFO_GUID, &bs, buffer); - /* This checks *either* the error from the first GetInfo, if it isn't - * the EFI_BUFFER_TOO_SMALL we're expecting, or the second GetInfo - * call in *any* case. */ - if (EFI_ERROR(efi_status)) { - console_print(L"Could not get file info: %r\n", efi_status); - if (buffer) - FreePool(buffer); - return efi_status; - } - EFI_FILE_INFO *fi = buffer; - *retsize = fi->FileSize; - FreePool(buffer); - return EFI_SUCCESS; -} - -EFI_STATUS -read_file(EFI_FILE_HANDLE fh, CHAR16 *fullpath, CHAR16 **buffer, UINT64 *bs) -{ - EFI_FILE_HANDLE fh2; - EFI_STATUS efi_status; - - efi_status = fh->Open(fh, &fh2, fullpath, EFI_FILE_READ_ONLY, 0); - if (EFI_ERROR(efi_status)) { - console_print(L"Couldn't open \"%s\": %r\n", fullpath, efi_status); - return efi_status; - } - - UINTN len = 0; - CHAR16 *b = NULL; - efi_status = get_file_size(fh2, &len); - if (EFI_ERROR(efi_status)) { - console_print(L"Could not get file size for \"%s\": %r\n", - fullpath, efi_status); - fh2->Close(fh2); - return efi_status; - } - - if (len > 1024 * PAGE_SIZE) { - fh2->Close(fh2); - return EFI_BAD_BUFFER_SIZE; - } - - b = AllocateZeroPool(len + 2); - if (!b) { - console_print(L"Could not allocate memory\n"); - fh2->Close(fh2); - return EFI_OUT_OF_RESOURCES; - } - - efi_status = fh->Read(fh, &len, b); - if (EFI_ERROR(efi_status)) { - FreePool(b); - fh2->Close(fh2); - console_print(L"Could not read file: %r\n", efi_status); - return efi_status; - } - *buffer = b; - *bs = len; - fh2->Close(fh2); - return EFI_SUCCESS; -} - EFI_STATUS make_full_path(CHAR16 *dirname, CHAR16 *filename, CHAR16 **out, UINT64 *outlen) { @@ -202,8 +119,8 @@ make_full_path(CHAR16 *dirname, CHAR16 *filename, CHAR16 **out, UINT64 *outlen) return EFI_SUCCESS; } -CHAR16 *bootorder = NULL; -int nbootorder = 0; +UINT16 *bootorder = NULL; +UINTN nbootorder = 0; EFI_DEVICE_PATH *first_new_option = NULL; VOID *first_new_option_args = NULL; @@ -211,7 +128,8 @@ UINTN first_new_option_size = 0; EFI_STATUS add_boot_option(EFI_DEVICE_PATH *hddp, EFI_DEVICE_PATH *fulldp, - CHAR16 *filename, CHAR16 *label, CHAR16 *arguments) + CHAR16 *filename, CHAR16 *label, CHAR16 *arguments, + UINT16 **newbootentries, UINTN *nnewbootentries) { static int i = 0; CHAR16 varname[] = L"Boot0000"; @@ -226,9 +144,11 @@ add_boot_option(EFI_DEVICE_PATH *hddp, EFI_DEVICE_PATH *fulldp, void *var = LibGetVariable(varname, &GV_GUID); if (!var) { + int arg_size = StrLen(arguments) ? StrLen(arguments) * sizeof (CHAR16) + + sizeof (CHAR16) : 0; int size = sizeof(UINT32) + sizeof (UINT16) + StrLen(label)*2 + 2 + DevicePathSize(hddp) + - StrLen(arguments) * 2; + arg_size; CHAR8 *data, *cursor; cursor = data = AllocateZeroPool(size + 2); @@ -252,7 +172,7 @@ add_boot_option(EFI_DEVICE_PATH *hddp, EFI_DEVICE_PATH *fulldp, if (!first_new_option) { first_new_option = DuplicateDevicePath(fulldp); first_new_option_args = StrDuplicate(arguments); - first_new_option_size = StrLen(arguments) * sizeof (CHAR16); + first_new_option_size = arg_size; } efi_status = RT->SetVariable(varname, &GV_GUID, @@ -269,24 +189,21 @@ add_boot_option(EFI_DEVICE_PATH *hddp, EFI_DEVICE_PATH *fulldp, return efi_status; } - CHAR16 *newbootorder = AllocateZeroPool(sizeof (CHAR16) - * (nbootorder + 1)); + UINT16 *newbootorder = AllocateZeroPool(sizeof (UINT16) * (*nnewbootentries + 1)); if (!newbootorder) return EFI_OUT_OF_RESOURCES; - int j = 0; - newbootorder[0] = i & 0xffff; - if (nbootorder) { - for (j = 0; j < nbootorder; j++) - newbootorder[j+1] = bootorder[j]; - FreePool(bootorder); - } - bootorder = newbootorder; - nbootorder += 1; - VerbosePrint(L"nbootorder: %d\nBootOrder: ", - nbootorder); - for (j = 0 ; j < nbootorder ; j++) - VerbosePrintUnprefixed(L"%04x ", bootorder[j]); + UINTN j = 0; + CopyMem(newbootorder, *newbootentries, sizeof (UINT16) * (*nnewbootentries)); + newbootorder[*nnewbootentries] = i & 0xffff; + if (*newbootentries) + FreePool(*newbootentries); + *newbootentries = newbootorder; + *nnewbootentries += 1; + VerbosePrint(L"nnewbootentries: %d\nnewbootentries: ", + *nnewbootentries); + for (j = 0 ; j < *nnewbootentries ; j++) + VerbosePrintUnprefixed(L"%04x ", (*newbootentries)[j]); VerbosePrintUnprefixed(L"\n"); return EFI_SUCCESS; @@ -400,9 +317,11 @@ find_boot_option(EFI_DEVICE_PATH *dp, EFI_DEVICE_PATH *fulldp, UINT16 *optnum) { unsigned int label_size = StrLen(label)*2 + 2; + int arg_size = StrLen(arguments) ? StrLen(arguments) * sizeof (CHAR16) + + sizeof (CHAR16) : 0; unsigned int size = sizeof(UINT32) + sizeof (UINT16) + label_size + DevicePathSize(dp) + - StrLen(arguments) * 2; + arg_size; CHAR8 *data = AllocateZeroPool(size + 2); if (!data) @@ -486,7 +405,7 @@ find_boot_option(EFI_DEVICE_PATH *dp, EFI_DEVICE_PATH *fulldp, if (!first_new_option) { first_new_option = DuplicateDevicePath(fulldp); first_new_option_args = StrDuplicate(arguments); - first_new_option_size = StrLen(arguments) * sizeof (CHAR16); + first_new_option_size = arg_size; } *optnum = xtoi(varname + 4); @@ -503,13 +422,13 @@ find_boot_option(EFI_DEVICE_PATH *dp, EFI_DEVICE_PATH *fulldp, EFI_STATUS set_boot_order(void) { - CHAR16 *oldbootorder; + UINT16 *oldbootorder; UINTN size; oldbootorder = LibGetVariableAndSize(L"BootOrder", &GV_GUID, &size); if (oldbootorder) { - int i; - nbootorder = size / sizeof (CHAR16); + UINTN i; + nbootorder = size / sizeof (UINT16); bootorder = oldbootorder; VerbosePrint(L"Original nbootorder: %d\nOriginal BootOrder: ", @@ -523,23 +442,41 @@ set_boot_order(void) } EFI_STATUS -update_boot_order(void) +update_boot_order(UINT16 *newbootentries, UINTN nnewbootentries) { UINTN size; UINTN len = 0; - CHAR16 *newbootorder = NULL; + UINT16 *newbootorder = NULL; EFI_STATUS efi_status; + UINTN i; - size = nbootorder * sizeof(CHAR16); + VerbosePrint(L"old boot order: "); + for (i = 0; i < nbootorder; i++) + VerbosePrintUnprefixed(L"%04x ", bootorder[i]); + VerbosePrintUnprefixed(L"\n"); + VerbosePrint(L"new boot entries: "); + for (i = 0; i < nnewbootentries; i++) + VerbosePrintUnprefixed(L"%04x ", newbootentries[i]); + VerbosePrintUnprefixed(L"\n"); + + size = nbootorder * sizeof(UINT16) + nnewbootentries * sizeof(UINT16); newbootorder = AllocateZeroPool(size); if (!newbootorder) return EFI_OUT_OF_RESOURCES; - CopyMem(newbootorder, bootorder, size); + for (i = 0 ; i < nnewbootentries; i++) { + newbootorder[i] = newbootentries[i]; + } + CopyMem(&newbootorder[i], bootorder, nbootorder * sizeof(UINT16)); - VerbosePrint(L"nbootorder: %d\nBootOrder: ", size / sizeof (CHAR16)); - UINTN j; - for (j = 0 ; j < size / sizeof (CHAR16); j++) - VerbosePrintUnprefixed(L"%04x ", newbootorder[j]); + if (bootorder) + FreePool(bootorder); + nbootorder = nnewbootentries + nbootorder; + bootorder = newbootorder; + + VerbosePrint(L"updated nbootorder: %d\n", nbootorder); + VerbosePrint(L"updated bootoder: "); + for (i = 0; i < nbootorder; i++) + VerbosePrintUnprefixed(L"%04x ", bootorder[i]); VerbosePrintUnprefixed(L"\n"); efi_status = RT->GetVariable(L"BootOrder", &GV_GUID, NULL, &len, NULL); if (efi_status == EFI_BUFFER_TOO_SMALL) @@ -549,13 +486,13 @@ update_boot_order(void) EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS, - size, newbootorder); - FreePool(newbootorder); + size, bootorder); return efi_status; } EFI_STATUS -add_to_boot_list(CHAR16 *dirname, CHAR16 *filename, CHAR16 *label, CHAR16 *arguments) +add_to_boot_list(CHAR16 *dirname, CHAR16 *filename, CHAR16 *label, CHAR16 *arguments, + UINT16 **newbootentries, UINTN *nnewbootentries) { CHAR16 *fullpath = NULL; UINT64 pathlen = 0; @@ -614,19 +551,19 @@ add_to_boot_list(CHAR16 *dirname, CHAR16 *filename, CHAR16 *label, CHAR16 *argum arguments, &option); if (EFI_ERROR(efi_status)) { add_boot_option(dp, full_device_path, fullpath, label, - arguments); + arguments, newbootentries, nnewbootentries); goto done; } UINT16 bootnum; - CHAR16 *newbootorder; + UINT16 *newbootorder; /* Search for the option in the current bootorder */ for (bootnum = 0; bootnum < nbootorder; bootnum++) if (bootorder[bootnum] == option) break; if (bootnum == nbootorder) { /* Option not found, prepend option and copy the rest */ - newbootorder = AllocateZeroPool(sizeof(CHAR16) + newbootorder = AllocateZeroPool(sizeof(UINT16) * (nbootorder + 1)); if (!newbootorder) { efi_status = EFI_OUT_OF_RESOURCES; @@ -634,30 +571,30 @@ add_to_boot_list(CHAR16 *dirname, CHAR16 *filename, CHAR16 *label, CHAR16 *argum } newbootorder[0] = option; CopyMem(newbootorder + 1, bootorder, - sizeof(CHAR16) * nbootorder); + sizeof(UINT16) * nbootorder); FreePool(bootorder); bootorder = newbootorder; nbootorder += 1; } else { /* Option found, put first and slice the rest */ newbootorder = AllocateZeroPool( - sizeof(CHAR16) * nbootorder); + sizeof(UINT16) * nbootorder); if (!newbootorder) { efi_status = EFI_OUT_OF_RESOURCES; goto done; } newbootorder[0] = option; CopyMem(newbootorder + 1, bootorder, - sizeof(CHAR16) * bootnum); + sizeof(UINT16) * bootnum); CopyMem(newbootorder + 1 + bootnum, bootorder + bootnum + 1, - sizeof(CHAR16) * (nbootorder - bootnum - 1)); + sizeof(UINT16) * (nbootorder - bootnum - 1)); FreePool(bootorder); bootorder = newbootorder; } VerbosePrint(L"New nbootorder: %d\nBootOrder: ", nbootorder); - for (int i = 0 ; i < nbootorder ; i++) + for (UINTN i = 0 ; i < nbootorder ; i++) VerbosePrintUnprefixed(L"%04x ", bootorder[i]); VerbosePrintUnprefixed(L"\n"); @@ -672,7 +609,8 @@ done: } EFI_STATUS -populate_stanza(CHAR16 *dirname, CHAR16 *filename UNUSED, CHAR16 *csv) +populate_stanza(CHAR16 *dirname, CHAR16 *filename UNUSED, CHAR16 *csv, + UINT16 **newbootentries, UINTN *nnewbootentries) { CHAR16 *file = csv; VerbosePrint(L"CSV data: \"%s\"\n", csv); @@ -696,13 +634,14 @@ populate_stanza(CHAR16 *dirname, CHAR16 *filename UNUSED, CHAR16 *csv) /* This one is optional, so don't check if comma2 is 0 */ VerbosePrint(L"arguments: \"%s\"\n", arguments); - add_to_boot_list(dirname, file, label, arguments); + add_to_boot_list(dirname, file, label, arguments, newbootentries, nnewbootentries); return EFI_SUCCESS; } EFI_STATUS -try_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename) +try_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename, + UINT16 **newbootentries, UINTN *nnewbootentries) { CHAR16 *fullpath = NULL; UINT64 pathlen = 0; @@ -751,7 +690,7 @@ try_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename) CHAR16 c = start[l]; start[l] = L'\0'; - populate_stanza(dirname, filename, start); + populate_stanza(dirname, filename, start, newbootentries, nnewbootentries); start[l] = c; start += l; @@ -762,7 +701,8 @@ try_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname, CHAR16 *filename) } EFI_STATUS -find_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname) +find_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname, + UINT16 **newbootentries, UINTN *nnewbootentries) { EFI_STATUS efi_status; void *buffer = NULL; @@ -861,7 +801,8 @@ find_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname) console_print(L"Couldn't open \\EFI\\%s\\%s: %r\n", dirname, bootarchcsv, efi_status); } else { - efi_status = try_boot_csv(fh2, dirname, bootarchcsv); + efi_status = try_boot_csv(fh2, dirname, bootarchcsv, + newbootentries, nnewbootentries); fh2->Close(fh2); if (EFI_ERROR(efi_status)) console_print(L"Could not process \\EFI\\%s\\%s: %r\n", @@ -876,7 +817,8 @@ find_boot_csv(EFI_FILE_HANDLE fh, CHAR16 *dirname) console_print(L"Couldn't open \\EFI\\%s\\%s: %r\n", dirname, bootcsv, efi_status); } else { - efi_status = try_boot_csv(fh2, dirname, bootcsv); + efi_status = try_boot_csv(fh2, dirname, bootcsv, + newbootentries, nnewbootentries); fh2->Close(fh2); if (EFI_ERROR(efi_status)) console_print(L"Could not process \\EFI\\%s\\%s: %r\n", @@ -891,6 +833,8 @@ find_boot_options(EFI_HANDLE device) { EFI_STATUS efi_status; EFI_FILE_IO_INTERFACE *fio = NULL; + UINT16 *newbootentries = NULL; + UINTN nnewbootentries = 0; efi_status = BS->HandleProtocol(device, &FileSystemProtocol, (void **) &fio); @@ -982,7 +926,8 @@ find_boot_options(EFI_HANDLE device) continue; } - efi_status = find_boot_csv(fh3, fi->FileName); + efi_status = find_boot_csv(fh3, fi->FileName, + &newbootentries, &nnewbootentries); fh3->Close(fh3); FreePool(buffer); buffer = NULL; @@ -991,8 +936,8 @@ find_boot_options(EFI_HANDLE device) } while (1); - if (!EFI_ERROR(efi_status) && nbootorder > 0) - efi_status = update_boot_order(); + if (!EFI_ERROR(efi_status) && (nbootorder > 0 || nnewbootentries > 0)) + efi_status = update_boot_order(newbootentries, nnewbootentries); fh2->Close(fh2); fh->Close(fh); diff --git a/fuzz-pe-relocate.c b/fuzz-pe-relocate.c index 1f62234..09d3833 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/generate_sbat_var_defs.c b/generate_sbat_var_defs.c new file mode 100644 index 0000000..1258e1b --- /dev/null +++ b/generate_sbat_var_defs.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent + +/* + * This generates the header files that produce the actual revocation + * string payload. On the one hand this grabs the defintions from the + * human readable SbatLevel_Variable.txt file which is nice. On the other + * hand it's one off c code. + */ + +#include +#include +#include + +typedef struct sbat_revocation sbat_revocation; + +struct sbat_revocation { + int date; + char *revocations; + sbat_revocation *next; +}; + +static sbat_revocation *revlisthead; + +int +readfile(char *SbatLevel_Variable) +{ + FILE *varfilep; + char line[1024]; + int date; + int ret = -1; + + unsigned int revocationsp = 0; + + sbat_revocation *revlistlast = NULL; + sbat_revocation *revlistentry = NULL; + + revlisthead = NULL; + + varfilep = fopen(SbatLevel_Variable, "r"); + if (varfilep == NULL) + return -1; + + while (fgets(line, sizeof(line), varfilep) != NULL) { + if (sscanf(line, "sbat,1,%d\n", &date) && strlen(line) == 18) { + revlistentry = calloc(1, sizeof(sbat_revocation)); + if (revlistentry == NULL) + goto err; + if (revlisthead == NULL) + revlisthead = revlistentry; + else + revlistlast->next = revlistentry; + + revlistlast = revlistentry; + + revlistentry->date = date; + while (line[0] != '\n' && + fgets(line, sizeof(line), varfilep) != NULL) { + char *new = NULL; + new = realloc(revlistentry->revocations, + revocationsp + strlen(line) + 1); + if (new == NULL) { + ret = -1; + goto err; + } + revlistentry->revocations = new; + if (strlen(line) > 1) { + line[strlen(line) - 1] = 0; + sprintf(revlistentry->revocations + + revocationsp, + "%s\\n", line); + revocationsp = + revocationsp + strlen(line) + 2; + } + } + revocationsp = 0; + } + } + + ret = 1; +err: + if (ret < 0 && revlisthead) { + sbat_revocation *rle = revlisthead; + while (rle) { + sbat_revocation *next = rle->next; + if (rle->revocations) + free(rle->revocations); + free(rle); + rle = next; + } + revlisthead = NULL; + } + fclose(varfilep); + return ret; +} + +int +writefile() +{ + int epochfound = 0; + int epochdate = 2021030218; + int latestdate = 0; + + sbat_revocation *revlistentry; + sbat_revocation *latest_revlistentry = NULL; + + revlistentry = revlisthead; + + while (revlistentry != NULL) { + if (revlistentry->date == epochdate) { + printf("#ifndef GEN_SBAT_VAR_DEFS_H_\n" + "#define GEN_SBAT_VAR_DEFS_H_\n" + "#ifndef ENABLE_SHIM_DEVEL\n\n" + "#ifndef SBAT_AUTOMATIC_DATE\n" + "#define SBAT_AUTOMATIC_DATE 2024040900\n" + "#endif /* SBAT_AUTOMATIC_DATE */\n" + "#if SBAT_AUTOMATIC_DATE == %d\n" + "#define SBAT_VAR_AUTOMATIC_REVOCATIONS\n", + revlistentry->date); + epochfound = 1; + } else if (epochfound == 1) { + printf("#elif SBAT_AUTOMATIC_DATE == %d\n" + "#define SBAT_VAR_AUTOMATIC_REVOCATIONS \"%s\"\n", + revlistentry->date, + revlistentry->revocations); + } + if (revlistentry->date > latestdate) { + latest_revlistentry = revlistentry; + latestdate = revlistentry->date; + } + revlistentry = revlistentry->next; + } + + if (epochfound == 0 || !latest_revlistentry) + return -1; + + printf("#else\n" + "#error \"Unknown SBAT_AUTOMATIC_DATE\"\n" + "#endif /* SBAT_AUTOMATIC_DATE == */\n\n" + "#define SBAT_VAR_AUTOMATIC_DATE QUOTEVAL(SBAT_AUTOMATIC_DATE)\n" + "#define SBAT_VAR_AUTOMATIC \\\n" + " SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_AUTOMATIC_DATE \"\\n\" \\\n" + " SBAT_VAR_AUTOMATIC_REVOCATIONS\n\n"); + + printf("#define SBAT_VAR_LATEST_DATE \"%d\"\n" + "#define SBAT_VAR_LATEST_REVOCATIONS \"%s\"\n", + latest_revlistentry->date, + latest_revlistentry->revocations); + + printf("#define SBAT_VAR_LATEST \\\n" + " SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_LATEST_DATE \"\\n\" \\\n" + " SBAT_VAR_LATEST_REVOCATIONS\n\n" + "#endif /* !ENABLE_SHIM_DEVEL */\n" + "#endif /* !GEN_SBAT_VAR_DEFS_H_ */\n"); + + return 0; +} + + +int +main(int argc, char *argv[]) +{ + char SbatLevel_Variable[2048]; + + if (argc == 2) + snprintf(SbatLevel_Variable, 2048, "%s/SbatLevel_Variable.txt", argv[1]); + else + snprintf(SbatLevel_Variable, 2048, "SbatLevel_Variable.txt"); + + if (readfile(SbatLevel_Variable)) + return writefile(); + else + return -1; +} diff --git a/globals.c b/globals.c index b4e80dd..1119712 100644 --- a/globals.c +++ b/globals.c @@ -24,13 +24,18 @@ UINT8 *build_cert; * indicator of how an image has been verified */ verification_method_t verification_method; -int loader_is_participating; + +SHIM_IMAGE_LOADER shim_image_loader_interface; UINT8 user_insecure_mode; +UINTN hsi_status = 0; UINT8 ignore_db; UINT8 trust_mok_list; UINT8 mok_policy = 0; UINT32 verbose = 0; +EFI_PHYSICAL_ADDRESS mok_config_table = 0; +UINTN mok_config_table_pages = 0; + // vim:fenc=utf-8:tw=75:noet diff --git a/gnu-efi/ia32/gnuefi/crt0-efi-ia32.o b/gnu-efi/ia32/gnuefi/crt0-efi-ia32.o deleted file mode 100644 index 8dfe2ab..0000000 Binary files a/gnu-efi/ia32/gnuefi/crt0-efi-ia32.o and /dev/null differ diff --git a/gnu-efi/ia32/gnuefi/libgnuefi.a b/gnu-efi/ia32/gnuefi/libgnuefi.a deleted file mode 100644 index e1a6097..0000000 Binary files a/gnu-efi/ia32/gnuefi/libgnuefi.a and /dev/null differ diff --git a/gnu-efi/ia32/gnuefi/reloc_ia32.o b/gnu-efi/ia32/gnuefi/reloc_ia32.o deleted file mode 100644 index 58fb22b..0000000 Binary files a/gnu-efi/ia32/gnuefi/reloc_ia32.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/boxdraw.o b/gnu-efi/ia32/lib/boxdraw.o deleted file mode 100644 index 9827434..0000000 Binary files a/gnu-efi/ia32/lib/boxdraw.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/cmdline.o b/gnu-efi/ia32/lib/cmdline.o deleted file mode 100644 index ef32059..0000000 Binary files a/gnu-efi/ia32/lib/cmdline.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/console.o b/gnu-efi/ia32/lib/console.o deleted file mode 100644 index 1d3fe24..0000000 Binary files a/gnu-efi/ia32/lib/console.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/crc.o b/gnu-efi/ia32/lib/crc.o deleted file mode 100644 index ec81bf5..0000000 Binary files a/gnu-efi/ia32/lib/crc.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/data.o b/gnu-efi/ia32/lib/data.o deleted file mode 100644 index 59d7aea..0000000 Binary files a/gnu-efi/ia32/lib/data.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/debug.o b/gnu-efi/ia32/lib/debug.o deleted file mode 100644 index 91098aa..0000000 Binary files a/gnu-efi/ia32/lib/debug.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/dpath.o b/gnu-efi/ia32/lib/dpath.o deleted file mode 100644 index 41371ed..0000000 Binary files a/gnu-efi/ia32/lib/dpath.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/error.o b/gnu-efi/ia32/lib/error.o deleted file mode 100644 index c0d875c..0000000 Binary files a/gnu-efi/ia32/lib/error.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/event.o b/gnu-efi/ia32/lib/event.o deleted file mode 100644 index 823f4cd..0000000 Binary files a/gnu-efi/ia32/lib/event.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/exit.o b/gnu-efi/ia32/lib/exit.o deleted file mode 100644 index 689a98b..0000000 Binary files a/gnu-efi/ia32/lib/exit.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/guid.o b/gnu-efi/ia32/lib/guid.o deleted file mode 100644 index b1d3e95..0000000 Binary files a/gnu-efi/ia32/lib/guid.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/hand.o b/gnu-efi/ia32/lib/hand.o deleted file mode 100644 index dae96ec..0000000 Binary files a/gnu-efi/ia32/lib/hand.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/hw.o b/gnu-efi/ia32/lib/hw.o deleted file mode 100644 index 48ef884..0000000 Binary files a/gnu-efi/ia32/lib/hw.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/ia32/initplat.o b/gnu-efi/ia32/lib/ia32/initplat.o deleted file mode 100644 index 5222a6d..0000000 Binary files a/gnu-efi/ia32/lib/ia32/initplat.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/ia32/math.o b/gnu-efi/ia32/lib/ia32/math.o deleted file mode 100644 index 82fd313..0000000 Binary files a/gnu-efi/ia32/lib/ia32/math.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/ia32/setjmp.o b/gnu-efi/ia32/lib/ia32/setjmp.o deleted file mode 100644 index ac75382..0000000 Binary files a/gnu-efi/ia32/lib/ia32/setjmp.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/init.o b/gnu-efi/ia32/lib/init.o deleted file mode 100644 index d785857..0000000 Binary files a/gnu-efi/ia32/lib/init.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/libefi.a b/gnu-efi/ia32/lib/libefi.a deleted file mode 100644 index 78a165b..0000000 Binary files a/gnu-efi/ia32/lib/libefi.a and /dev/null differ diff --git a/gnu-efi/ia32/lib/lock.o b/gnu-efi/ia32/lib/lock.o deleted file mode 100644 index cad0d16..0000000 Binary files a/gnu-efi/ia32/lib/lock.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/misc.o b/gnu-efi/ia32/lib/misc.o deleted file mode 100644 index 860b837..0000000 Binary files a/gnu-efi/ia32/lib/misc.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/pause.o b/gnu-efi/ia32/lib/pause.o deleted file mode 100644 index fec13d5..0000000 Binary files a/gnu-efi/ia32/lib/pause.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/print.o b/gnu-efi/ia32/lib/print.o deleted file mode 100644 index 3c8dd17..0000000 Binary files a/gnu-efi/ia32/lib/print.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/runtime/efirtlib.o b/gnu-efi/ia32/lib/runtime/efirtlib.o deleted file mode 100644 index 80a4d84..0000000 Binary files a/gnu-efi/ia32/lib/runtime/efirtlib.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/runtime/rtdata.o b/gnu-efi/ia32/lib/runtime/rtdata.o deleted file mode 100644 index 920475c..0000000 Binary files a/gnu-efi/ia32/lib/runtime/rtdata.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/runtime/rtlock.o b/gnu-efi/ia32/lib/runtime/rtlock.o deleted file mode 100644 index ba24dd3..0000000 Binary files a/gnu-efi/ia32/lib/runtime/rtlock.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/runtime/rtstr.o b/gnu-efi/ia32/lib/runtime/rtstr.o deleted file mode 100644 index a75652d..0000000 Binary files a/gnu-efi/ia32/lib/runtime/rtstr.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/runtime/vm.o b/gnu-efi/ia32/lib/runtime/vm.o deleted file mode 100644 index 8818308..0000000 Binary files a/gnu-efi/ia32/lib/runtime/vm.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/smbios.o b/gnu-efi/ia32/lib/smbios.o deleted file mode 100644 index 7f406a5..0000000 Binary files a/gnu-efi/ia32/lib/smbios.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/sread.o b/gnu-efi/ia32/lib/sread.o deleted file mode 100644 index 102b665..0000000 Binary files a/gnu-efi/ia32/lib/sread.o and /dev/null differ diff --git a/gnu-efi/ia32/lib/str.o b/gnu-efi/ia32/lib/str.o deleted file mode 100644 index c16826f..0000000 Binary files a/gnu-efi/ia32/lib/str.o and /dev/null differ diff --git a/gnu-efi/inc/efiapi.h b/gnu-efi/inc/efiapi.h index 96e9e4a..9d399a2 100644 --- a/gnu-efi/inc/efiapi.h +++ b/gnu-efi/inc/efiapi.h @@ -971,5 +971,75 @@ typedef struct _EFI_SYSTEM_TABLE { } EFI_SYSTEM_TABLE; -#endif +// +// Not technically EFI, but oh well. +// + +#define EFI_DXE_SERVICES_TABLE_SIGNATURE 0x565245535f455844ULL + +typedef enum { + EFI_GCD_MEMORY_TYPE_NON_EXISTENT, + EFI_GCD_MEMORY_TYPE_RESERVED, + EFI_GCD_MEMORY_TYPE_SYSTEM_MEMORY, + EFI_GCD_MEMORY_TYPE_MEMORY_MAPPED_IO, + EFI_GCD_MEMORY_TYPE_PERSISTENT, + EFI_GCD_MEMORY_TYPE_MORE_RELIABLE, + EFI_GCD_MEMORY_TYPE_MAXIMUM +} EFI_GCD_MEMORY_TYPE_T; + +#define DXE_SERVICES_TABLE_GUID \ + { \ + 0x5ad34ba, 0x6f02, 0x4214, {0x95, 0x2e, 0x4d, 0xa0, 0x39, 0x8e, 0x2b, 0xb9 } \ + } + +struct _EFI_GCD_MEMORY_SPACE_DESCRIPTOR { + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 Length; + UINT64 Capabilities; + UINT64 Attributes; + EFI_GCD_MEMORY_TYPE_T GcdMemoryType; + EFI_HANDLE ImageHandle; + EFI_HANDLE DeviceHandle; +} __attribute__((__packed__)); + +typedef struct _EFI_GCD_MEMORY_SPACE_DESCRIPTOR EFI_GCD_MEMORY_SPACE_DESCRIPTOR; + +typedef +EFI_STATUS +(EFIAPI *GET_MEMORY_SPACE_DESCRIPTOR) ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + OUT EFI_GCD_MEMORY_SPACE_DESCRIPTOR *Desc + ); + +typedef +EFI_STATUS +(EFIAPI *SET_MEMORY_SPACE_ATTRIBUTES) ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN UINT64 Attributes + ); + +typedef struct _EFI_DXE_SERVICES_TABLE { + EFI_TABLE_HEADER Hdr; + VOID *AddMemorySpace; + VOID *AllocateMemorySpace; + VOID *FreeMemorySpace; + VOID *RemoveMemorySpace; + GET_MEMORY_SPACE_DESCRIPTOR GetMemorySpaceDescriptor; + SET_MEMORY_SPACE_ATTRIBUTES SetMemorySpaceAttributes; + VOID *GetMemorySpaceMap; + VOID *AddIoSpace; + VOID *AllocateIoSpace; + VOID *FreeIoSpace; + VOID *RemoveIoSpace; + VOID *GetIoSpaceDescriptor; + VOID *GetIoSpaceMap; + VOID *Dispatch; + VOID *Schedule; + VOID *Trust; + VOID *ProcessFirmwareVolume; + VOID *SetMemorySpaceCapabilities; +} EFI_DXE_SERVICES_TABLE; + +#endif diff --git a/gnu-efi/inc/efierr.h b/gnu-efi/inc/efierr.h index 5a66e1a..ac9ef7b 100644 --- a/gnu-efi/inc/efierr.h +++ b/gnu-efi/inc/efierr.h @@ -57,12 +57,17 @@ Revision History #define EFI_END_OF_FILE EFIERR(31) #define EFI_INVALID_LANGUAGE EFIERR(32) #define EFI_COMPROMISED_DATA EFIERR(33) +#define EFI_IP_ADDRESS_CONFLICT EFIERR(34) +#define EFI_HTTP_ERROR EFIERR(35) #define EFI_WARN_UNKOWN_GLYPH EFIWARN(1) #define EFI_WARN_UNKNOWN_GLYPH EFIWARN(1) #define EFI_WARN_DELETE_FAILURE EFIWARN(2) #define EFI_WARN_WRITE_FAILURE EFIWARN(3) #define EFI_WARN_BUFFER_TOO_SMALL EFIWARN(4) +#define EFI_WARN_STALE_DATA EFIWARN(5) +#define EFI_WARN_FILE_SYSTEM EFIWARN(6) +#define EFI_WARN_RESET_REQUIRED EFIWARN(7) #endif diff --git a/gnu-efi/inc/efilib.h b/gnu-efi/inc/efilib.h index af47019..a831666 100644 --- a/gnu-efi/inc/efilib.h +++ b/gnu-efi/inc/efilib.h @@ -79,10 +79,16 @@ extern EFI_GUID gEfiDiskIoProtocolGuid; #define DiskIoProtocol gEfiDiskIoProtocolGuid extern EFI_GUID gEfiDiskIo2ProtocolGuid; #define DiskIo2Protocol gEfiDiskIo2ProtocolGuid +extern EFI_GUID gEfiDxeServicesTableGuid; +#define DxeServicesTable gEfiDxeServicesTableGuid extern EFI_GUID gEfiSimpleFileSystemProtocolGuid; #define FileSystemProtocol gEfiSimpleFileSystemProtocolGuid +extern EFI_GUID gEfiLoadedImageDevicePathProtocolGuid; +#define LoadedImageDevicePathProtocol gEfiLoadedImageDevicePathProtocolGuid extern EFI_GUID gEfiLoadFileProtocolGuid; #define LoadFileProtocol gEfiLoadFileProtocolGuid +extern EFI_GUID gEfiLoadFile2ProtocolGuid; +#define LoadFile2Protocol gEfiLoadFile2ProtocolGuid extern EFI_GUID gEfiDeviceIoProtocolGuid; #define DeviceIoProtocol gEfiDeviceIoProtocolGuid extern EFI_GUID VariableStoreProtocol; diff --git a/gnu-efi/inc/efiprot.h b/gnu-efi/inc/efiprot.h index 4013ab2..db29157 100644 --- a/gnu-efi/inc/efiprot.h +++ b/gnu-efi/inc/efiprot.h @@ -554,6 +554,69 @@ typedef struct _EFI_LOAD_FILE_PROTOCOL { typedef struct _EFI_LOAD_FILE_PROTOCOL _EFI_LOAD_FILE_INTERFACE; typedef EFI_LOAD_FILE_PROTOCOL EFI_LOAD_FILE_INTERFACE; + +// +// Load File 2 Protocol +// + +#define EFI_LOAD_FILE2_PROTOCOL_GUID \ + { \ + 0x4006c0c1, 0xfcb3, 0x403e, {0x99, 0x6d, 0x4a, 0x6c, 0x87, 0x24, 0xe0, 0x6d } \ + } + +/// +/// Protocol Guid defined by UEFI2.1. +/// +#define LOAD_FILE2_PROTOCOL EFI_LOAD_FILE2_PROTOCOL_GUID + +typedef struct _EFI_LOAD_FILE2_PROTOCOL EFI_LOAD_FILE2_PROTOCOL; + +/** + Causes the driver to load a specified file. + + @param This Protocol instance pointer. + @param FilePath The device specific path of the file to load. + @param BootPolicy Should always be FALSE. + @param BufferSize On input the size of Buffer in bytes. On output with a return + code of EFI_SUCCESS, the amount of data transferred to + Buffer. On output with a return code of EFI_BUFFER_TOO_SMALL, + the size of Buffer required to retrieve the requested file. + @param Buffer The memory buffer to transfer the file to. IF Buffer is NULL, + then no the size of the requested file is returned in + BufferSize. + + @retval EFI_SUCCESS The file was loaded. + @retval EFI_UNSUPPORTED BootPolicy is TRUE. + @retval EFI_INVALID_PARAMETER FilePath is not a valid device path, or + BufferSize is NULL. + @retval EFI_NO_MEDIA No medium was present to load the file. + @retval EFI_DEVICE_ERROR The file was not loaded due to a device error. + @retval EFI_NO_RESPONSE The remote system did not respond. + @retval EFI_NOT_FOUND The file was not found + @retval EFI_ABORTED The file load process was manually canceled. + @retval EFI_BUFFER_TOO_SMALL The BufferSize is too small to read the current + directory entry. BufferSize has been updated with + the size needed to complete the request. + + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_LOAD_FILE2)( + IN EFI_LOAD_FILE2_PROTOCOL *This, + IN EFI_DEVICE_PATH_PROTOCOL *FilePath, + IN BOOLEAN BootPolicy, + IN OUT UINTN *BufferSize, + IN VOID *Buffer OPTIONAL + ); + +/// +/// The EFI_LOAD_FILE_PROTOCOL is a simple protocol used to obtain files from arbitrary devices. +/// +struct _EFI_LOAD_FILE2_PROTOCOL { + EFI_LOAD_FILE2 LoadFile; +}; + // // Device IO protocol // diff --git a/gnu-efi/lib/data.c b/gnu-efi/lib/data.c index 34717d7..3b21c21 100644 --- a/gnu-efi/lib/data.c +++ b/gnu-efi/lib/data.c @@ -102,8 +102,11 @@ EFI_GUID gEfiBlockIoProtocolGuid = EFI_BLOCK_IO_PROTOCOL_GUID EFI_GUID gEfiBlockIo2ProtocolGuid = EFI_BLOCK_IO2_PROTOCOL_GUID; EFI_GUID gEfiDiskIoProtocolGuid = EFI_DISK_IO_PROTOCOL_GUID; EFI_GUID gEfiDiskIo2ProtocolGuid = EFI_DISK_IO2_PROTOCOL_GUID; +EFI_GUID gEfiDxeServicesTableGuid = DXE_SERVICES_TABLE_GUID; EFI_GUID gEfiSimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +EFI_GUID gEfiLoadedImageDevicePathProtocolGuid = EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID; EFI_GUID gEfiLoadFileProtocolGuid = EFI_LOAD_FILE_PROTOCOL_GUID; +EFI_GUID gEfiLoadFile2ProtocolGuid = EFI_LOAD_FILE2_PROTOCOL_GUID; EFI_GUID gEfiDeviceIoProtocolGuid = EFI_DEVICE_IO_PROTOCOL_GUID; EFI_GUID gEfiUnicodeCollationProtocolGuid = EFI_UNICODE_COLLATION_PROTOCOL_GUID; EFI_GUID gEfiSerialIoProtocolGuid = EFI_SERIAL_IO_PROTOCOL_GUID; diff --git a/gnu-efi/lib/error.c b/gnu-efi/lib/error.c index 5b36f5f..24864cb 100644 --- a/gnu-efi/lib/error.c +++ b/gnu-efi/lib/error.c @@ -54,12 +54,17 @@ struct { { EFI_END_OF_FILE, L"End of File"}, { EFI_INVALID_LANGUAGE, L"Invalid Languages"}, { EFI_COMPROMISED_DATA, L"Compromised Data"}, + { EFI_IP_ADDRESS_CONFLICT, L"IP Address Conflict"}, + { EFI_HTTP_ERROR, L"HTTP Error"}, // warnings { EFI_WARN_UNKNOWN_GLYPH, L"Warning Unknown Glyph"}, { EFI_WARN_DELETE_FAILURE, L"Warning Delete Failure"}, { EFI_WARN_WRITE_FAILURE, L"Warning Write Failure"}, { EFI_WARN_BUFFER_TOO_SMALL, L"Warning Buffer Too Small"}, + { EFI_WARN_STALE_DATA, L"Warning Stale Data"}, + { EFI_WARN_FILE_SYSTEM, L"Warning File System"}, + { EFI_WARN_RESET_REQUIRED, L"Warning Reset Required"}, { 0, NULL} } ; diff --git a/httpboot.c b/httpboot.c index ac9ea25..9b0947d 100644 --- a/httpboot.c +++ b/httpboot.c @@ -55,6 +55,51 @@ convert_http_status_code (EFI_HTTP_STATUS_CODE status_code) return 0; } +/* Convert an HTTP status code to an EFI status code. */ +static EFI_STATUS +efi_status_from_http_status(EFI_HTTP_STATUS_CODE status_code) +{ + switch (status_code) { + case HTTP_STATUS_400_BAD_REQUEST: + case HTTP_STATUS_411_LENGTH_REQUIRED: + case HTTP_STATUS_413_REQUEST_ENTITY_TOO_LARGE: + case HTTP_STATUS_414_REQUEST_URI_TOO_LARGE: + case HTTP_STATUS_415_UNSUPPORTED_MEDIA_TYPE: + case HTTP_STATUS_416_REQUESTED_RANGE_NOT_SATISFIED: + case HTTP_STATUS_417_EXPECTATION_FAILED: + return EFI_INVALID_PARAMETER; + case HTTP_STATUS_401_UNAUTHORIZED: + case HTTP_STATUS_402_PAYMENT_REQUIRED: + case HTTP_STATUS_403_FORBIDDEN: + case HTTP_STATUS_407_PROXY_AUTHENTICATION_REQUIRED: + return EFI_ACCESS_DENIED; + case HTTP_STATUS_404_NOT_FOUND: + case HTTP_STATUS_410_GONE: + return EFI_NOT_FOUND; + case HTTP_STATUS_405_METHOD_NOT_ALLOWED: + case HTTP_STATUS_501_NOT_IMPLEMENTED: + return EFI_UNSUPPORTED; + case HTTP_STATUS_406_NOT_ACCEPTABLE: + return EFI_NO_MEDIA; + case HTTP_STATUS_408_REQUEST_TIME_OUT: + case HTTP_STATUS_504_GATEWAY_TIME_OUT: + return EFI_TIMEOUT; + case HTTP_STATUS_409_CONFLICT: + case HTTP_STATUS_412_PRECONDITION_FAILED: + return EFI_MEDIA_CHANGED; + case HTTP_STATUS_500_INTERNAL_SERVER_ERROR: + case HTTP_STATUS_502_BAD_GATEWAY: + return EFI_DEVICE_ERROR; + case HTTP_STATUS_503_SERVICE_UNAVAILABLE: + return EFI_NOT_READY; + case HTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED: + return EFI_INCOMPATIBLE_VERSION; + default: + /* Use a generic HTTP error for anything else. */ + return EFI_HTTP_ERROR; + } +} + static EFI_DEVICE_PATH *devpath; static EFI_MAC_ADDRESS mac_addr; static IPv4_DEVICE_PATH ip4_node; @@ -517,7 +562,7 @@ receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) EFI_HTTP_RESPONSE_DATA response; EFI_HTTP_STATUS_CODE http_status; BOOLEAN response_done; - UINTN i, downloaded; + UINTN i, j, downloaded; CHAR8 rx_buffer[9216]; EFI_STATUS efi_status; EFI_STATUS event_status; @@ -565,7 +610,7 @@ receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) if (http_status != HTTP_STATUS_200_OK) { perror(L"HTTP Status Code: %d\n", convert_http_status_code(http_status)); - efi_status = EFI_ABORTED; + efi_status = efi_status_from_http_status(http_status); goto error; } @@ -574,6 +619,15 @@ receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) if (!strcasecmp(rx_message.Headers[i].FieldName, (CHAR8 *)"Content-Length")) { *buf_size = ascii_to_int(rx_message.Headers[i].FieldValue); + for(j = 0; j < i; j++) { + if (!strcasecmp(rx_message.Headers[i].FieldName, + (CHAR8 *)"Content-Length")) { + if (*buf_size != ascii_to_int(rx_message.Headers[j].FieldValue)) { + perror(L"Content-Length is invalid\n"); + goto error; + } + } + } } } diff --git a/include/compiler.h b/include/compiler.h index 8e8a658..6a19217 100644 --- a/include/compiler.h +++ b/include/compiler.h @@ -175,14 +175,19 @@ #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg) #endif -#ifndef ALIGN +#ifndef __ALIGN #define __ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask)) #define __ALIGN(x, a) __ALIGN_MASK(x, (typeof(x))(a) - 1) +#endif +#ifndef ALIGN #define ALIGN(x, a) __ALIGN((x), (a)) #endif #ifndef ALIGN_DOWN #define ALIGN_DOWN(x, a) __ALIGN((x) - ((a) - 1), (a)) #endif +#ifndef ALIGN_UP +#define ALIGN_UP(addr, align) (((addr) + (typeof (addr)) (align) - 1) & ~((typeof (addr)) (align) - 1)) +#endif #define MIN(a, b) ({(a) < (b) ? (a) : (b);}) #define MAX(a, b) ({(a) <= (b) ? (b) : (a);}) @@ -205,6 +210,7 @@ #define GNUC_PREREQ(maj, min) 0 #endif +#if !defined(CLANG_PREREQ) #if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__) #define CLANG_PREREQ(maj, min) \ ((__clang_major__ > (maj)) || \ @@ -212,6 +218,7 @@ #else #define CLANG_PREREQ(maj, min) 0 #endif +#endif /* CLANG_PREREQ */ #if GNUC_PREREQ(5, 1) || CLANG_PREREQ(3, 8) #define checked_add(addend0, addend1, sum) \ diff --git a/include/console.h b/include/console.h index 7ac4e11..c90e3e7 100644 --- a/include/console.h +++ b/include/console.h @@ -98,6 +98,7 @@ extern UINT32 verbose; #ifndef SHIM_UNIT_TEST #define dprint_(fmt, ...) ({ \ UINTN __dprint_ret = 0; \ + log_debug_print((fmt), ##__VA_ARGS__); \ if (verbose) \ __dprint_ret = console_print((fmt), ##__VA_ARGS__); \ __dprint_ret; \ diff --git a/include/dp.h b/include/dp.h new file mode 100644 index 0000000..884c146 --- /dev/null +++ b/include/dp.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * dp.h - device path helper functions + * Copyright Peter Jones + */ + +#ifndef DP_H_ +#define DP_H_ + +int +is_removable_media_path(EFI_LOADED_IMAGE *li); + +#endif /* !DP_H_ */ +// vim:fenc=utf-8:tw=75:noet diff --git a/include/errlog.h b/include/errlog.h new file mode 100644 index 0000000..b9f089b --- /dev/null +++ b/include/errlog.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * errlog.h - error logging utilities + * Copyright Peter Jones + */ + +#ifndef ERRLOG_H_ +#define ERRLOG_H_ + +extern EFI_STATUS EFIAPI LogError_(const char *file, int line, const char *func, + const CHAR16 *fmt, ...); +extern EFI_STATUS EFIAPI VLogError(const char *file, int line, const char *func, + const CHAR16 *fmt, ms_va_list args); +extern VOID LogHexdump_(const char *file, int line, const char *func, + const void *data, size_t sz); +extern VOID PrintErrors(VOID); +extern VOID ClearErrors(VOID); +extern void save_logs(void); +extern UINTN EFIAPI log_debug_print(const CHAR16 *fmt, ...); + +#endif /* !ERRLOG_H_ */ +// vim:fenc=utf-8:tw=75:noet diff --git a/include/errors.h b/include/errors.h index 67d821e..eab5845 100644 --- a/include/errors.h +++ b/include/errors.h @@ -9,5 +9,8 @@ #ifndef EFI_SECURITY_VIOLATION #define EFI_SECURITY_VIOLATION EFIERR(26) #endif +#ifndef EFI_HTTP_ERROR +#define EFI_HTTP_ERROR EFIERR(35) +#endif #endif /* SHIM_ERRORS_H */ diff --git a/include/fanalyzer.mk b/include/fanalyzer.mk index a0679e3..b22656e 100644 --- a/include/fanalyzer.mk +++ b/include/fanalyzer.mk @@ -21,7 +21,7 @@ fanalyzer-build-all : COMPILER=gcc fanalyzer-build-all : CCACHE_DISABLE=1 fanalyzer-build-all : FEATUREFLAGS+=-fanalyzer fanalyzer-build-all : WERRFLAGS=-Werror=analyzer-null-dereference -fanalyzer-build-all : IGNORE_COMPILER_ERRORS=" || :" +fanalyzer-build-all : IGNORE_COMPILER_ERRORS= || : fanalyzer-build-all : all fanalyzer-no-openssl : | fanalyzer-test diff --git a/include/guid.h b/include/guid.h index 898c4fa..26628d1 100644 --- a/include/guid.h +++ b/include/guid.h @@ -3,6 +3,16 @@ #ifndef SHIM_GUID_H #define SHIM_GUID_H +#define LGUID_FMT L"%08x-%04hx-%04hx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" +#define GUID_FMT "%08x-%04hx-%04hx-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" + +#define GUID_ARGS(guid) \ + ((EFI_GUID)guid).Data1, ((EFI_GUID)guid).Data2, ((EFI_GUID)guid).Data3, \ + ((EFI_GUID)guid).Data4[1], ((EFI_GUID)guid).Data4[0], \ + ((EFI_GUID)guid).Data4[2], ((EFI_GUID)guid).Data4[3], \ + ((EFI_GUID)guid).Data4[4], ((EFI_GUID)guid).Data4[5], \ + ((EFI_GUID)guid).Data4[6], ((EFI_GUID)guid).Data4[7] + extern EFI_GUID BDS_GUID; extern EFI_GUID GV_GUID; extern EFI_GUID SIG_DB; @@ -36,6 +46,8 @@ extern EFI_GUID SECURITY_PROTOCOL_GUID; extern EFI_GUID SECURITY2_PROTOCOL_GUID; extern EFI_GUID EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID; extern EFI_GUID SHIM_LOCK_GUID; +extern EFI_GUID SHIM_IMAGE_LOADER_GUID; +extern EFI_GUID SHIM_LOADED_IMAGE_GUID; extern EFI_GUID MOK_VARIABLE_STORE; extern EFI_GUID SECUREBOOT_EFI_NAMESPACE_GUID; diff --git a/include/hexdump.h b/include/hexdump.h index e8f4fe1..6f3d5fa 100644 --- a/include/hexdump.h +++ b/include/hexdump.h @@ -89,10 +89,14 @@ vhexdumpf(const char *file, int line, const char *func, const CHAR16 *const fmt, if (verbose == 0) return; - if (!data || !size) { + if (!data) { dprint(L"hexdump of a NULL pointer!\n"); return; } + if (!size) { + dprint(L"hexdump of a 0 size region!\n"); + return; + } while (offset < size) { char hexbuf[49]; diff --git a/include/load-options.h b/include/load-options.h index d2bee3b..78b1dcc 100644 --- a/include/load-options.h +++ b/include/load-options.h @@ -13,6 +13,7 @@ EFI_STATUS generate_path_from_image_path(EFI_LOADED_IMAGE *li, EFI_STATUS parse_load_options(EFI_LOADED_IMAGE *li); extern CHAR16 *second_stage; +extern CHAR16 *optional_second_stage; extern void *load_options; extern UINT32 load_options_size; diff --git a/include/replacements.h b/include/loader-proto.h similarity index 70% rename from include/replacements.h rename to include/loader-proto.h index 8b35c85..db8e670 100644 --- a/include/replacements.h +++ b/include/loader-proto.h @@ -16,7 +16,6 @@ typedef enum { } verification_method_t; extern verification_method_t verification_method; -extern int loader_is_participating; extern void hook_system_services(EFI_SYSTEM_TABLE *local_systab); extern void unhook_system_services(void); @@ -24,7 +23,14 @@ extern void unhook_system_services(void); extern void hook_exit(EFI_SYSTEM_TABLE *local_systab); extern void unhook_exit(void); -extern EFI_STATUS install_shim_protocols(void); -extern void uninstall_shim_protocols(void); +typedef struct _SHIM_IMAGE_LOADER { + EFI_IMAGE_LOAD LoadImage; + EFI_IMAGE_START StartImage; + EFI_EXIT Exit; + EFI_IMAGE_UNLOAD UnloadImage; +} SHIM_IMAGE_LOADER; + +extern SHIM_IMAGE_LOADER shim_image_loader_interface; +extern void init_image_loader(void); #endif /* SHIM_REPLACEMENTS_H */ diff --git a/include/memattrs.h b/include/memattrs.h new file mode 100644 index 0000000..193da98 --- /dev/null +++ b/include/memattrs.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * memattrs.h - EFI and DXE memory attribute helpers + * Copyright Peter Jones + */ + +#ifndef SHIM_MEMATTRS_H_ +#define SHIM_MEMATTRS_H_ + +extern EFI_STATUS get_mem_attrs (uintptr_t addr, size_t size, uint64_t *attrs); +extern EFI_STATUS update_mem_attrs(uintptr_t addr, uint64_t size, + uint64_t set_attrs, uint64_t clear_attrs); + +extern void get_hsi_mem_info(void); +extern char *decode_hsi_bits(UINTN hsi); + +#endif /* !SHIM_MEMATTRS_H_ */ +// vim:fenc=utf-8:tw=75:noet diff --git a/include/mock-variables.h b/include/mock-variables.h index 9f276e6..b7ee1cb 100644 --- a/include/mock-variables.h +++ b/include/mock-variables.h @@ -115,6 +115,9 @@ void mock_uninstall_query_variable_info(void); void mock_reset_variables(void); void mock_reset_config_table(void); void mock_finalize_vars_and_configs(void); +void mock_set_usage_limits(list_t *limit_list, + struct mock_variable_limits *limits); +void mock_set_default_usage_limits(void); typedef enum { NONE = 0, diff --git a/include/mok.h b/include/mok.h index fb19423..f4468ab 100644 --- a/include/mok.h +++ b/include/mok.h @@ -17,6 +17,14 @@ typedef enum { struct mok_state_variable; typedef vendor_addend_category_t (vendor_addend_categorizer_t)(struct mok_state_variable *); +typedef UINTN (mok_variable_format_helper_t)(UINT8 *buf, size_t sz, struct mok_state_variable *); + +#define MOK_MIRROR_KEYDB 0x01 +#define MOK_MIRROR_DELETE_FIRST 0x02 +#define MOK_VARIABLE_MEASURE 0x04 +#define MOK_VARIABLE_LOG 0x08 +#define MOK_VARIABLE_INVERSE 0x10 +#define MOK_VARIABLE_CONFIG_ONLY 0x20 /* * MoK variables that need to have their storage validated. @@ -81,6 +89,8 @@ struct mok_state_variable { * MOK_MIRROR_DELETE_FIRST delete any existing variable first * MOK_VARIABLE_MEASURE extend PCR 7 and log the hash change * MOK_VARIABLE_LOG measure into whatever .pcr says and log + * MOK_VARIABLE_CONFIG_ONLY don't create a UEFI variable, only add + * it to the config space variables. */ UINTN pcr; /* PCR to measure and hash to */ @@ -89,6 +99,23 @@ struct mok_state_variable { * mirrored. */ UINT8 *state; + + /* + * If this is non-NULL, this function will be called during the + * "import" phase to format the variable data. It'll get called + * twice, once as: + * + * sz = format(NULL, 0, ptr); + * + * a buffer of size sz will then be allocated, and it'll be called + * again to fill the buffer: + * + * format(buf, sz, ptr); + * + * Note that as an implementation detail data and data_size must be + * NULL and 0 respectively for this entry. + */ + mok_variable_format_helper_t *format; }; extern size_t n_mok_state_variables; @@ -100,10 +127,31 @@ struct mok_variable_config_entry { UINT8 data[]; }; +extern EFI_PHYSICAL_ADDRESS mok_config_table; +extern UINTN mok_config_table_pages; + /* * bit definitions for MokPolicy */ #define MOK_POLICY_REQUIRE_NX 1 +extern UINTN hsi_status; +/* heap is executable */ +#define SHIM_HSI_STATUS_HEAPX 0x00000001ULL +/* stack is executable */ +#define SHIM_HSI_STATUS_STACKX 0x00000002ULL +/* read-only sections are writable */ +#define SHIM_HSI_STATUS_ROW 0x00000004ULL +/* platform provides the EFI Memory Attribute Protocol */ +#define SHIM_HSI_STATUS_HASMAP 0x00000008ULL +/* platform provides DXE Services Table */ +#define SHIM_HSI_STATUS_HASDST 0x00000010ULL +/* platform has DST->GetMemorySpaceDescriptor */ +#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/netboot.h b/include/netboot.h index a7bf6cd..296f10f 100644 --- a/include/netboot.h +++ b/include/netboot.h @@ -3,10 +3,13 @@ #ifndef SHIM_NETBOOT_H #define SHIM_NETBOOT_H +#define SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE 1 + extern BOOLEAN findNetboot(EFI_HANDLE image_handle); extern EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle, CHAR8 *name); -extern EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, UINT64 *bufsiz); +extern EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle, VOID **buffer, + UINT64 *bufsiz, int flags); #endif /* SHIM_NETBOOT_H */ diff --git a/include/pe.h b/include/pe.h index 9ea9eb4..ea40184 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, @@ -52,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/peimage.h b/include/peimage.h index 6eef105..5d04968 100644 --- a/include/peimage.h +++ b/include/peimage.h @@ -144,12 +144,12 @@ typedef struct { /// /// @attention -/// EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC means PE32 and +/// EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC means PE32 and /// EFI_IMAGE_OPTIONAL_HEADER32 must be used. The data structures only vary /// after NT additional fields. /// #define EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b - + /// /// Optional Header Standard Fields for PE32. /// @@ -195,7 +195,7 @@ typedef struct { /// /// @attention -/// EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC means PE32+ and +/// EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC means PE32+ and /// EFI_IMAGE_OPTIONAL_HEADER64 must be used. The data structures only vary /// after NT additional fields. /// @@ -465,7 +465,7 @@ typedef struct { #define EFI_IMAGE_COMDAT_SELECT_SAME_SIZE 3 #define EFI_IMAGE_COMDAT_SELECT_EXACT_MATCH 4 #define EFI_IMAGE_COMDAT_SELECT_ASSOCIATIVE 5 - + // // the following values only be referred in PeCoff, not defined in PECOFF. // @@ -500,9 +500,9 @@ typedef struct { #define EFI_IMAGE_REL_I386_SECREL 0x000B #define EFI_IMAGE_REL_I386_REL32 0x0014 ///< PC-relative 32-bit reference to the symbols virtual address. -// +// // x64 processor relocation types. -// +// #define IMAGE_REL_AMD64_ABSOLUTE 0x0000 #define IMAGE_REL_AMD64_ADDR64 0x0001 #define IMAGE_REL_AMD64_ADDR32 0x0002 @@ -824,6 +824,7 @@ typedef struct { EFI_IMAGE_DATA_DIRECTORY *RelocDir; EFI_IMAGE_DATA_DIRECTORY *SecDir; UINT64 NumberOfRvaAndSizes; + UINT16 DllCharacteristics; EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr; } PE_COFF_LOADER_IMAGE_CONTEXT; diff --git a/include/sbat.h b/include/sbat.h index bb523e7..093bb64 100644 --- a/include/sbat.h +++ b/include/sbat.h @@ -38,7 +38,8 @@ #define POLICY_RESET 3 #define POLICY_NOTREAD 255 -#define REVOCATIONFILE L"revocations.efi" +#define SBATREVOCATIONFILE L"revocations_sbat.efi" +#define SKUSIREVOCATIONFILE L"revocations_sku.efi" extern UINTN _sbat, _esbat; diff --git a/include/sbat_var_defs.h b/include/sbat_var_defs.h index f8cba02..f4f5a27 100644 --- a/include/sbat_var_defs.h +++ b/include/sbat_var_defs.h @@ -7,7 +7,9 @@ #define QUOTE(s) #s /* - * This is the entry for the sbat data format + * SbatLevel Epoch and SHIM_DEVEL definitions are here + * Actual revocations are now soley defined in + * SbatLevel_Variable.txt */ #define SBAT_VAR_SIG "sbat," #define SBAT_VAR_VERSION "1," @@ -22,46 +24,10 @@ #define SBAT_VAR_LATEST_DATE "2022050100" #define SBAT_VAR_LATEST_REVOCATIONS "component,2\nothercomponent,2\n" -#define SBAT_VAR_LATEST \ - SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_LATEST_DATE "\n" \ - SBAT_VAR_LATEST_REVOCATIONS -#else /* !ENABLE_SHIM_DEVEL */ -/* - * Some distros may want to apply revocations from 2022052400 - * or 2022111500 automatically. They can be selected by setting - * SBAT_AUTOMATIC_DATE= at build time. Otherwise the - * default is to apply the second to most recent revocations - * automatically. Distros that need to manage automatic updates - * externally from shim can choose the epoch 2021030218 emtpy - * revocations. - */ -#ifndef SBAT_AUTOMATIC_DATE -#define SBAT_AUTOMATIC_DATE 2023012900 -#endif /* SBAT_AUTOMATIC_DATE */ -#if SBAT_AUTOMATIC_DATE == 2021030218 -#define SBAT_VAR_AUTOMATIC_REVOCATIONS -#elif SBAT_AUTOMATIC_DATE == 2022052400 -#define SBAT_VAR_AUTOMATIC_REVOCATIONS "grub,2\n" -#elif SBAT_AUTOMATIC_DATE == 2022111500 -#define SBAT_VAR_AUTOMATIC_REVOCATIONS "shim,2\ngrub,3\n" -#elif SBAT_AUTOMATIC_DATE == 2023012900 -#define SBAT_VAR_AUTOMATIC_REVOCATIONS "shim,2\ngrub,3\ngrub.debian,4\n" -#else -#error "Unknown SBAT_AUTOMATIC_DATE" -#endif /* SBAT_AUTOMATIC_DATE == */ -#define SBAT_VAR_AUTOMATIC_DATE QUOTEVAL(SBAT_AUTOMATIC_DATE) -#define SBAT_VAR_AUTOMATIC \ - SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_AUTOMATIC_DATE "\n" \ - SBAT_VAR_AUTOMATIC_REVOCATIONS - -/* - * Revocations for January 2024 shim CVEs - */ -#define SBAT_VAR_LATEST_DATE "2024010900" -#define SBAT_VAR_LATEST_REVOCATIONS "shim,4\ngrub,3\ngrub.debian,4\n" #define SBAT_VAR_LATEST \ SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_LATEST_DATE "\n" \ SBAT_VAR_LATEST_REVOCATIONS #endif /* ENABLE_SHIM_DEVEL */ + #endif /* !SBAT_VAR_DEFS_H_ */ diff --git a/include/test-data-efivars-1.h b/include/test-data-efivars-1.h index 2831bd2..259558e 100644 --- a/include/test-data-efivars-1.h +++ b/include/test-data-efivars-1.h @@ -106,5 +106,16 @@ static const unsigned char test_data_efivars_1_MokListTrustedRT[] ={ 0x01 }; +static const unsigned char test_data_efivars_1_HSIStatus[] = + "heap-is-executable: 0\n" + "stack-is-executable: 0\n" + "ro-sections-are-writable: 0\n" + "has-memory-attribute-protocol: 0\n" + "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_ */ // vim:fenc=utf-8:tw=75:noet diff --git a/include/test.h b/include/test.h index 5261dbd..ccb6114 100644 --- a/include/test.h +++ b/include/test.h @@ -85,14 +85,14 @@ extern EFI_RUNTIME_SERVICES *RT; static inline INT64 guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): Comparing "GUID_FMT" to "GUID_FMT"\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(*guid0), GUID_ARGS(*guid1)); #endif if (guid0->Data1 != guid1->Data1) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x%"PRIx64"-0x%"PRIx64"->0x%"PRIx64"\n", __FILE__, __LINE__-1, __func__, (INT64)guid0->Data1, (INT64)guid1->Data1, @@ -102,7 +102,7 @@ guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) } if (guid0->Data2 != guid1->Data2) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x%"PRIx64"-0x%"PRIx64"->0x%"PRIx64"\n", __FILE__, __LINE__-1, __func__, (INT64)guid0->Data2, (INT64)guid1->Data2, @@ -112,7 +112,7 @@ guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) } if (guid0->Data3 != guid1->Data3) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x%"PRIx64"-0x%"PRIx64"->0x%"PRIx64"\n", __FILE__, __LINE__-1, __func__, (INT64)guid0->Data3, (INT64)guid1->Data3, @@ -126,7 +126,7 @@ guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) * representation of it. */ if (guid0->Data4[1] != guid1->Data4[1]) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x%"PRIx64"-0x%"PRIx64"->0x%"PRIx64"\n", __FILE__, __LINE__-1, __func__, (INT64)guid0->Data4[1], (INT64)guid1->Data4[1], @@ -136,7 +136,7 @@ guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) } if (guid0->Data4[0] != guid1->Data4[0]) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x%"PRIx64"-0x%"PRIx64"->0x%"PRIx64"\n", __FILE__, __LINE__-1, __func__, (INT64)guid0->Data4[0], (INT64)guid1->Data4[0], @@ -147,7 +147,7 @@ guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) for (UINTN i = 2; i < 8; i++) { if (guid0->Data4[i] != guid1->Data4[i]) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x%"PRIx64"-0x%"PRIx64"->0x%"PRIx64"\n", __FILE__, __LINE__-1, __func__, (INT64)guid0->Data4[i], (INT64)guid1->Data4[i], @@ -157,7 +157,7 @@ guidcmp_helper(const EFI_GUID * const guid0, const EFI_GUID * const guid1) } } -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): returning 0x0\n", __FILE__, __LINE__-1, __func__); #endif @@ -177,7 +177,7 @@ guidcmp(const EFI_GUID * const guid0, const EFI_GUID * const guid1) cmp = guidcmp_helper(guida, guidb); ret = cmp < 0 ? -1 : (cmp > 0 ? 1 : 0); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s():CompareGuid("GUID_FMT","GUID_FMT")->%lld (%d)\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(*guida), GUID_ARGS(*guidb), cmp, ret); diff --git a/include/test.mk b/include/test.mk index e6d4659..ee2d2fd 100644 --- a/include/test.mk +++ b/include/test.mk @@ -76,8 +76,12 @@ libefi-test.a : clean test-random.h: - dd if=/dev/urandom bs=512 count=17 of=random.bin - xxd -i random.bin test-random.h + dd if=/dev/urandom bs=512 count=17 status=none | ( \ + echo "unsigned char random_bin[] = {" ; \ + xxd -i - ; \ + echo "};" ; \ + echo "unsigned int random_bin_len = 8704;" ; \ + ) > test-random.h $(wildcard test-*.c) :: %.c : test-random.h $(patsubst %.c,%,$(wildcard test-*.c)) :: | test-random.h @@ -119,7 +123,7 @@ test-coverage : CFLAGS_GCOV+=--coverage test-coverage : $(tests) test-clean : - @rm -vf test-random.h random.bin libefi-test.a + @rm -vf test-random.h libefi-test.a @rm -vf *.gcda *.gcno *.gcov vgcore.* clean : test-clean @@ -127,6 +131,5 @@ clean : test-clean all : test-clean test .PHONY: $(tests) all test clean -.SECONDARY: random.bin # vim:ft=make diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..654f05d --- /dev/null +++ b/include/utils.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +#ifndef UTILS_H_ +#define UTILS_H_ + +EFI_STATUS get_file_size(EFI_FILE_HANDLE fh, UINTN *retsize); +EFI_STATUS +read_file(EFI_FILE_HANDLE fh, CHAR16 *fullpath, CHAR16 **buffer, UINT64 *bs); + +#endif /* UTILS_H_ */ diff --git a/lib/console.c b/lib/console.c index a751f79..f603832 100644 --- a/lib/console.c +++ b/lib/console.c @@ -651,6 +651,7 @@ static struct { { EFI_PROTOCOL_ERROR, L"Protocol Error"}, { EFI_INCOMPATIBLE_VERSION, L"Incompatible Version"}, { EFI_SECURITY_VIOLATION, L"Security Violation"}, + { EFI_HTTP_ERROR, L"HTTP Error"}, // warnings { EFI_WARN_UNKNOWN_GLYPH, L"Warning Unknown Glyph"}, diff --git a/lib/guid.c b/lib/guid.c index 6e92cea..1dc90ca 100644 --- a/lib/guid.c +++ b/lib/guid.c @@ -35,5 +35,7 @@ EFI_GUID SECURITY_PROTOCOL_GUID = { 0xA46423E3, 0x4617, 0x49f1, {0xB9, 0xFF, 0xD EFI_GUID SECURITY2_PROTOCOL_GUID = { 0x94ab2f58, 0x1438, 0x4ef1, {0x91, 0x52, 0x18, 0x94, 0x1a, 0x3a, 0x0e, 0x68 } }; EFI_GUID EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID = { 0xf4560cf6, 0x40ec, 0x4b4a, {0xa1, 0x92, 0xbf, 0x1d, 0x57, 0xd0, 0xb1, 0x89} }; EFI_GUID SHIM_LOCK_GUID = {0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23 } }; +EFI_GUID SHIM_IMAGE_LOADER_GUID = {0x1f492041, 0xfadb, 0x4e59, {0x9e, 0x57, 0x7c, 0xaf, 0xe7, 0x3a, 0x55, 0xab } }; +EFI_GUID SHIM_LOADED_IMAGE_GUID = {0x6e6baeb8, 0x7108, 0x4179, {0x94, 0x9d, 0xa3, 0x49, 0x34, 0x15, 0xec, 0x97 } }; EFI_GUID MOK_VARIABLE_STORE = {0xc451ed2b, 0x9694, 0x45d3, {0xba, 0xba, 0xed, 0x9f, 0x89, 0x88, 0xa3, 0x89} }; EFI_GUID SECUREBOOT_EFI_NAMESPACE_GUID = {0x77fa9abd, 0x0359, 0x4d32, {0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b} }; diff --git a/lib/simple_file.c b/lib/simple_file.c index f22852d..abbc497 100644 --- a/lib/simple_file.c +++ b/lib/simple_file.c @@ -170,7 +170,7 @@ simple_file_write_all(EFI_FILE *file, UINTN size, void *buffer) EFI_STATUS simple_volume_selector(CHAR16 **title, CHAR16 **selected, EFI_HANDLE *h) { - UINTN count, i; + UINTN count, i, j; EFI_HANDLE *vol_handles = NULL; EFI_STATUS efi_status; CHAR16 **entries; @@ -184,11 +184,11 @@ simple_volume_selector(CHAR16 **title, CHAR16 **selected, EFI_HANDLE *h) if (!count || !vol_handles) return EFI_NOT_FOUND; - entries = AllocatePool(sizeof(CHAR16 *) * (count+1)); + entries = AllocateZeroPool(sizeof(CHAR16 *) * (count+1)); if (!entries) return EFI_OUT_OF_RESOURCES; - for (i = 0; i < count; i++) { + for (i = 0, j = 0; i < count; i++) { char buf[4096]; UINTN size = sizeof(buf); EFI_FILE_SYSTEM_INFO *fi = (void *)buf; @@ -208,19 +208,22 @@ simple_volume_selector(CHAR16 **title, CHAR16 **selected, EFI_HANDLE *h) efi_status = root->GetInfo(root, &EFI_FILE_SYSTEM_INFO_GUID, &size, fi); - if (EFI_ERROR(efi_status)) - continue; + /* If GetInfo fails, try to form a name from DevicePath. */ + if (EFI_ERROR(efi_status)){ + name = NULL; + } else { + name = fi->VolumeLabel; + } - name = fi->VolumeLabel; if (!name || StrLen(name) == 0 || StrCmp(name, L" ") == 0) name = DevicePathToStr(DevicePathFromHandle(vol_handles[i])); - entries[i] = AllocatePool((StrLen(name) + 2) * sizeof(CHAR16)); - if (!entries[i]) + entries[j] = AllocatePool((StrLen(name) + 2) * sizeof(CHAR16)); + if (!entries[j]) break; - StrCpy(entries[i], name); + StrCpy(entries[j++], name); } - entries[i] = NULL; + entries[j] = NULL; val = console_select(title, entries, 0); @@ -285,7 +288,7 @@ simple_dir_filter(EFI_HANDLE image, CHAR16 *name, CHAR16 *filter, goto out; ptr = next = *entries; - for (i = 0; i < tot; i++) { + for (i = 0; next && i < tot; i++) { int len = StrLen(next->FileName); for (c = 0; c < filtercount; c++) { @@ -308,7 +311,7 @@ simple_dir_filter(EFI_HANDLE image, CHAR16 *name, CHAR16 *filter, *count = 0; ptr = next = *entries; - for (i = 0; i < tot; i++) { + for (i = 0; next && i < tot; i++) { int len = StrLen(next->FileName); if (StrCmp(next->FileName, L".") == 0) diff --git a/lib/variables.c b/lib/variables.c index 8e63aa8..1a2c7d4 100644 --- a/lib/variables.c +++ b/lib/variables.c @@ -226,6 +226,8 @@ SetSecureVariable(const CHAR16 * const var, UINT8 *Data, UINTN len, } efi_status = CreateTimeBasedPayload(&DataSize, (UINT8 **)&Cert); if (EFI_ERROR(efi_status)) { + if (Cert && Cert != (EFI_SIGNATURE_LIST *)Data) + FreePool(Cert); console_print(L"Failed to create time based payload %d\n", efi_status); return efi_status; diff --git a/load-options.c b/load-options.c index a8c6e1a..660eaa9 100644 --- a/load-options.c +++ b/load-options.c @@ -6,6 +6,7 @@ #include "shim.h" CHAR16 *second_stage; +CHAR16 *optional_second_stage = NULL; void *load_options; UINT32 load_options_size; @@ -207,14 +208,14 @@ get_load_option_optional_data(VOID *data, UINT32 data_size, */ i += dp.len; } - if (i != fplistlen) + if (i > fplistlen) return EFI_INVALID_PARAMETER; /* - * if there's any space left, it's "optional data" + * Anything left after the file path list is optional data. */ - *od = cur + i; - *ods = limit - i; + *od = cur + fplistlen; + *ods = limit - fplistlen; return EFI_SUCCESS; } @@ -310,8 +311,13 @@ parse_load_options(EFI_LOADED_IMAGE *li) UINT32 remaining_size; CHAR16 *loader_str = NULL; - dprint(L"full load options:\n"); - dhexdumpat(li->LoadOptions, li->LoadOptionsSize, 0); + if (!li->LoadOptions || !li->LoadOptionsSize) { + dprint(L"LoadOptions is empty\n"); + return EFI_SUCCESS; + } else { + dprint(L"full LoadOptions:\n"); + dhexdumpat(li->LoadOptions, li->LoadOptionsSize, 0); + } /* * Sanity check since we make several assumptions about the length @@ -442,15 +448,25 @@ parse_load_options(EFI_LOADED_IMAGE *li) } } + /* + * Windows bcdedit.exe puts "WINDOWS\0" (in 8-bit) in the beginning of + * the options, so if we see that, we know it's not useful to us. + */ + if (li->LoadOptionsSize >= 8) + if (CompareMem(li->LoadOptions, "WINDOWS", 8) == 0) + return EFI_SUCCESS; + loader_str = split_load_options(li->LoadOptions, li->LoadOptionsSize, &remaining, &remaining_size); /* * Set up the name of the alternative loader and the LoadOptions for - * the loader + * the loader if it's not the empty string. */ if (loader_str) { - second_stage = loader_str; + if (*loader_str) { + second_stage = loader_str; + } load_options = remaining; load_options_size = remaining_size; } diff --git a/loader-proto.c b/loader-proto.c new file mode 100644 index 0000000..4581664 --- /dev/null +++ b/loader-proto.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * loader-proto.c - shim's loader protocol + * + * Copyright Red Hat, Inc + * Copyright Canonical, Ltd + */ + +#include "shim.h" + +static EFI_SYSTEM_TABLE *systab; + +EFI_SYSTEM_TABLE * +get_active_systab(void) +{ + if (systab) + return systab; + return ST; +} + +static typeof(systab->BootServices->LoadImage) system_load_image; +static typeof(systab->BootServices->StartImage) system_start_image; +static typeof(systab->BootServices->UnloadImage) system_unload_image; +static typeof(systab->BootServices->Exit) system_exit; + +void +unhook_system_services(void) +{ + if (!systab) + return; + + systab->BootServices->LoadImage = system_load_image; + systab->BootServices->StartImage = system_start_image; + systab->BootServices->Exit = system_exit; + systab->BootServices->UnloadImage = system_unload_image; + BS = systab->BootServices; +} + +typedef struct { + EFI_HANDLE hnd; + EFI_DEVICE_PATH *dp; + void *buffer; + size_t size; +} buffer_properties_t; + +static EFI_STATUS +try_load_from_sfs(EFI_DEVICE_PATH *dp, buffer_properties_t *bprop) +{ + EFI_STATUS status = EFI_SUCCESS; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *sfs = NULL; + EFI_FILE_HANDLE root = NULL; + EFI_FILE_HANDLE file = NULL; + UINT64 tmpsz = 0; + + bprop->buffer = NULL; + + /* look for a handle with SFS support from the input DP */ + bprop->dp = dp; + status = BS->LocateDevicePath(&EFI_SIMPLE_FILE_SYSTEM_GUID, &bprop->dp, &bprop->hnd); + if (EFI_ERROR(status)) { + goto out; + } + + /* make sure the remaining DP portion is really a file path */ + if (DevicePathType(bprop->dp) != MEDIA_DEVICE_PATH || + DevicePathSubType(bprop->dp) != MEDIA_FILEPATH_DP) { + status = EFI_LOAD_ERROR; + goto out; + } + + /* find protocol, open the root directory, then open file */ + status = BS->HandleProtocol(bprop->hnd, &EFI_SIMPLE_FILE_SYSTEM_GUID, (void **)&sfs); + if (EFI_ERROR(status)) + goto out; + status = sfs->OpenVolume(sfs, &root); + if (EFI_ERROR(status)) + goto out; + status = root->Open(root, &file, ((FILEPATH_DEVICE_PATH *) bprop->dp)->PathName, EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(status)) + goto out; + + /* get file size */ + status = file->SetPosition(file, -1ULL); + if (EFI_ERROR(status)) + goto out; + status = file->GetPosition(file, &tmpsz); + if (EFI_ERROR(status)) + goto out; + bprop->size = (size_t)tmpsz; + status = file->SetPosition(file, 0); + if (EFI_ERROR(status)) + goto out; + + /* allocate buffer */ + bprop->buffer = AllocatePool(bprop->size); + if (bprop->buffer == NULL) { + status = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* read file */ + status = file->Read(file, &bprop->size, bprop->buffer); + +out: + if (EFI_ERROR(status) && bprop->buffer) + FreePool(bprop->buffer); + if (file) + file->Close(file); + if (root) + root->Close(root); + return status; +} + + +static EFI_STATUS +try_load_from_lf2(EFI_DEVICE_PATH *dp, buffer_properties_t *bprop) +{ + EFI_STATUS status = EFI_SUCCESS; + EFI_LOAD_FILE2_PROTOCOL *lf2 = NULL; + + bprop->buffer = NULL; + + /* look for a handle with LF2 support from the input DP */ + bprop->dp = dp; + status = BS->LocateDevicePath(&gEfiLoadFile2ProtocolGuid, &bprop->dp, &bprop->hnd); + if (EFI_ERROR(status)) + goto out; + + /* find protocol */ + status = BS->HandleProtocol(bprop->hnd, &gEfiLoadFile2ProtocolGuid, (void **) &lf2); + if (EFI_ERROR(status)) + goto out; + + /* get file size */ + bprop->size = 0; /* this shouldn't be read when Buffer=NULL but better be safe */ + status = lf2->LoadFile(lf2, bprop->dp, /*BootPolicy=*/false, &bprop->size, NULL); + /* + * NOTE: the spec is somewhat ambiguous what is the correct return + * status code when asking for the buffer size with Buffer=NULL. I am + * assuming EFI_SUCCESS and EFI_BUFFER_TOO_SMALL are the only + * reasonable interpretations. + */ + if (EFI_ERROR(status) && status != EFI_BUFFER_TOO_SMALL) { + status = EFI_LOAD_ERROR; + goto out; + } + + /* allocate buffer */ + bprop->buffer = AllocatePool(bprop->size); + if (!bprop->buffer) { + status = EFI_OUT_OF_RESOURCES; + goto out; + } + + /* read file */ + status = lf2->LoadFile(lf2, bprop->dp, /*BootPolicy=*/false, &bprop->size, bprop->buffer); + if (EFI_ERROR(status)) + goto out; + +out: + if (EFI_ERROR(status) && bprop->buffer) + FreePool(bprop->buffer); + return status; +} + +static EFI_STATUS EFIAPI +shim_load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle, + EFI_DEVICE_PATH *DevicePath, VOID *SourceBuffer, + UINTN SourceSize, EFI_HANDLE *ImageHandle) +{ + SHIM_LOADED_IMAGE *image; + EFI_STATUS efi_status; + buffer_properties_t bprop = { NULL, NULL, NULL, 0 }; + + if (BootPolicy) + return EFI_UNSUPPORTED; + + if (!SourceBuffer || !SourceSize) { + if (!DevicePath) /* Both SourceBuffer and DevicePath are NULL */ + return EFI_NOT_FOUND; + + if (try_load_from_sfs(DevicePath, &bprop) == EFI_SUCCESS) + ; + else if (try_load_from_lf2(DevicePath, &bprop) == EFI_SUCCESS) + ; + else + /* no buffer given and we cannot load from this device */ + return EFI_LOAD_ERROR; + + SourceBuffer = bprop.buffer; + SourceSize = bprop.size; + } else { + bprop.buffer = NULL; + /* + * Even if we are using a buffer, try populating the + * device_handle and file_path fields the best we can + */ + + bprop.dp = DevicePath; + + if (bprop.dp) { + efi_status = BS->LocateDevicePath(&gEfiDevicePathProtocolGuid, + &bprop.dp, + &bprop.hnd); + if (efi_status != EFI_SUCCESS) { + /* can't seem to pull apart this DP */ + bprop.dp = DevicePath; + bprop.hnd = NULL; + } + } + } + + image = AllocatePool(sizeof(*image)); + if (!image) { + efi_status = EFI_OUT_OF_RESOURCES; + goto free_buffer; + } + + SetMem(image, sizeof(*image), 0); + + image->li.Revision = 0x1000; + image->li.ParentHandle = ParentImageHandle; + image->li.SystemTable = systab; + image->li.DeviceHandle = bprop.hnd; + if (bprop.dp) { + image->li.FilePath = DuplicateDevicePath(bprop.dp); + if (!image->li.FilePath) { + efi_status = EFI_OUT_OF_RESOURCES; + goto free_image; + } + } + if (DevicePath) { + image->loaded_image_device_path = DuplicateDevicePath(DevicePath); + if (!image->loaded_image_device_path) { + efi_status = EFI_OUT_OF_RESOURCES; + goto free_image; + } + } + + in_protocol = 1; + efi_status = handle_image(SourceBuffer, SourceSize, &image->li, + &image->entry_point, &image->alloc_address, + &image->alloc_pages); + in_protocol = 0; + if (EFI_ERROR(efi_status)) + goto free_image; + + *ImageHandle = NULL; + efi_status = BS->InstallMultipleProtocolInterfaces(ImageHandle, + &SHIM_LOADED_IMAGE_GUID, image, + &EFI_LOADED_IMAGE_GUID, &image->li, + &gEfiLoadedImageDevicePathProtocolGuid, + image->loaded_image_device_path, + NULL); + if (EFI_ERROR(efi_status)) + goto free_alloc; + + if (bprop.buffer) + FreePool(bprop.buffer); + + return EFI_SUCCESS; + +free_alloc: + BS->FreePages(image->alloc_address, image->alloc_pages); +free_image: + if (image->loaded_image_device_path) + FreePool(image->loaded_image_device_path); + if (image->li.FilePath) + FreePool(image->li.FilePath); + FreePool(image); +free_buffer: + if (bprop.buffer) + FreePool(bprop.buffer); + return efi_status; +} + +static EFI_STATUS EFIAPI +shim_start_image(IN EFI_HANDLE ImageHandle, OUT UINTN *ExitDataSize, + OUT CHAR16 **ExitData OPTIONAL) +{ + SHIM_LOADED_IMAGE *image; + EFI_STATUS efi_status; + + efi_status = BS->HandleProtocol(ImageHandle, &SHIM_LOADED_IMAGE_GUID, + (void **)&image); + + /* + * This image didn't come from shim_load_image(), so it must have come + * from something before shim was involved. + */ + if (efi_status == EFI_UNSUPPORTED) + return system_start_image(ImageHandle, ExitDataSize, ExitData); + + if (EFI_ERROR(efi_status) || image->started) + return EFI_INVALID_PARAMETER; + + if (!setjmp(image->longjmp_buf)) { + image->started = true; + efi_status = + image->entry_point(ImageHandle, image->li.SystemTable); + } else { + if (ExitData) { + *ExitDataSize = image->exit_data_size; + *ExitData = (CHAR16 *)image->exit_data; + } + efi_status = image->exit_status; + } + + // + // We only support EFI applications, so we can unload and free the + // image unconditionally. + // + BS->UninstallMultipleProtocolInterfaces(ImageHandle, + &EFI_LOADED_IMAGE_GUID, image, + &SHIM_LOADED_IMAGE_GUID, &image->li, + &gEfiLoadedImageDevicePathProtocolGuid, + image->loaded_image_device_path, + NULL); + + BS->FreePages(image->alloc_address, image->alloc_pages); + if (image->li.FilePath) + BS->FreePool(image->li.FilePath); + if (image->loaded_image_device_path) + BS->FreePool(image->loaded_image_device_path); + FreePool(image); + + return efi_status; +} + +static EFI_STATUS EFIAPI +shim_unload_image(EFI_HANDLE ImageHandle) +{ + SHIM_LOADED_IMAGE *image; + EFI_STATUS efi_status; + + efi_status = BS->HandleProtocol(ImageHandle, &SHIM_LOADED_IMAGE_GUID, + (void **)&image); + + if (efi_status == EFI_UNSUPPORTED) + return system_unload_image(ImageHandle); + + BS->FreePages(image->alloc_address, image->alloc_pages); + FreePool(image); + + return EFI_SUCCESS; +} + +static EFI_STATUS EFIAPI +shim_exit(EFI_HANDLE ImageHandle, + EFI_STATUS ExitStatus, + UINTN ExitDataSize, + CHAR16 *ExitData) +{ + EFI_STATUS efi_status; + SHIM_LOADED_IMAGE *image; + + efi_status = BS->HandleProtocol(ImageHandle, &SHIM_LOADED_IMAGE_GUID, + (void **)&image); + + /* + * If this happens, something above us on the stack of running + * applications called Exit(), and we're getting aborted along with + * it. + */ + if (efi_status == EFI_UNSUPPORTED) { + shim_fini(); + return system_exit(ImageHandle, ExitStatus, ExitDataSize, + ExitData); + } + + if (EFI_ERROR(efi_status)) + return efi_status; + + image->exit_status = ExitStatus; + image->exit_data_size = ExitDataSize; + image->exit_data = ExitData; + + longjmp(image->longjmp_buf, 1); +} + +void +init_image_loader(void) +{ + shim_image_loader_interface.LoadImage = shim_load_image; + shim_image_loader_interface.StartImage = shim_start_image; + shim_image_loader_interface.Exit = shim_exit; + shim_image_loader_interface.UnloadImage = shim_unload_image; +} + +void +hook_system_services(EFI_SYSTEM_TABLE *local_systab) +{ + systab = local_systab; + BS = systab->BootServices; + + /* We need to hook various calls to make this work... */ + + /* + * We need LoadImage() hooked so that we can guarantee everything is + * verified. + */ + system_load_image = systab->BootServices->LoadImage; + systab->BootServices->LoadImage = shim_load_image; + + /* + * We need StartImage() hooked because the system's StartImage() + * doesn't know about our structure layout. + */ + system_start_image = systab->BootServices->StartImage; + systab->BootServices->StartImage = shim_start_image; + + /* + * We need Exit() hooked so that we make sure to use the right jmp_buf + * when an application calls Exit(), but that happens in a separate + * function. + */ + + /* + * We need UnloadImage() to match our LoadImage() + */ + system_unload_image = systab->BootServices->UnloadImage; + systab->BootServices->UnloadImage = shim_unload_image; +} + +void +unhook_exit(void) +{ + systab->BootServices->Exit = system_exit; + BS = systab->BootServices; +} + +void +hook_exit(EFI_SYSTEM_TABLE *local_systab) +{ + systab = local_systab; + BS = local_systab->BootServices; + + /* + * We need to hook Exit() so that we can allow users to quit the + * bootloader and still e.g. start a new one or run an internal + * shell. + */ + system_exit = systab->BootServices->Exit; + systab->BootServices->Exit = shim_exit; +} diff --git a/make-archive b/make-archive index 9ae9eef..d0352b7 100755 --- a/make-archive +++ b/make-archive @@ -1,5 +1,7 @@ #!/bin/sh -set -e +set -eu +set -o pipefail +set -x usage() { status="${1}" @@ -78,10 +80,11 @@ main() { rm -rf "${ARCHIVE_DIR}/shim-${VERSION}" "${ARCHIVE_DIR}/shim-${VERSION}" mkdir -p "${ARCHIVE_DIR}/shim-${VERSION}/gnu-efi" cd gnu-efi || exit 1 + git fetch if [ "x" = "x${GNUEFI_GIT_TAG}" ] ; then git archive --format=tar "$(git log -1 --pretty=format:%h)" | ( cd "${ARCHIVE_DIR}/shim-${VERSION}/gnu-efi" ; tar x ) else - git archive --format=tar "${ORIGIN}/${GNUEFI_GIT_TAG}" | ( cd "${ARCHIVE_DIR}/shim-${VERSION}/gnu-efi" ; tar x ) + git archive --format=tar "${GNUEFI_GIT_TAG}" | ( cd "${ARCHIVE_DIR}/shim-${VERSION}/gnu-efi" ; tar x ) fi cd .. if [ "x" = "x${SHIM_GIT_TAG}" ] ; then diff --git a/make-certs b/make-certs index 6f40b23..e2d3e4b 100755 --- a/make-certs +++ b/make-certs @@ -7,6 +7,11 @@ set -e +if [[ ! -f `which openssl` ]]; then + echo "OpenSSL not found. Install it first, then run this script again." + exit 1 +fi + DOMAIN=xn--u4h.net DAYS=365 KEYTYPE=RSA diff --git a/memattrs.c b/memattrs.c new file mode 100644 index 0000000..ed8a3ae --- /dev/null +++ b/memattrs.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * memattrs.c - EFI and DXE memory attribute helpers + * Copyright Peter Jones + */ + +#include "shim.h" + +static inline uint64_t +shim_mem_attrs_to_uefi_mem_attrs (uint64_t attrs) +{ + uint64_t ret = EFI_MEMORY_RP | + EFI_MEMORY_RO | + EFI_MEMORY_XP; + + if (attrs & MEM_ATTR_R) + ret &= ~EFI_MEMORY_RP; + + if (attrs & MEM_ATTR_W) + ret &= ~EFI_MEMORY_RO; + + if (attrs & MEM_ATTR_X) + ret &= ~EFI_MEMORY_XP; + + return ret; +} + +static inline uint64_t +uefi_mem_attrs_to_shim_mem_attrs (uint64_t attrs) +{ + uint64_t ret = MEM_ATTR_R | + MEM_ATTR_W | + MEM_ATTR_X; + + if (attrs & EFI_MEMORY_RP) + ret &= ~MEM_ATTR_R; + + if (attrs & EFI_MEMORY_RO) + ret &= ~MEM_ATTR_W; + + if (attrs & EFI_MEMORY_XP) + ret &= ~MEM_ATTR_X; + + return ret; +} + +static void +get_dxe_services_table(EFI_DXE_SERVICES_TABLE **dstp) +{ + static EFI_DXE_SERVICES_TABLE *dst = NULL; + + if (dst == NULL) { + dprint(L"Looking for configuration table " LGUID_FMT L"\n", GUID_ARGS(gEfiDxeServicesTableGuid)); + + for (UINTN i = 0; i < ST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *ct = &ST->ConfigurationTable[i]; + dprint(L"Testing configuration table " LGUID_FMT L"\n", GUID_ARGS(ct->VendorGuid)); + if (CompareMem(&ct->VendorGuid, &gEfiDxeServicesTableGuid, sizeof(EFI_GUID)) != 0) + continue; + + dst = (EFI_DXE_SERVICES_TABLE *)ct->VendorTable; + dprint(L"Looking for DXE Services Signature 0x%16llx, found signature 0x%16llx\n", + EFI_DXE_SERVICES_TABLE_SIGNATURE, dst->Hdr.Signature); + if (dst->Hdr.Signature != EFI_DXE_SERVICES_TABLE_SIGNATURE) + continue; + + if (!dst->GetMemorySpaceDescriptor || !dst->SetMemorySpaceAttributes) { + /* + * purposefully not treating this as an error so that HSIStatus + * can tell us about it later. + */ + dprint(L"DXE Services lacks Get/SetMemorySpace* functions\n"); + } + + dprint(L"Setting dxe_services_table to 0x%llx\n", dst); + *dstp = dst; + return; + } + } else { + *dstp = dst; + return; + } + + dst = NULL; + dprint(L"Couldn't find DXE services\n"); +} + +static EFI_STATUS +dxe_get_mem_attrs(uintptr_t physaddr, size_t size, uint64_t *attrs) +{ + EFI_STATUS status; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR desc; + EFI_PHYSICAL_ADDRESS start, end, next; + EFI_DXE_SERVICES_TABLE *dst = NULL; + + get_dxe_services_table(&dst); + if (!dst) + return EFI_UNSUPPORTED; + + if (!dst->GetMemorySpaceDescriptor || !dst->SetMemorySpaceAttributes) + return EFI_UNSUPPORTED; + + if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0 || attrs == NULL) { + dprint(L"%a called on 0x%llx-0x%llx and attrs 0x%llx\n", + __func__, (unsigned long long)physaddr, + (unsigned long long)(physaddr+size-1), + attrs); + return EFI_SUCCESS; + } + + start = ALIGN_DOWN(physaddr, EFI_PAGE_SIZE); + end = ALIGN_UP(physaddr + size, EFI_PAGE_SIZE); + + for (; start < end; start = next) { + status = dst->GetMemorySpaceDescriptor(start, &desc); + if (EFI_ERROR(status)) { + dprint(L"GetMemorySpaceDescriptor(0x%llx, ...): %r\n", + start, status); + return status; + } + + next = desc.BaseAddress + desc.Length; + + if (desc.GcdMemoryType != EFI_GCD_MEMORY_TYPE_SYSTEM_MEMORY) + continue; + + *attrs = uefi_mem_attrs_to_shim_mem_attrs(desc.Attributes); + return EFI_SUCCESS; + } + + return EFI_NOT_FOUND; +} + +static EFI_STATUS +dxe_update_mem_attrs(uintptr_t addr, size_t size, + uint64_t set_attrs, uint64_t clear_attrs) +{ +#if 0 + EFI_STATUS status; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR desc; + EFI_PHYSICAL_ADDRESS start, end, next; + uint64_t before = 0, after = 0, dxe_set_attrs, dxe_clear_attrs; +#endif + EFI_DXE_SERVICES_TABLE *dst = NULL; + + get_dxe_services_table(&dst); + if (!dst) + return EFI_UNSUPPORTED; + + if (!dst->GetMemorySpaceDescriptor || !dst->SetMemorySpaceAttributes) + return EFI_UNSUPPORTED; + + if (!IS_PAGE_ALIGNED(addr) || !IS_PAGE_ALIGNED(size) || size == 0) { + perror(L"Invalid call %a(addr:0x%llx-0x%llx, size:0x%llx, +%a%a%a, -%a%a%a)\n", + __func__, (unsigned long long)addr, + (unsigned long long)(addr + size - 1), + (unsigned long long)size, + (set_attrs & MEM_ATTR_R) ? "r" : "", + (set_attrs & MEM_ATTR_W) ? "w" : "", + (set_attrs & MEM_ATTR_X) ? "x" : "", + (clear_attrs & MEM_ATTR_R) ? "r" : "", + (clear_attrs & MEM_ATTR_W) ? "w" : "", + (clear_attrs & MEM_ATTR_X) ? "x" : ""); + if (!IS_PAGE_ALIGNED(addr)) + perror(L" addr is not page aligned\n"); + if (!IS_PAGE_ALIGNED(size)) + perror(L" size is not page aligned\n"); + if (size == 0) + perror(L" size is 0\n"); + return EFI_SUCCESS; + } + + /* + * We know this only works coincidentally, so nerfing it for now + * until we have a chance to debug more thoroughly on these niche + * systems. + */ +#if 0 + start = ALIGN_DOWN(addr, EFI_PAGE_SIZE); + end = ALIGN_UP(addr + size, EFI_PAGE_SIZE); + + for (; start < end; start = next) { + EFI_PHYSICAL_ADDRESS mod_start; + UINT64 mod_size; + + status = dst->GetMemorySpaceDescriptor(start, &desc); + if (EFI_ERROR(status)) { + dprint(L"GetMemorySpaceDescriptor(0x%llx, ...): %r\n", + start, status); + return status; + } + + next = desc.BaseAddress + desc.Length; + + if (desc.GcdMemoryType != EFI_GCD_MEMORY_TYPE_SYSTEM_MEMORY) + continue; + + mod_start = MAX(start, desc.BaseAddress); + mod_size = MIN(end, next) - mod_start; + + before = uefi_mem_attrs_to_shim_mem_attrs(desc.Attributes); + dxe_set_attrs = shim_mem_attrs_to_uefi_mem_attrs(set_attrs); + dprint("translating set_attrs from 0x%lx to 0x%lx\n", set_attrs, dxe_set_attrs); + dxe_clear_attrs = shim_mem_attrs_to_uefi_mem_attrs(clear_attrs); + dprint("translating clear_attrs from 0x%lx to 0x%lx\n", clear_attrs, dxe_clear_attrs); + desc.Attributes |= dxe_set_attrs; + desc.Attributes &= ~dxe_clear_attrs; + after = uefi_mem_attrs_to_shim_mem_attrs(desc.Attributes); + + status = dst->SetMemorySpaceAttributes(mod_start, mod_size, desc.Attributes); + if (EFI_ERROR(status)) { + dprint(L"Failed to update memory attrs:0x%0x addr:0x%llx size:0x%0lx status:%r\n", + desc.Attributes, mod_start, mod_size, status); + return status; + } + + break; + } + + dprint(L"set +%a%a%a -%a%a%a on 0x%llx-0x%llx before:%c%c%c after:%c%c%c\n", + (set_attrs & MEM_ATTR_R) ? "r" : "", + (set_attrs & MEM_ATTR_W) ? "w" : "", + (set_attrs & MEM_ATTR_X) ? "x" : "", + (clear_attrs & MEM_ATTR_R) ? "r" : "", + (clear_attrs & MEM_ATTR_W) ? "w" : "", + (clear_attrs & MEM_ATTR_X) ? "x" : "", + (unsigned long long)addr, (unsigned long long)(addr + size - 1), + (before & MEM_ATTR_R) ? 'r' : '-', + (before & MEM_ATTR_W) ? 'w' : '-', + (before & MEM_ATTR_X) ? 'x' : '-', + (after & MEM_ATTR_R) ? 'r' : '-', + (after & MEM_ATTR_W) ? 'w' : '-', + (after & MEM_ATTR_X) ? 'x' : '-'); +#endif + + return EFI_SUCCESS; +} + +static void +get_efi_mem_attr_protocol(EFI_MEMORY_ATTRIBUTE_PROTOCOL **protop) +{ + static EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; + static bool has_mem_access_proto = true; + + if (proto == NULL && has_mem_access_proto == true) { + EFI_STATUS efi_status; + efi_status = LibLocateProtocol(&EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID, + (VOID **)&proto); + if (EFI_ERROR(efi_status) || !proto) { + has_mem_access_proto = false; + *protop = NULL; + } + } + + *protop = proto; +} + +static EFI_STATUS +efi_get_mem_attrs(uintptr_t addr, size_t size, uint64_t *attrs) +{ + EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; + EFI_PHYSICAL_ADDRESS physaddr = addr; + EFI_STATUS efi_status; + + get_efi_mem_attr_protocol(&proto); + if (!proto) + return EFI_UNSUPPORTED; + + if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0 || attrs == NULL) { + dprint(L"%a called on 0x%llx-0x%llx and attrs 0x%llx\n", + __func__, (unsigned long long)physaddr, + (unsigned long long)(physaddr+size-1), + attrs); + return EFI_SUCCESS; + } + + efi_status = proto->GetMemoryAttributes(proto, physaddr, size, attrs); + if (EFI_ERROR(efi_status)) { + dprint(L"GetMemoryAttributes(..., 0x%llx, 0x%x, 0x%x): %r\n", + physaddr, size, attrs, efi_status); + } else { + *attrs = uefi_mem_attrs_to_shim_mem_attrs (*attrs); + } + + return efi_status; +} + +static EFI_STATUS +efi_update_mem_attrs(uintptr_t addr, uint64_t size, + uint64_t set_attrs, uint64_t clear_attrs) +{ + EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; + EFI_PHYSICAL_ADDRESS physaddr = addr; + EFI_STATUS efi_status, ret; + uint64_t before = 0, after = 0, uefi_set_attrs, uefi_clear_attrs; + + get_efi_mem_attr_protocol(&proto); + if (!proto) + return EFI_UNSUPPORTED; + + efi_status = efi_get_mem_attrs(addr, size, &before); + if (EFI_ERROR(efi_status)) + dprint(L"efi_get_mem_attrs(0x%llx, 0x%llx, 0x%llx) -> 0x%lx\n", + (unsigned long long)addr, (unsigned long long)size, + &before, efi_status); + + if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0) { + perror(L"Invalid call %a(addr:0x%llx-0x%llx, size:0x%llx, +%a%a%a, -%a%a%a)\n", + __func__, (unsigned long long)physaddr, + (unsigned long long)(physaddr + size - 1), + (unsigned long long)size, + (set_attrs & MEM_ATTR_R) ? "r" : "", + (set_attrs & MEM_ATTR_W) ? "w" : "", + (set_attrs & MEM_ATTR_X) ? "x" : "", + (clear_attrs & MEM_ATTR_R) ? "r" : "", + (clear_attrs & MEM_ATTR_W) ? "w" : "", + (clear_attrs & MEM_ATTR_X) ? "x" : ""); + if (!IS_PAGE_ALIGNED(physaddr)) + perror(L" addr is not page aligned\n"); + if (!IS_PAGE_ALIGNED(size)) + perror(L" size is not page aligned\n"); + if (size == 0) + perror(L" size is 0\n"); + return EFI_SUCCESS; + } + + uefi_set_attrs = shim_mem_attrs_to_uefi_mem_attrs (set_attrs); + dprint("translating set_attrs from 0x%lx to 0x%lx\n", set_attrs, uefi_set_attrs); + uefi_clear_attrs = shim_mem_attrs_to_uefi_mem_attrs (clear_attrs); + dprint("translating clear_attrs from 0x%lx to 0x%lx\n", clear_attrs, uefi_clear_attrs); + efi_status = EFI_SUCCESS; + if (uefi_set_attrs) { + efi_status = proto->SetMemoryAttributes(proto, physaddr, size, uefi_set_attrs); + if (EFI_ERROR(efi_status)) { + dprint(L"Failed to set memory attrs:0x%0x physaddr:0x%llx size:0x%0lx status:%r\n", + uefi_set_attrs, physaddr, size, efi_status); + } + } + if (!EFI_ERROR(efi_status) && uefi_clear_attrs) { + efi_status = proto->ClearMemoryAttributes(proto, physaddr, size, uefi_clear_attrs); + if (EFI_ERROR(efi_status)) { + dprint(L"Failed to clear memory attrs:0x%0x physaddr:0x%llx size:0x%0lx status:%r\n", + uefi_clear_attrs, physaddr, size, efi_status); + } + } + ret = efi_status; + + efi_status = efi_get_mem_attrs(addr, size, &after); + if (EFI_ERROR(efi_status)) + dprint(L"efi_get_mem_attrs(0x%llx, %llu, 0x%llx) -> 0x%lx\n", + (unsigned long long)addr, (unsigned long long)size, + &after, efi_status); + + dprint(L"set +%a%a%a -%a%a%a on 0x%llx-0x%llx before:%c%c%c after:%c%c%c\n", + (set_attrs & MEM_ATTR_R) ? "r" : "", + (set_attrs & MEM_ATTR_W) ? "w" : "", + (set_attrs & MEM_ATTR_X) ? "x" : "", + (clear_attrs & MEM_ATTR_R) ? "r" : "", + (clear_attrs & MEM_ATTR_W) ? "w" : "", + (clear_attrs & MEM_ATTR_X) ? "x" : "", + (unsigned long long)addr, (unsigned long long)(addr + size - 1), + (before & MEM_ATTR_R) ? 'r' : '-', + (before & MEM_ATTR_W) ? 'w' : '-', + (before & MEM_ATTR_X) ? 'x' : '-', + (after & MEM_ATTR_R) ? 'r' : '-', + (after & MEM_ATTR_W) ? 'w' : '-', + (after & MEM_ATTR_X) ? 'x' : '-'); + + return ret; +} + +EFI_STATUS +update_mem_attrs(uintptr_t addr, uint64_t size, + uint64_t set_attrs, uint64_t clear_attrs) +{ + EFI_STATUS efi_status; + + efi_status = efi_update_mem_attrs(addr, size, set_attrs, clear_attrs); + if (!EFI_ERROR(efi_status)) + return efi_status; + + if (efi_status == EFI_UNSUPPORTED) + efi_status = dxe_update_mem_attrs(addr, size, set_attrs, clear_attrs); + + return efi_status; +} + +EFI_STATUS +get_mem_attrs(uintptr_t addr, size_t size, uint64_t *attrs) +{ + EFI_STATUS efi_status; + + efi_status = efi_get_mem_attrs(addr, size, attrs); + if (!EFI_ERROR(efi_status)) + return efi_status; + + if (efi_status == EFI_UNSUPPORTED) + efi_status = dxe_get_mem_attrs(addr, size, attrs); + + return efi_status; +} + +char * +decode_hsi_bits(UINTN hsi) +{ + static const struct { + UINTN bit; + char name[16]; + } bits[] = { + {.bit = SHIM_HSI_STATUS_HEAPX, .name = "HEAPX"}, + {.bit = SHIM_HSI_STATUS_STACKX, .name = "STACKX"}, + {.bit = SHIM_HSI_STATUS_ROW, .name = "ROW"}, + {.bit = SHIM_HSI_STATUS_HASMAP, .name = "HASMAP"}, + {.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; + static char retbufs[2][sizeof(bits)]; + char *retbuf = &retbufs[x % 2][0]; + char *prev = &retbuf[0]; + char *pos = &retbuf[0]; + + x = ( x + 1 ) % 2; + + ZeroMem(retbuf, sizeof(bits)); + + if (hsi == 0) { + prev = stpcpy(retbuf, "0"); + } else { + for (UINTN i = 0; bits[i].bit != 0; i++) { + if (hsi & bits[i].bit) { + prev = stpcpy(pos, bits[i].name); + pos = stpcpy(prev, "|"); + } + } + } + prev[0] = '\0'; + return retbuf; +} + +void +get_hsi_mem_info(void) +{ + EFI_STATUS efi_status; + uintptr_t addr; + uint64_t attrs = 0; + uint32_t *tmp_alloc; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *efiproto = NULL; + EFI_DXE_SERVICES_TABLE *dst = NULL; + + get_efi_mem_attr_protocol(&efiproto); + if (efiproto) { + dprint(L"Setting HSI from %a to %a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASMAP)); + hsi_status |= SHIM_HSI_STATUS_HASMAP; + } + + get_dxe_services_table(&dst); + if (dst) { + dprint(L"Setting HSI from %a to %a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASDST)); + hsi_status |= SHIM_HSI_STATUS_HASDST; + if (dst->GetMemorySpaceDescriptor) { + dprint(L"Setting HSI from %a to %a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASDSTGMSD)); + hsi_status |= SHIM_HSI_STATUS_HASDSTGMSD; + } + if (dst->SetMemorySpaceAttributes) { + dprint(L"Setting HSI from %a to %a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HASDSTSMSA)); + hsi_status |= SHIM_HSI_STATUS_HASDSTSMSA; + } + } + + if (!(hsi_status & SHIM_HSI_STATUS_HASMAP) && + !(hsi_status & SHIM_HSI_STATUS_HASDSTGMSD && + hsi_status & SHIM_HSI_STATUS_HASDSTSMSA)) { + dprint(L"No memory protocol, not testing further\n"); + return; + } + + addr = ((uintptr_t)&get_hsi_mem_info) & ~EFI_PAGE_MASK; + efi_status = get_mem_attrs(addr, EFI_PAGE_SIZE, &attrs); + if (EFI_ERROR(efi_status)) { + dprint(L"get_mem_attrs(0x%016llx, 0x%x, &attrs) failed.\n", addr, EFI_PAGE_SIZE); + goto error; + } + + if (attrs & MEM_ATTR_W) { + dprint(L"get_hsi_mem_info() is on a writable page: %a->%a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_ROW)); + hsi_status |= SHIM_HSI_STATUS_ROW; + } + + addr = ((uintptr_t)&addr) & ~EFI_PAGE_MASK; + efi_status = get_mem_attrs(addr, EFI_PAGE_SIZE, &attrs); + if (EFI_ERROR(efi_status)) { + dprint(L"get_mem_attrs(0x%016llx, 0x%x, &attrs) failed.\n", addr, EFI_PAGE_SIZE); + goto error; + } + + if (attrs & MEM_ATTR_X) { + dprint(L"Stack variable is on an executable page: %a->%a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_STACKX)); + hsi_status |= SHIM_HSI_STATUS_STACKX; + } + + tmp_alloc = AllocatePool(EFI_PAGE_SIZE); + if (!tmp_alloc) { + dprint(L"Failed to allocate heap variable.\n"); + goto error; + } + + addr = ((uintptr_t)tmp_alloc) & ~EFI_PAGE_MASK; + efi_status = get_mem_attrs(addr, EFI_PAGE_SIZE, &attrs); + FreePool(tmp_alloc); + if (EFI_ERROR(efi_status)) { + dprint(L"get_mem_attrs(0x%016llx, 0x%x, &attrs) failed.\n", addr, EFI_PAGE_SIZE); + goto error; + } + if (attrs & MEM_ATTR_X) { + dprint(L"Heap variable is on an executable page: %a->%a\n", + decode_hsi_bits(hsi_status), + decode_hsi_bits(hsi_status | SHIM_HSI_STATUS_HEAPX)); + hsi_status |= SHIM_HSI_STATUS_HEAPX; + } + + return; + +error: + /* + * In this case we can't actually tell anything, so assume + * and report the worst case scenario. + */ + hsi_status = SHIM_HSI_STATUS_HEAPX | + SHIM_HSI_STATUS_STACKX | + SHIM_HSI_STATUS_ROW; + dprint(L"Setting HSI to 0x%lx due to error: %r\n", decode_hsi_bits(hsi_status), efi_status); +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/mock-variables.c b/mock-variables.c index 0304454..723cdda 100644 --- a/mock-variables.c +++ b/mock-variables.c @@ -163,7 +163,7 @@ variable_cmp(const struct mock_variable * const v0, ret = CompareGuid(&v0->guid, &v1->guid); ret <<= 8ul; -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if (defined(SHIM_DEBUG) && SHIM_DEBUG > 3) printf("%s:%d:%s(): "GUID_FMT" %s "GUID_FMT" (0x%011"PRIx64" %"PRId64")\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(v0->guid), @@ -177,7 +177,7 @@ variable_cmp(const struct mock_variable * const v0, } ret = StrCmp(v0->name, v1->name); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if (defined(SHIM_DEBUG) && SHIM_DEBUG > 3) printf("%s:%d:%s(): \"%s\" %s \"%s\" (0x%02hhx (%d)\n", __FILE__, __LINE__-1, __func__, Str2str(v0->name), @@ -284,7 +284,7 @@ mock_gnvn_set_result(UINTN *size, CHAR16 *name, EFI_GUID *guid, *size = StrSize(result->name); status = EFI_BUFFER_TOO_SMALL; mock_gnvn_post_hook(size, name, guid, &status); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 3 printf("%s:%d:%s(): returning %lx\n", __FILE__, __LINE__-1, __func__, status); #endif @@ -297,7 +297,7 @@ mock_gnvn_set_result(UINTN *size, CHAR16 *name, EFI_GUID *guid, status = EFI_SUCCESS; mock_gnvn_post_hook(size, name, guid, &status); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 3 printf("%s:%d:%s(): returning %lx\n", __FILE__, __LINE__-1, __func__, status); #endif @@ -351,15 +351,20 @@ mock_get_next_variable_name(UINTN *size, CHAR16 *name, EFI_GUID *guid) struct mock_variable *var; var = list_entry(pos, struct mock_variable, list); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) +# if SHIM_DEBUG > 1 printf("%s:%d:%s(): candidate var:%p &var->guid:%p &var->list:%p\n", __FILE__, __LINE__-1, __func__, var, &var->guid, &var->list); +# elif SHIM_DEBUG > 0 + printf("%s:%d:%s(): candidate var:%p var->guid:" GUID_FMT"\n", + __FILE__, __LINE__-1, __func__, var, GUID_ARGS(var->guid)); +# endif #endif if (name[0] == 0) { if (CompareGuid(&var->guid, guid) == 0) { #if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) - printf("%s:%d:%s(): found\n", - __FILE__, __LINE__-1, __func__); + printf("%s:%d:%s(): found guid in entry var:%p var->name:%p\n", + __FILE__, __LINE__-1, __func__, var, var->name); #endif result = var; found = true; @@ -374,14 +379,14 @@ mock_get_next_variable_name(UINTN *size, CHAR16 *name, EFI_GUID *guid) continue; } -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(goal.guid), Str2str(goal.name), GUID_ARGS(var->guid), Str2str(var->name)); #endif if (variable_cmp(&goal, var) == 0) { -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): found\n", __FILE__, __LINE__-1, __func__); #endif @@ -391,15 +396,15 @@ mock_get_next_variable_name(UINTN *size, CHAR16 *name, EFI_GUID *guid) } #if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) if (result) { - printf("%s:%d:%s(): found:%d result:%p &result->guid:%p &result->list:%p\n" + printf("%s:%d:%s(): found:%d result:%p &result->guid:%p &result->list:%p\n", __FILE__, __LINE__-1, __func__, found, result, &result->guid, &result->list); printf("%s:%d:%s(): "GUID_FMT"-%s\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(result->guid), Str2str(result->name)); } else { - printf("%s:%d:%s(): not found\n", - __FILE__, __LINE__-1, __func__); + printf("%s:%d:%s(): not found (found:%d status:0x%016x)\n", + __FILE__, __LINE__-1, __func__, found, status); } #endif @@ -408,13 +413,25 @@ mock_get_next_variable_name(UINTN *size, CHAR16 *name, EFI_GUID *guid) status = EFI_NOT_FOUND; else status = EFI_INVALID_PARAMETER; +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 + printf("%s:%d:%s(): not found (found:%d status:0x%016x)\n", + __FILE__, __LINE__-1, __func__, found, status); +#endif mock_gnvn_post_hook(size, name, guid, &status); +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 + printf("%s:%d:%s(): not found (found:%d status:0x%016x)\n", + __FILE__, __LINE__-1, __func__, found, status); +#endif return status; } if (!result) { status = EFI_NOT_FOUND; mock_gnvn_post_hook(size, name, guid, &status); +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 + printf("%s:%d:%s(): found (found:%d status:0x%016x)\n", + __FILE__, __LINE__-1, __func__, found, status); +#endif return status; } @@ -678,7 +695,7 @@ mock_new_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, } var = (struct mock_variable *)buf; -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): var:%p &var->guid:%p &var->list:%p\n", __FILE__, __LINE__-1, __func__, var, &var->guid, &var->list); #endif @@ -695,7 +712,7 @@ mock_new_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, var->attrs = attrs; INIT_LIST_HEAD(&var->list); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): var: "GUID_FMT"-%s\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(var->guid), Str2str(var->name)); @@ -772,10 +789,10 @@ mock_set_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, } #endif -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) - printf("%s:%d:%s():Setting "GUID_FMT"-%s\n", +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 + printf("%s:%d:%s():Setting "GUID_FMT"-%s size:0x%"PRIx64"\n", __FILE__, __LINE__ - 1, __func__, - GUID_ARGS(*guid), Str2str(name)); + GUID_ARGS(*guid), Str2str(name), size); #endif switch (mock_variable_sort_policy) { case MOCK_SORT_PREPEND: @@ -800,7 +817,7 @@ mock_set_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, list_for_each_safe(pos, tmp, &mock_variables) { found = false; var = list_entry(pos, struct mock_variable, list); -#if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s(): varcmp("GUID_FMT"-%s, "GUID_FMT"-%s)\n", __FILE__, __LINE__-1, __func__, GUID_ARGS(goal.guid), Str2str(goal.name), @@ -832,32 +849,32 @@ mock_set_variable(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINTN size, if (found) break; } -#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s():var_list:%p &mock_variables:%p cmp:%ld\n", __FILE__, __LINE__ - 1, __func__, var_list, &mock_variables, cmp); #endif if (cmp != 0 || (cmp == 0 && var_list == &mock_variables)) { size_t totalsz = size + StrSize(name); -#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 printf("%s:%d:%s():var:%p attrs:0x%lx\n", __FILE__, __LINE__ - 1, __func__, var, attrs); #endif - status = mock_new_variable(name, guid, attrs, size, data, &var); + status = mock_sv_adjust_usage_data(attrs, size, -totalsz); if (EFI_ERROR(status)) { mock_sv_post_hook(name, guid, attrs, size, data, &status, CREATE); return status; } - mock_sv_adjust_usage_data(attrs, size, totalsz); + status = mock_new_variable(name, guid, attrs, size, data, &var); mock_sv_post_hook(name, guid, attrs, size, data, &status, CREATE); if (EFI_ERROR(status)) { - mock_sv_adjust_usage_data(attrs, 0, -totalsz); + mock_sv_adjust_usage_data(attrs, 0, totalsz); return status; } -#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 printf("%s:%d:%s(): Adding "GUID_FMT"-%s %s %s\n", __FILE__, __LINE__ - 1, __func__, GUID_ARGS(var->guid), Str2str(var->name), @@ -1001,6 +1018,20 @@ static struct mock_variable_limits default_limits[] = { {.attrs = 0, } }; +void +mock_set_usage_limits(list_t *limit_list, + struct mock_variable_limits *limits) +{ + INIT_LIST_HEAD(limit_list); + for (size_t i = 0; limits[i].attrs != 0; i++) { + INIT_LIST_HEAD(&limits[i].list); + list_add_tail(&limits[i].list, limit_list); + } + + mock_qvi_limits = limit_list; + mock_sv_limits = limit_list; +} + void mock_set_default_usage_limits(void) { @@ -1008,12 +1039,7 @@ mock_set_default_usage_limits(void) default_remaining_var_storage = 65536; default_max_var_size = 32768; - INIT_LIST_HEAD(&mock_default_variable_limits); - for (size_t i = 0; default_limits[i].attrs != 0; i++) { - INIT_LIST_HEAD(&default_limits[i].list); - list_add_tail(&default_limits[i].list, - &mock_default_variable_limits); - } + mock_set_usage_limits(&mock_default_variable_limits, &default_limits[0]); } void @@ -1079,7 +1105,8 @@ mock_load_one_variable(int dfd, const char * const dirname, char * const name) name[namelen-1] = 0; #if (defined(SHIM_DEBUG) && SHIM_DEBUG != 0) - printf("loading %s-%s\n", &name[namelen], name); + printf("%s:%d:%s(): loading %s-%s\n", __FILE__, __LINE__, __func__, + &name[namelen], name); #endif for (size_t i = 0; i < namelen; i++) namebuf[i] = name[i]; @@ -1109,6 +1136,9 @@ mock_load_variables(const char *const dirname, const char *filters[], DIR *d; struct dirent *entry; +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("Started loading variablles from \"%s\"\n", dirname); +#endif d = opendir(dirname); if (!d) err(1, "Could not open directory \"%s\"", dirname); @@ -1121,6 +1151,11 @@ mock_load_variables(const char *const dirname, const char *filters[], while ((entry = readdir(d)) != NULL) { size_t len = strlen(entry->d_name); bool found = false; + if (entry->d_type != DT_REG) + continue; +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("%s:%d:%s(): maybe adding entry \"%s\"\n", __FILE__, __LINE__, __func__, entry->d_name); +#endif if (filters && len > guidstr_size + 1) { char spacebuf[len]; @@ -1131,6 +1166,9 @@ mock_load_variables(const char *const dirname, const char *filters[], if (strlen(filters[i]) > len) continue; if (!strncmp(entry->d_name, filters[i], len)) { +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 2 + printf("%s:%d:%s(): filter matched for \"%s\" && \"%s\"\n", __FILE__, __LINE__, __func__, entry->d_name, filters[i]); +#endif found = true; break; } @@ -1138,9 +1176,23 @@ mock_load_variables(const char *const dirname, const char *filters[], } if ((found == false && filter_out == true) || (found == true && filter_out == false)) { +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("%s:%d:%s(): Adding \"%s\" because filter %s\n", + __FILE__, __LINE__-1, __func__, entry->d_name, + found ? "matched" : "did not match"); +#endif mock_load_one_variable(dfd, dirname, entry->d_name); + } else { +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("%s:%d:%s(): Skipping \"%s\" because filter %s\n", + __FILE__, __LINE__-1, __func__, entry->d_name, + found ? "matched" : "did not match"); +#endif } } +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("Done loading variablles from \"%s\"\n", dirname); +#endif closedir(d); #if 0 diff --git a/mok.c b/mok.c index 0ac3415..bf71542 100644 --- a/mok.c +++ b/mok.c @@ -34,6 +34,58 @@ static BOOLEAN check_var(CHAR16 *varname) efi_status_; \ }) +static UINTN +format_hsi_status(UINT8 *buf, size_t sz, + struct mok_state_variable *msv UNUSED) +{ + const char heapx[] = "heap-is-executable: "; + const char stackx[] = "\nstack-is-executable: "; + const char row[] = "\nro-sections-are-writable: "; + const char hasmap[] = "\nhas-memory-attribute-protocol: "; + 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; + + /* + * sizeof includes the trailing NUL which is where our 0 or 1 value + * fits + */ + UINTN ret = sizeof(heapx) + sizeof(stackx) + + sizeof(row) + sizeof(hasmap) + + sizeof(hasdxeservices) + sizeof(hasdsgmsd) + + sizeof(hasdssmsa) + sizeof(shimhasnx) + + sizeof(finale); + + if (buf == 0 || sz < ret) { + return ret; + } + + buf[0] = 0; + pos = (char *)buf; + pos = stpcpy(pos, heapx); + pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HEAPX) ? "1" : "0"); + pos = stpcpy(pos, stackx); + pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_STACKX) ? "1" : "0"); + pos = stpcpy(pos, row); + pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_ROW) ? "1" : "0"); + pos = stpcpy(pos, hasmap); + pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASMAP) ? "1" : "0"); + pos = stpcpy(pos, hasdxeservices); + pos = stpcpy(pos, (hsi_status & SHIM_HSI_STATUS_HASDST) ? "1" : "0"); + pos = stpcpy(pos, hasdsgmsd); + 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; +} + /* * If the OS has set any of these variables we need to drop into MOK and * handle them appropriately @@ -50,6 +102,32 @@ static EFI_STATUS check_mok_request(EFI_HANDLE image_handle) efi_status = start_image(image_handle, MOK_MANAGER); if (EFI_ERROR(efi_status)) { + /* + * We don't do this in the unit tests because we + * don't have simulation for console_countdown() + * and similar. + */ +#ifndef SHIM_UNIT_TEST + EFI_STATUS efi_status_2; + EFI_LOADED_IMAGE *li; + efi_status_2 = BS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID, + (void **)&li); + if (EFI_ERROR(efi_status_2)) + perror (L"Failed to get image: %r\n", efi_status_2); + else if (is_removable_media_path(li) && + efi_status == EFI_NOT_FOUND) { + CHAR16 *title = L"Could not find MokManager"; + CHAR16 *message = L"MokManager is missing on removable media."; + /* + * This occurs when system is booting on + * hard disk's EFI/BOOT/BOOTxxx.EFI entry + * while it should have booted on + * EFI//shimxxx.efi entry + */ + console_countdown(title, message, 10); + RT->ResetSystem(EfiResetWarm, EFI_SUCCESS, 0, NULL); + } +#endif perror(L"Failed to start MokManager: %r\n", efi_status); return efi_status; } @@ -80,12 +158,6 @@ categorize_deauthorized(struct mok_state_variable *v) return VENDOR_ADDEND_DB; } -#define MOK_MIRROR_KEYDB 0x01 -#define MOK_MIRROR_DELETE_FIRST 0x02 -#define MOK_VARIABLE_MEASURE 0x04 -#define MOK_VARIABLE_LOG 0x08 -#define MOK_VARIABLE_INVERSE 0x10 - struct mok_state_variable mok_state_variable_data[] = { {.name = L"MokList", .name8 = "MokList", @@ -196,6 +268,161 @@ struct mok_state_variable mok_state_variable_data[] = { .pcr = 14, .state = &mok_policy, }, + {.name = L"HSIStatus", + .name8 = "HSIStatus", + .rtname = L"HSIStatus", + .rtname8 = "HSIStatus", + .guid = &SHIM_LOCK_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + .format = format_hsi_status, + }, + {.name = L"AuditMode", + .name8 = "AuditMode", + .rtname = L"AuditMode", + .rtname8 = "AuditMode", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"BootOrder", + .name8 = "BootOrder", + .rtname = L"BootOrder", + .rtname8 = "BootOrder", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"BootCurrent", + .name8 = "BootCurrent", + .rtname = L"BootCurrent", + .rtname8 = "BootCurrent", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"BootNext", + .name8 = "BootNext", + .rtname = L"BootNext", + .rtname8 = "BootNext", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0000", + .name8 = "Boot0000", + .rtname = L"Boot0000", + .rtname8 = "Boot0000", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0001", + .name8 = "Boot0001", + .rtname = L"Boot0001", + .rtname8 = "Boot0001", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0002", + .name8 = "Boot0002", + .rtname = L"Boot0002", + .rtname8 = "Boot0002", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0003", + .name8 = "Boot0003", + .rtname = L"Boot0003", + .rtname8 = "Boot0003", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0004", + .name8 = "Boot0004", + .rtname = L"Boot0004", + .rtname8 = "Boot0004", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0005", + .name8 = "Boot0005", + .rtname = L"Boot0005", + .rtname8 = "Boot0005", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Boot0006", + .name8 = "Boot0006", + .rtname = L"Boot0006", + .rtname8 = "Boot0006", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"DeployedMode", + .name8 = "DeployedMode", + .rtname = L"DeployedMode", + .rtname8 = "DeployedMode", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"SecureBoot", + .name8 = "SecureBoot", + .rtname = L"SecureBoot", + .rtname8 = "SecureBoot", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"SetupMode", + .name8 = "SetupMode", + .rtname = L"SetupMode", + .rtname8 = "SetupMode", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"SignatureSupport", + .name8 = "SignatureSupport", + .rtname = L"SignatureSupport", + .rtname8 = "SignatureSupport", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Timeout", + .name8 = "Timeout", + .rtname = L"Timeout", + .rtname8 = "Timeout", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"PK", + .name8 = "PK", + .rtname = L"PK", + .rtname8 = "PK", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"KEK", + .name8 = "KEK", + .rtname = L"KEK", + .rtname8 = "KEK", + .guid = &GV_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"db", + .name8 = "db", + .rtname = L"db", + .rtname8 = "db", + .guid = &SIG_DB, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"dbx", + .name8 = "dbx", + .rtname = L"dbx", + .rtname8 = "dbx", + .guid = &SIG_DB, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, + {.name = L"Kernel_SkuSiStatus", + .name8 = "Kernel_SkuSiStatus", + .rtname = L"Kernel_SkuSiStatus", + .rtname8 = "Kernel_SkuSiStatus", + .guid = &SECUREBOOT_EFI_NAMESPACE_GUID, + .flags = MOK_VARIABLE_CONFIG_ONLY, + }, { NULL, } }; size_t n_mok_state_variables = sizeof(mok_state_variable_data) / sizeof(mok_state_variable_data[0]); @@ -217,6 +444,47 @@ typedef UINTN SIZE_T; #define EFI_MAJOR_VERSION(tablep) ((UINT16)((((tablep)->Hdr.Revision) >> 16) & 0xfffful)) #define EFI_MINOR_VERSION(tablep) ((UINT16)(((tablep)->Hdr.Revision) & 0xfffful)) +static BOOLEAN is_apple_firmware_vendor(void) +{ + CHAR16 vendorbuf[6] = L""; + CHAR16 *vendor = ST->FirmwareVendor; + if (!vendor) + return FALSE; + + ZeroMem(vendorbuf, sizeof(vendorbuf)); + + /* + * We've had a problem where ST->FirmwareVendor is only as big as + * it needs to be (or at least less than the 200 bytes we formerly + * defined vendorbuf as) and it's up against a page that's not + * mapped readable, so we take a fault and reset when copying from + * it. + * + * We modeled this after kernel, which has the 200 byte CHAR16 + * array and copies 198 bytes into it, so that there's a NUL + * terminator. They solve this issue by mapping the whole 200 + * bytes unconditionally and then unmapping it after the copy, but + * we can't take that approach because we don't necessarily have + * page permission primitives at all. + * + * The 200 bytes (CHAR16 [100]) is an arbitrary number anyway, but + * it's likely larger than any sane vendor name, and we still want + * to do the copy into an array larger than our copied data because + * that's how we guard against failure to terminate with a NUL. + * + * So right now we're only copying ten bytes, because Apple is the + * only vendor we're testing against. + */ + CopyMem(vendorbuf, vendor, 10); + + dprint(L"FirmwareVendor: \"%s\"\n", vendor); + + if (StrnCmp(vendor, L"Apple", 5) == 0) + return TRUE; + + return FALSE; +} + static EFI_STATUS get_max_var_sz(UINT32 attrs, SIZE_T *max_var_szp) { @@ -226,7 +494,7 @@ get_max_var_sz(UINT32 attrs, SIZE_T *max_var_szp) uint64_t max_var_sz = 0; *max_var_szp = 0; - if (EFI_MAJOR_VERSION(RT) < 2) { + if (EFI_MAJOR_VERSION(RT) < 2 || is_apple_firmware_vendor()) { dprint(L"EFI %d.%d; no RT->QueryVariableInfo(). Using 1024!\n", EFI_MAJOR_VERSION(RT), EFI_MINOR_VERSION(RT)); max_var_sz = remaining_sz = max_storage_sz = 1024; @@ -310,7 +578,7 @@ mirror_one_esl(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, } static EFI_STATUS -mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, +mirror_mok_db(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, UINT8 *FullData, SIZE_T FullDataSize, BOOLEAN only_first) { EFI_STATUS efi_status = EFI_SUCCESS; @@ -336,15 +604,13 @@ mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, return efi_status; } - CHAR16 *namen; - CHAR8 *namen8; + CHAR16 *namen = NULL; UINTN namelen, namesz; namelen = StrLen(name); namesz = namelen * 2; if (only_first) { namen = name; - namen8 = name8; } else { namelen += 18; namesz += 34; @@ -353,12 +619,6 @@ mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, LogError(L"Could not allocate %lu bytes", namesz); return EFI_OUT_OF_RESOURCES; } - namen8 = AllocateZeroPool(namelen); - if (!namen8) { - FreePool(namen); - LogError(L"Could not allocate %lu bytes", namelen); - return EFI_OUT_OF_RESOURCES; - } } UINTN pos, i; @@ -400,11 +660,6 @@ mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, if (!only_first) { SPrint(namen, namelen, L"%s%lu", name, i); namen[namelen-1] = 0; - /* uggggh */ - UINTN j; - for (j = 0; j < namelen; j++) - namen8[j] = (CHAR8)(namen[j] & 0xff); - namen8[namelen - 1] = 0; } /* @@ -417,7 +672,6 @@ mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, efi_status); if (!only_first) { FreePool(namen); - FreePool(namen8); } return efi_status; } @@ -472,6 +726,9 @@ mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, break; i++; } + if (namen && namen != name) { + FreePool(namen); + } if (EFI_ERROR(efi_status)) { perror(L"Failed to set %s: %r\n", name, efi_status); @@ -515,6 +772,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, EFI_STATUS efi_status = EFI_SUCCESS; uint8_t *FullData = NULL; size_t FullDataSize = 0; + bool allocated_full_data = false; vendor_addend_category_t addend_category = VENDOR_ADDEND_NONE; uint8_t *p = NULL; uint32_t attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | @@ -579,6 +837,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, if (efi_status != EFI_BUFFER_TOO_SMALL) { perror(L"Could not add built-in cert to %s: %r\n", v->name, efi_status); + goto err; return efi_status; } FullDataSize += addend_esl_sz; @@ -663,6 +922,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, FullDataSize, v->name); return EFI_OUT_OF_RESOURCES; } + allocated_full_data = true; p = FullData; } } @@ -692,7 +952,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, if (EFI_ERROR(efi_status)) { perror(L"Could not add built-in cert to %s: %r\n", v->name, efi_status); - return efi_status; + goto err; } p += addend_esl_sz; dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", @@ -719,7 +979,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, if (EFI_ERROR(efi_status)) { perror(L"Could not add built-in cert to %s: %r\n", v->name, efi_status); - return efi_status; + goto err; } p += build_cert_esl_sz; dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", @@ -758,7 +1018,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, if (EFI_ERROR(efi_status)) { perror(L"Failed to allocate %lu bytes for %s\n", FullDataSize, v->name); - return efi_status; + goto err; } p = FullData + FullDataSize; dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", @@ -767,15 +1027,17 @@ mirror_one_mok_variable(struct mok_state_variable *v, dprint(L"FullDataSize:%lu FullData:0x%llx p:0x%llx pos:%lld\n", FullDataSize, FullData, p, p-(uintptr_t)FullData); - if (FullDataSize && v->flags & MOK_MIRROR_KEYDB) { + if (FullDataSize && v->flags & MOK_MIRROR_KEYDB && + !(v->flags & MOK_VARIABLE_CONFIG_ONLY)) { dprint(L"calling mirror_mok_db(\"%s\", datasz=%lu)\n", v->rtname, FullDataSize); - efi_status = mirror_mok_db(v->rtname, (CHAR8 *)v->rtname8, v->guid, + efi_status = mirror_mok_db(v->rtname, v->guid, attrs, FullData, FullDataSize, only_first); dprint(L"mirror_mok_db(\"%s\", datasz=%lu) returned %r\n", v->rtname, FullDataSize, efi_status); - } else if (FullDataSize && only_first) { + } else if (FullDataSize && only_first && + !(v->flags & MOK_VARIABLE_CONFIG_ONLY)) { efi_status = SetVariable(v->rtname, v->guid, attrs, FullDataSize, FullData); } @@ -789,7 +1051,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, if (EFI_ERROR(efi_status)) { dprint(L"tpm_measure_variable(\"%s\",%lu,0x%llx)->%r\n", v->name, FullDataSize, FullData, efi_status); - return efi_status; + goto err; } } @@ -806,7 +1068,7 @@ mirror_one_mok_variable(struct mok_state_variable *v, dprint(L"tpm_log_event(0x%llx, %lu, %lu, \"%s\")->%r\n", FullData, FullDataSize, v->pcr, v->name, efi_status); - return efi_status; + goto err; } } @@ -820,6 +1082,10 @@ mirror_one_mok_variable(struct mok_state_variable *v, v->data_size = FullDataSize; dprint(L"returning %r\n", efi_status); return efi_status; +err: + if (FullData && allocated_full_data) + FreePool(FullData); + return efi_status; } /* @@ -871,7 +1137,8 @@ EFI_STATUS import_one_mok_state(struct mok_state_variable *v, dprint(L"importing mok state for \"%s\"\n", v->name); - if (!v->data && !v->data_size) { + if (!v->data && !v->data_size && + !(v->flags & MOK_VARIABLE_CONFIG_ONLY)) { efi_status = get_variable_attr(v->name, &v->data, &v->data_size, *v->guid, &attrs); @@ -913,6 +1180,36 @@ EFI_STATUS import_one_mok_state(struct mok_state_variable *v, } } } + + if (v->format) { + v->data_size = v->format(NULL, 0, v); + if (v->data_size > 0) { + v->data = AllocatePool(v->data_size); + if (!v->data) { + perror(L"Could not allocate %lu bytes for %s\n", + v->data_size, v->name); + return EFI_OUT_OF_RESOURCES; + } + } + v->format(v->data, v->data_size, v); + } + + if (!v->data && !v->data_size && + (v->flags & MOK_VARIABLE_CONFIG_ONLY) && + !v->format) { + efi_status = get_variable_attr(v->name, + &v->data, &v->data_size, + *v->guid, &attrs); + if (EFI_ERROR(efi_status)) { + dprint(L"Couldn't get variable \"%s\" for mirroring: %r\n", + v->name, efi_status); + if (efi_status != EFI_NOT_FOUND) + return efi_status; + v->data = NULL; + v->data_size = 0; + } + } + if (delete == TRUE) { perror(L"Deleting bad variable %s\n", v->name); efi_status = LibDeleteVariable(v->name, v->guid); @@ -1004,6 +1301,8 @@ EFI_STATUS import_mok_state(EFI_HANDLE image_handle) config_table = NULL; } else { ZeroMem(config_table, npages << EFI_PAGE_SHIFT); + mok_config_table = (EFI_PHYSICAL_ADDRESS)(uintptr_t)config_table; + mok_config_table_pages = npages; } } diff --git a/netboot.c b/netboot.c index d8b1093..4676542 100644 --- a/netboot.c +++ b/netboot.c @@ -16,6 +16,16 @@ #define ntohs(x) __builtin_bswap16(x) /* supported both by GCC and clang */ #define htons(x) ntohs(x) +/* TFTP error codes from RFC 1350 */ +#define TFTP_ERROR_NOT_DEFINED 0 /* Not defined, see error message (if any). */ +#define TFTP_ERROR_NOT_FOUND 1 /* File not found. */ +#define TFTP_ERROR_ACCESS 2 /* Access violation. */ +#define TFTP_ERROR_NO_SPACE 3 /* Disk full or allocation exceeded. */ +#define TFTP_ERROR_ILLEGAL_OP 4 /* Illegal TFTP operation. */ +#define TFTP_ERROR_UNKNOWN_ID 5 /* Unknown transfer ID. */ +#define TFTP_ERROR_EXISTS 6 /* File already exists. */ +#define TFTP_ERROR_NO_USER 7 /* No such user. */ + static EFI_PXE_BASE_CODE *pxe; static EFI_IP_ADDRESS tftp_addr; static CHAR8 *full_path; @@ -333,7 +343,33 @@ EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED, CHAR8 *netbootname) return efi_status; } -EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT64 *bufsiz) +/* Convert a TFTP error code to an EFI status code. */ +static EFI_STATUS +status_from_error(UINT8 error_code) +{ + switch (error_code) { + case TFTP_ERROR_NOT_FOUND: + return EFI_NOT_FOUND; + case TFTP_ERROR_ACCESS: + case TFTP_ERROR_NO_USER: + return EFI_ACCESS_DENIED; + case TFTP_ERROR_NO_SPACE: + return EFI_VOLUME_FULL; + case TFTP_ERROR_ILLEGAL_OP: + return EFI_PROTOCOL_ERROR; + case TFTP_ERROR_UNKNOWN_ID: + return EFI_INVALID_PARAMETER; + case TFTP_ERROR_EXISTS: + return EFI_WRITE_PROTECTED; + case TFTP_ERROR_NOT_DEFINED: + default: + /* Use a generic TFTP error for anything else. */ + return EFI_TFTP_ERROR; + } +} + +EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, + UINT64 *bufsiz, int flags) { EFI_STATUS efi_status; EFI_PXE_BASE_CODE_TFTP_OPCODE read = EFI_PXE_BASE_CODE_TFTP_READ_FILE; @@ -341,7 +377,8 @@ EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT BOOLEAN nobuffer = FALSE; UINTN blksz = 512; - console_print(L"Fetching Netboot Image %a\n", full_path); + if (~flags & SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE) + console_print(L"Fetching Netboot Image %a\n", full_path); if (*buffer == NULL) { *buffer = AllocatePool(4096 * 1024); if (!*buffer) @@ -362,8 +399,31 @@ try_again: goto try_again; } - if (EFI_ERROR(efi_status) && *buffer) { - FreePool(*buffer); + if (EFI_ERROR(efi_status)) { + if (pxe->Mode->TftpErrorReceived) { + if (~flags & SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE) + console_print(L"TFTP error %u: %a\n", + pxe->Mode->TftpError.ErrorCode, + pxe->Mode->TftpError.ErrorString); + + efi_status = status_from_error(pxe->Mode->TftpError.ErrorCode); + } else if (efi_status == EFI_TFTP_ERROR) { + /* + * Unfortunately, some firmware doesn't fill in the + * error details. Treat all TFTP errors like file not + * found so shim falls back to the default loader. + * + * https://github.com/tianocore/edk2/pull/6287 + */ + if (~flags & SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE) + console_print(L"Unknown TFTP error, treating " + "as file not found\n"); + efi_status = EFI_NOT_FOUND; + } + + if (*buffer) { + FreePool(*buffer); + } } return efi_status; } diff --git a/pe-relocate.c b/pe-relocate.c index bde7172..6f2af4e 100644 --- a/pe-relocate.c +++ b/pe-relocate.c @@ -368,21 +368,28 @@ 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; unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize; unsigned long FileAlignment = 0; - UINT16 DllFlags; size_t dos_sz = 0; size_t tmpsz0, tmpsz1; + /* + * It has to be big enough to hold the DOS header; right now we + * don't support images without it. + */ if (datasize < sizeof (*DosHdr)) { perror(L"Invalid image\n"); return EFI_UNSUPPORTED; } + /* + * It must have a valid DOS header + */ if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { if (DosHdr->e_lfanew < sizeof (*DosHdr) || DosHdr->e_lfanew > datasize - 4) { @@ -394,11 +401,17 @@ read_header(void *data, unsigned int datasize, PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); } + /* + * Has to be big enough to hold a PE header + */ if (datasize - dos_sz < sizeof (PEHdr->Pe32)) { perror(L"Invalid image\n"); return EFI_UNSUPPORTED; } + /* + * If it's 64-bit, it has to hold the PE32+ header + */ if (image_is_64_bit(PEHdr) && (datasize - dos_sz < sizeof (PEHdr->Pe32Plus))) { perror(L"Invalid image\n"); @@ -426,6 +439,13 @@ read_header(void *data, unsigned int datasize, OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32); } + /* + * Set up our file alignment and section alignment expectations to + * be mostly sane. + * + * This probably should have a check for /power/ of two not just + * multiple, but in practice it hasn't been an issue. + */ if (FileAlignment % 2 != 0) { perror(L"File Alignment is invalid (%d)\n", FileAlignment); return EFI_UNSUPPORTED; @@ -439,11 +459,24 @@ read_header(void *data, unsigned int datasize, context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections; + /* + * Check and make sure the space for data directory entries is as + * large as we expect. + * + * In truth we could set this number smaller if we needed to - + * currently it's 16 but we only care about #4 and #5 (the fifth + * and sixth ones) - but it hasn't been a problem. If it's too + * weird we'll fail trying to allocate it. + */ if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) { perror(L"Image header too large\n"); return EFI_UNSUPPORTED; } + /* + * Check that the OptionalHeaderSize and the end of the Data + * Directory match up sanely + */ if (checked_mul(sizeof(EFI_IMAGE_DATA_DIRECTORY), EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES, &tmpsz0) || checked_sub(OptHeaderSize, tmpsz0, &HeaderWithoutDataDir) || checked_sub((size_t)PEHdr->Pe32.FileHeader.SizeOfOptionalHeader, HeaderWithoutDataDir, &tmpsz0) || @@ -453,6 +486,9 @@ read_header(void *data, unsigned int datasize, return EFI_UNSUPPORTED; } + /* + * Check that the SectionHeaderOffset field is within the image. + */ if (checked_add((size_t)DosHdr->e_lfanew, sizeof(UINT32), &tmpsz0) || checked_add(tmpsz0, sizeof(EFI_IMAGE_FILE_HEADER), &tmpsz0) || checked_add(tmpsz0, PEHdr->Pe32.FileHeader.SizeOfOptionalHeader, &SectionHeaderOffset)) { @@ -460,18 +496,29 @@ read_header(void *data, unsigned int datasize, return EFI_UNSUPPORTED; } + /* + * Check that the sections headers themselves are within the image + */ if (checked_sub((size_t)context->ImageSize, SectionHeaderOffset, &tmpsz0) || (tmpsz0 / EFI_IMAGE_SIZEOF_SECTION_HEADER <= context->NumberOfSections)) { perror(L"Image sections overflow image size\n"); return EFI_UNSUPPORTED; } + /* + * Check that the section headers fit within the total headers + */ if (checked_sub((size_t)context->SizeOfHeaders, SectionHeaderOffset, &tmpsz0) || (tmpsz0 / EFI_IMAGE_SIZEOF_SECTION_HEADER < (UINT32)context->NumberOfSections)) { perror(L"Image sections overflow section headers\n"); return EFI_UNSUPPORTED; } + /* + * Check that the section headers are actually within the data + * we've read. Might be duplicative of the ImageSize one, but it + * won't hurt. + */ if (checked_mul((size_t)context->NumberOfSections, sizeof(EFI_IMAGE_SECTION_HEADER), &tmpsz0) || checked_add(tmpsz0, SectionHeaderOffset, &tmpsz0) || (tmpsz0 > datasize)) { @@ -479,6 +526,9 @@ read_header(void *data, unsigned int datasize, return EFI_UNSUPPORTED; } + /* + * Check that the optional header fits in the image. + */ if (checked_sub((size_t)(uintptr_t)PEHdr, (size_t)(uintptr_t)data, &tmpsz0) || checked_add(tmpsz0, sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION), &tmpsz0) || (tmpsz0 > datasize)) { @@ -486,11 +536,17 @@ read_header(void *data, unsigned int datasize, return EFI_UNSUPPORTED; } + /* + * Check that this claims to be a PE binary + */ if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { perror(L"Unsupported image type\n"); return EFI_UNSUPPORTED; } + /* + * Check that relocations aren't stripped, because that won't work. + */ if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { perror(L"Unsupported image - Relocations have been stripped\n"); return EFI_UNSUPPORTED; @@ -503,27 +559,37 @@ 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 NX_COMPAT is required, check that it's set. + */ 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; } + /* + * Check that the file header fits within the image. + */ if (checked_add((size_t)(uintptr_t)PEHdr, PEHdr->Pe32.FileHeader.SizeOfOptionalHeader, &tmpsz0) || checked_add(tmpsz0, sizeof(UINT32), &tmpsz0) || checked_add(tmpsz0, sizeof(EFI_IMAGE_FILE_HEADER), &tmpsz0)) { perror(L"Invalid image\n"); return EFI_UNSUPPORTED; } + + /* + * Check that the first section header is within the image data + */ context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)(uintptr_t)tmpsz0; if ((uint64_t)(uintptr_t)(context->FirstSection) > (uint64_t)(uintptr_t)data + datasize) { @@ -531,24 +597,69 @@ read_header(void *data, unsigned int datasize, return EFI_UNSUPPORTED; } + /* + * Check that the headers fit within the image. + */ if (context->ImageSize < context->SizeOfHeaders) { perror(L"Invalid image\n"); return EFI_UNSUPPORTED; } + /* + * check that the data directory fits within the image. + */ if (checked_sub((size_t)(uintptr_t)context->SecDir, (size_t)(uintptr_t)data, &tmpsz0) || (tmpsz0 > 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)) { + /* + * Check that the certificate table is within the binary - + * "VirtualAddress" is a misnomer here, it's a relative offset to + * the image's load address, so compared to datasize it should be + * absolute. + */ + 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; } 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/pe.c b/pe.c index 3305601..d785c44 100644 --- a/pe.c +++ b/pe.c @@ -395,149 +395,6 @@ err: return efi_status; } -static inline uint64_t -shim_mem_attrs_to_uefi_mem_attrs (uint64_t attrs) -{ - uint64_t ret = EFI_MEMORY_RP | - EFI_MEMORY_RO | - EFI_MEMORY_XP; - - if (attrs & MEM_ATTR_R) - ret &= ~EFI_MEMORY_RP; - - if (attrs & MEM_ATTR_W) - ret &= ~EFI_MEMORY_RO; - - if (attrs & MEM_ATTR_X) - ret &= ~EFI_MEMORY_XP; - - return ret; -} - -static inline uint64_t -uefi_mem_attrs_to_shim_mem_attrs (uint64_t attrs) -{ - uint64_t ret = MEM_ATTR_R | - MEM_ATTR_W | - MEM_ATTR_X; - - if (attrs & EFI_MEMORY_RP) - ret &= ~MEM_ATTR_R; - - if (attrs & EFI_MEMORY_RO) - ret &= ~MEM_ATTR_W; - - if (attrs & EFI_MEMORY_XP) - ret &= ~MEM_ATTR_X; - - return ret; -} - -static EFI_STATUS -get_mem_attrs (uintptr_t addr, size_t size, uint64_t *attrs) -{ - EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; - EFI_PHYSICAL_ADDRESS physaddr = addr; - EFI_STATUS efi_status; - - efi_status = LibLocateProtocol(&EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID, - (VOID **)&proto); - if (EFI_ERROR(efi_status) || !proto) - return efi_status; - - if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0 || attrs == NULL) { - dprint(L"%a called on 0x%llx-0x%llx and attrs 0x%llx\n", - __func__, (unsigned long long)physaddr, - (unsigned long long)(physaddr+size-1), - attrs); - return EFI_SUCCESS; - } - - efi_status = proto->GetMemoryAttributes(proto, physaddr, size, attrs); - *attrs = uefi_mem_attrs_to_shim_mem_attrs (*attrs); - - return efi_status; -} - -static EFI_STATUS -update_mem_attrs(uintptr_t addr, uint64_t size, - uint64_t set_attrs, uint64_t clear_attrs) -{ - EFI_MEMORY_ATTRIBUTE_PROTOCOL *proto = NULL; - EFI_PHYSICAL_ADDRESS physaddr = addr; - EFI_STATUS efi_status, ret; - uint64_t before = 0, after = 0, uefi_set_attrs, uefi_clear_attrs; - - efi_status = LibLocateProtocol(&EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID, - (VOID **)&proto); - if (EFI_ERROR(efi_status) || !proto) - return efi_status; - - efi_status = get_mem_attrs (addr, size, &before); - if (EFI_ERROR(efi_status)) - dprint(L"get_mem_attrs(0x%llx, 0x%llx, 0x%llx) -> 0x%lx\n", - (unsigned long long)addr, (unsigned long long)size, - &before, efi_status); - - if (!IS_PAGE_ALIGNED(physaddr) || !IS_PAGE_ALIGNED(size) || size == 0) { - dprint(L"%a called on 0x%llx-0x%llx (size 0x%llx) +%a%a%a -%a%a%a\n", - __func__, (unsigned long long)physaddr, - (unsigned long long)(physaddr + size - 1), - (unsigned long long)size, - (set_attrs & MEM_ATTR_R) ? "r" : "", - (set_attrs & MEM_ATTR_W) ? "w" : "", - (set_attrs & MEM_ATTR_X) ? "x" : "", - (clear_attrs & MEM_ATTR_R) ? "r" : "", - (clear_attrs & MEM_ATTR_W) ? "w" : "", - (clear_attrs & MEM_ATTR_X) ? "x" : ""); - return 0; - } - - uefi_set_attrs = shim_mem_attrs_to_uefi_mem_attrs (set_attrs); - dprint("translating set_attrs from 0x%lx to 0x%lx\n", set_attrs, uefi_set_attrs); - uefi_clear_attrs = shim_mem_attrs_to_uefi_mem_attrs (clear_attrs); - dprint("translating clear_attrs from 0x%lx to 0x%lx\n", clear_attrs, uefi_clear_attrs); - efi_status = EFI_SUCCESS; - if (uefi_set_attrs) { - efi_status = proto->SetMemoryAttributes(proto, physaddr, size, uefi_set_attrs); - if (EFI_ERROR(efi_status)) { - dprint(L"Failed to set memory attrs:0x%0x physaddr:0x%llx size:0x%0lx status:%r\n", - uefi_set_attrs, physaddr, size, efi_status); - } - } - if (!EFI_ERROR(efi_status) && uefi_clear_attrs) { - efi_status = proto->ClearMemoryAttributes(proto, physaddr, size, uefi_clear_attrs); - if (EFI_ERROR(efi_status)) { - dprint(L"Failed to clear memory attrs:0x%0x physaddr:0x%llx size:0x%0lx status:%r\n", - uefi_clear_attrs, physaddr, size, efi_status); - } - } - ret = efi_status; - - efi_status = get_mem_attrs (addr, size, &after); - if (EFI_ERROR(efi_status)) - dprint(L"get_mem_attrs(0x%llx, %llu, 0x%llx) -> 0x%lx\n", - (unsigned long long)addr, (unsigned long long)size, - &after, efi_status); - - dprint(L"set +%a%a%a -%a%a%a on 0x%llx-0x%llx before:%c%c%c after:%c%c%c\n", - (set_attrs & MEM_ATTR_R) ? "r" : "", - (set_attrs & MEM_ATTR_W) ? "w" : "", - (set_attrs & MEM_ATTR_X) ? "x" : "", - (clear_attrs & MEM_ATTR_R) ? "r" : "", - (clear_attrs & MEM_ATTR_W) ? "w" : "", - (clear_attrs & MEM_ATTR_X) ? "x" : "", - (unsigned long long)addr, (unsigned long long)(addr + size - 1), - (before & MEM_ATTR_R) ? 'r' : '-', - (before & MEM_ATTR_W) ? 'w' : '-', - (before & MEM_ATTR_X) ? 'x' : '-', - (after & MEM_ATTR_R) ? 'r' : '-', - (after & MEM_ATTR_W) ? 'w' : '-', - (after & MEM_ATTR_X) ? 'x' : '-'); - - return ret; -} - EFI_STATUS verify_image(void *data, unsigned int datasize, EFI_LOADED_IMAGE *li, PE_COFF_LOADER_IMAGE_CONTEXT *context) @@ -549,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; @@ -625,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; @@ -640,7 +497,7 @@ handle_image (void *data, unsigned int datasize, sha1hash); if (EFI_ERROR(efi_status)) { - if (verbose) + if (verbose || in_protocol) console_print(L"Verification failed: %r\n", efi_status); else console_error(L"Verification failed", efi_status); diff --git a/post-process-pe.c b/post-process-pe.c index de8f4a3..008da93 100644 --- a/post-process-pe.c +++ b/post-process-pe.c @@ -43,6 +43,7 @@ static int verbosity; }) static bool set_nx_compat = false; +static bool require_nx_compat = false; typedef uint8_t UINT8; typedef uint16_t UINT16; @@ -162,6 +163,7 @@ load_pe(const char *const file, void *const data, const size_t datasize, ctx->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage; ctx->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment; + ctx->DllCharacteristics = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment; OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64); } else { @@ -172,6 +174,7 @@ load_pe(const char *const file, void *const data, const size_t datasize, ctx->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage; ctx->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment; + ctx->DllCharacteristics = PEHdr->Pe32.OptionalHeader.DllCharacteristics; FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment; OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32); } @@ -358,6 +361,50 @@ set_dll_characteristics(PE_COFF_LOADER_IMAGE_CONTEXT *ctx) } else { ctx->PEHdr->Pe32.OptionalHeader.DllCharacteristics = newflags; } + ctx->DllCharacteristics = newflags; +} + +static int +validate_nx_compat(PE_COFF_LOADER_IMAGE_CONTEXT *ctx) +{ + EFI_IMAGE_SECTION_HEADER *Section; + int i; + bool nx_compat_flag; + const int level = require_nx_compat ? ERROR : WARNING; + int ret = 0; + + nx_compat_flag = EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT + & ctx->DllCharacteristics; + debug(NOISE, "NX-Compat-Flag: %s\n", nx_compat_flag ? "set" : "unset"); + if (!nx_compat_flag) { + debug(level, "NX Compatibility flag is not set\n"); + if (require_nx_compat) + ret = -1; + } + + debug(NOISE, "Section alignment is 0x%x, page size is 0x%x\n", + ctx->SectionAlignment, PAGE_SIZE); + if (ctx->SectionAlignment != PAGE_SIZE) { + debug(level, "Section alignment is not page aligned\n"); + if (require_nx_compat) + ret = -1; + } + + Section = ctx->FirstSection; + for (i=0, Section = ctx->FirstSection; i < ctx->NumberOfSections; i++, Section++) { + debug(NOISE, "Section %d has WRITE=%d and EXECUTE=%d\n", i, + (Section->Characteristics & EFI_IMAGE_SCN_MEM_WRITE) ? 1 : 0, + (Section->Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE) ? 1 : 0); + + if ((Section->Characteristics & EFI_IMAGE_SCN_MEM_WRITE) && + (Section->Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE)) { + debug(level, "Section %d is writable and executable\n", i); + if (require_nx_compat) + ret = -1; + } + } + + return ret; } static void @@ -449,6 +496,10 @@ handle_one(char *f) set_dll_characteristics(&ctx); + rc = validate_nx_compat(&ctx); + if (rc < 0) + err(2, "NX compatibility check failed\n"); + fix_timestamp(&ctx); fix_checksum(&ctx, map, sz); @@ -483,6 +534,7 @@ static void __attribute__((__noreturn__)) usage(int status) fprintf(out, " -v Be more verbose\n"); fprintf(out, " -N Disable the NX compatibility flag\n"); fprintf(out, " -n Enable the NX compatibility flag\n"); + fprintf(out, " -x Error on NX incompatibility\n"); fprintf(out, " -h Print this help text and exit\n"); exit(status); @@ -510,11 +562,14 @@ int main(int argc, char **argv) {.name = "verbose", .val = 'v', }, + {.name = "error-nx-compat", + .val = 'x', + }, {.name = ""} }; int longindex = -1; - while ((i = getopt_long(argc, argv, "hNnqv", options, &longindex)) != -1) { + while ((i = getopt_long(argc, argv, "hNnqvx", options, &longindex)) != -1) { switch (i) { case 'h': case '?': @@ -532,6 +587,9 @@ int main(int argc, char **argv) case 'v': verbosity = MIN(verbosity + 1, MAX_VERBOSITY); break; + case 'x': + require_nx_compat = true; + break; } } diff --git a/replacements.c b/replacements.c deleted file mode 100644 index 469e73a..0000000 --- a/replacements.c +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause-Patent -/* - * shim - trivial UEFI first-stage bootloader - * - * Copyright Red Hat, Inc - */ - -/* Chemical agents lend themselves to covert use in sabotage against - * which it is exceedingly difficult to visualize any really effective - * defense... I will not dwell upon this use of CBW because, as one - * pursues the possibilities of such covert uses, one discovers that the - * scenarios resemble that in which the components of a nuclear weapon - * are smuggled into New York City and assembled in the basement of the - * Empire State Building. - * In other words, once the possibility is recognized to exist, about - * all that one can do is worry about it. - * -- Dr. Ivan L Bennett, Jr., testifying before the Subcommittee on - * National Security Policy and Scientific Developments, November 20, - * 1969. - */ -#include "shim.h" - -static EFI_SYSTEM_TABLE *systab; - -EFI_SYSTEM_TABLE * -get_active_systab(void) -{ - if (systab) - return systab; - return ST; -} - -static typeof(systab->BootServices->LoadImage) system_load_image; -static typeof(systab->BootServices->StartImage) system_start_image; -static typeof(systab->BootServices->Exit) system_exit; -#if !defined(DISABLE_EBS_PROTECTION) -static typeof(systab->BootServices->ExitBootServices) system_exit_boot_services; -#endif /* !defined(DISABLE_EBS_PROTECTION) */ - -static EFI_HANDLE last_loaded_image; - -void -unhook_system_services(void) -{ - if (!systab) - return; - - systab->BootServices->LoadImage = system_load_image; - systab->BootServices->StartImage = system_start_image; -#if !defined(DISABLE_EBS_PROTECTION) - systab->BootServices->ExitBootServices = system_exit_boot_services; -#endif /* !defined(DISABLE_EBS_PROTECTION) */ - BS = systab->BootServices; -} - -static EFI_STATUS EFIAPI -load_image(BOOLEAN BootPolicy, EFI_HANDLE ParentImageHandle, - EFI_DEVICE_PATH *DevicePath, VOID *SourceBuffer, - UINTN SourceSize, EFI_HANDLE *ImageHandle) -{ - EFI_STATUS efi_status; - - unhook_system_services(); - efi_status = BS->LoadImage(BootPolicy, ParentImageHandle, DevicePath, - SourceBuffer, SourceSize, ImageHandle); - hook_system_services(systab); - if (EFI_ERROR(efi_status)) - last_loaded_image = NULL; - else - last_loaded_image = *ImageHandle; - return efi_status; -} - -static EFI_STATUS EFIAPI -replacement_start_image(EFI_HANDLE image_handle, UINTN *exit_data_size, CHAR16 **exit_data) -{ - EFI_STATUS efi_status; - unhook_system_services(); - - if (image_handle == last_loaded_image) { - UINT8 retain_protocol = 0; - UINTN retain_protocol_size = sizeof(retain_protocol); - UINT32 retain_protocol_attrs = 0; - - loader_is_participating = 1; - - /* If a boot component asks us, keep our protocol around - it will be used to - * validate further PE payloads (e.g.: by the UKI stub, before the kernel is booted). - * But also check that the variable was set by a boot component, to ensure that - * nobody at runtime can attempt to change shim's behaviour. */ - efi_status = RT->GetVariable(SHIM_RETAIN_PROTOCOL_VAR_NAME, - &SHIM_LOCK_GUID, - &retain_protocol_attrs, - &retain_protocol_size, - &retain_protocol); - if (EFI_ERROR(efi_status) || - (retain_protocol_attrs & EFI_VARIABLE_NON_VOLATILE) || - !(retain_protocol_attrs & EFI_VARIABLE_BOOTSERVICE_ACCESS) || - retain_protocol_size != sizeof(retain_protocol) || - retain_protocol == 0) - uninstall_shim_protocols(); - } - efi_status = BS->StartImage(image_handle, exit_data_size, exit_data); - if (EFI_ERROR(efi_status)) { - if (image_handle == last_loaded_image) { - EFI_STATUS efi_status2 = install_shim_protocols(); - - if (EFI_ERROR(efi_status2)) { - console_print(L"Something has gone seriously wrong: %r\n", - efi_status2); - console_print(L"shim cannot continue, sorry.\n"); - usleep(5000000); - RT->ResetSystem(EfiResetShutdown, - EFI_SECURITY_VIOLATION, - 0, NULL); - } - } - hook_system_services(systab); - loader_is_participating = 0; - } - return efi_status; -} - -#if !defined(DISABLE_EBS_PROTECTION) -static EFI_STATUS EFIAPI -exit_boot_services(EFI_HANDLE image_key, UINTN map_key) -{ - if (loader_is_participating || - verification_method == VERIFIED_BY_HASH) { - unhook_system_services(); - EFI_STATUS efi_status; - efi_status = BS->ExitBootServices(image_key, map_key); - if (EFI_ERROR(efi_status)) - hook_system_services(systab); - return efi_status; - } - - console_print(L"Bootloader has not verified loaded image.\n"); - console_print(L"System is compromised. halting.\n"); - usleep(5000000); - RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL); - return EFI_SECURITY_VIOLATION; -} -#endif /* !defined(DISABLE_EBS_PROTECTION) */ - -static EFI_STATUS EFIAPI -do_exit(EFI_HANDLE ImageHandle, EFI_STATUS ExitStatus, - UINTN ExitDataSize, CHAR16 *ExitData) -{ - EFI_STATUS efi_status; - - shim_fini(); - - restore_loaded_image(); - - efi_status = BS->Exit(ImageHandle, ExitStatus, - ExitDataSize, ExitData); - if (EFI_ERROR(efi_status)) { - EFI_STATUS efi_status2 = shim_init(); - - if (EFI_ERROR(efi_status2)) { - console_print(L"Something has gone seriously wrong: %r\n", - efi_status2); - console_print(L"shim cannot continue, sorry.\n"); - usleep(5000000); - RT->ResetSystem(EfiResetShutdown, - EFI_SECURITY_VIOLATION, 0, NULL); - } - } - return efi_status; -} - -void -hook_system_services(EFI_SYSTEM_TABLE *local_systab) -{ - systab = local_systab; - BS = systab->BootServices; - - /* We need to hook various calls to make this work... */ - - /* We need LoadImage() hooked so that fallback.c can load shim - * without having to fake LoadImage as well. This allows it - * to call the system LoadImage(), and have us track the output - * and mark loader_is_participating in replacement_start_image. This - * means anything added by fallback has to be verified by the system - * db, which we want to preserve anyway, since that's all launching - * through BDS gives us. */ - system_load_image = systab->BootServices->LoadImage; - systab->BootServices->LoadImage = load_image; - - /* we need StartImage() so that we can allow chain booting to an - * image trusted by the firmware */ - system_start_image = systab->BootServices->StartImage; - systab->BootServices->StartImage = replacement_start_image; - -#if !defined(DISABLE_EBS_PROTECTION) - /* we need to hook ExitBootServices() so a) we can enforce the policy - * and b) we can unwrap when we're done. */ - system_exit_boot_services = systab->BootServices->ExitBootServices; - systab->BootServices->ExitBootServices = exit_boot_services; -#endif /* defined(DISABLE_EBS_PROTECTION) */ -} - -void -unhook_exit(void) -{ - systab->BootServices->Exit = system_exit; - BS = systab->BootServices; -} - -void -hook_exit(EFI_SYSTEM_TABLE *local_systab) -{ - systab = local_systab; - BS = local_systab->BootServices; - - /* we need to hook Exit() so that we can allow users to quit the - * bootloader and still e.g. start a new one or run an internal - * shell. */ - system_exit = systab->BootServices->Exit; - systab->BootServices->Exit = do_exit; -} diff --git a/sbat.c b/sbat.c index 0695612..f31d945 100644 --- a/sbat.c +++ b/sbat.c @@ -537,9 +537,9 @@ set_sbat_uefi_variable(char *sbat_var_automatic, char *sbat_var_latest) */ if (EFI_ERROR(efi_status)) { dprint(L"SBAT read failed %r\n", efi_status); - } else if (preserve_sbat_uefi_variable(sbat, sbatsize, attributes, - sbat_var_candidate) && - !reset_sbat) { + } else if (!reset_sbat && + preserve_sbat_uefi_variable(sbat, sbatsize, attributes, + sbat_var_candidate)) { dprint(L"preserving %s variable it is %d bytes, attributes are 0x%08x\n", SBAT_VAR_NAME, sbatsize, attributes); FreePool(sbat); diff --git a/sbat_var.S b/sbat_var.S index ed82a46..7292406 100644 --- a/sbat_var.S +++ b/sbat_var.S @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-2-Clause-Patent #include "include/sbat_var_defs.h" +#include "generated_sbat_var_defs.h" .section .sbatlevel, "a", %progbits .balignl 4, 0 diff --git a/shim.c b/shim.c index 633163a..8b933d7 100644 --- a/shim.c +++ b/shim.c @@ -52,8 +52,6 @@ extern struct { UINT32 vendor_deauthorized_offset; } cert_table; -#define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }} - typedef enum { DATA_FOUND, DATA_NOT_FOUND, @@ -780,39 +778,6 @@ verify_buffer (char *data, int datasize, return verify_buffer_sbat(data, datasize, context); } -static int -is_removable_media_path(EFI_LOADED_IMAGE *li) -{ - unsigned int pathlen = 0; - CHAR16 *bootpath = NULL; - int ret = 0; - - bootpath = DevicePathToStr(li->FilePath); - - /* Check the beginning of the string and the end, to avoid - * caring about which arch this is. */ - /* I really don't know why, but sometimes bootpath gives us - * L"\\EFI\\BOOT\\/BOOTX64.EFI". So just handle that here... - */ - if (StrnCaseCmp(bootpath, L"\\EFI\\BOOT\\BOOT", 14) && - StrnCaseCmp(bootpath, L"\\EFI\\BOOT\\/BOOT", 15) && - StrnCaseCmp(bootpath, L"EFI\\BOOT\\BOOT", 13) && - StrnCaseCmp(bootpath, L"EFI\\BOOT\\/BOOT", 14)) - goto error; - - pathlen = StrLen(bootpath); - if (pathlen < 5 || StrCaseCmp(bootpath + pathlen - 4, L".EFI")) - goto error; - - ret = 1; - -error: - if (bootpath) - FreePool(bootpath); - - return ret; -} - static int should_use_fallback(EFI_HANDLE image_handle) { @@ -994,10 +959,9 @@ EFI_STATUS shim_verify (void *buffer, UINT32 size) if ((INT32)size < 0) return EFI_INVALID_PARAMETER; - loader_is_participating = 1; 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; @@ -1052,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; @@ -1091,7 +1055,8 @@ str16_to_str8(CHAR16 *str16, CHAR8 **str8) * Load and run an EFI executable */ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, - CHAR16 **PathName, void **data, int *datasize) + CHAR16 **PathName, void **data, int *datasize, + int flags) { EFI_STATUS efi_status; void *sourcebuffer = NULL; @@ -1128,10 +1093,11 @@ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, } FreePool(netbootname); efi_status = FetchNetbootimage(image_handle, &sourcebuffer, - &sourcesize); + &sourcesize, flags); if (EFI_ERROR(efi_status)) { - perror(L"Unable to fetch TFTP image: %r\n", - efi_status); + if (~flags & SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE) + perror(L"Unable to fetch TFTP image: %r\n", + efi_status); return efi_status; } *data = sourcebuffer; @@ -1143,8 +1109,9 @@ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, &sourcesize, netbootname); if (EFI_ERROR(efi_status)) { - perror(L"Unable to fetch HTTP image %a: %r\n", - netbootname, efi_status); + if (~flags & SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE) + perror(L"Unable to fetch HTTP image %a: %r\n", + netbootname, efi_status); return efi_status; } *data = sourcebuffer; @@ -1156,7 +1123,7 @@ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, 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); + *PathName, efi_status); PrintErrors(); ClearErrors(); return efi_status; @@ -1183,7 +1150,7 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) int datasize = 0; efi_status = read_image(image_handle, ImagePath, &PathName, &data, - &datasize); + &datasize, 0); if (EFI_ERROR(efi_status)) goto done; @@ -1215,7 +1182,9 @@ EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath) goto restore; } - loader_is_participating = 0; +#if 0 + save_logs(); +#endif /* * The binary is trusted and relocated. Run it @@ -1257,10 +1226,15 @@ EFI_STATUS init_grub(EFI_HANDLE image_handle) use_fb ? FALLBACK : second_stage); } - // If the filename is invalid, or the file does not exist, - // just fallback to the default loader. + /* + * If the filename is invalid, or the file does not exist, just fall + * back to the default loader. Also fall back to the default loader + * if we get a TFTP error or HTTP error. + */ if (!use_fb && (efi_status == EFI_INVALID_PARAMETER || - efi_status == EFI_NOT_FOUND)) { + efi_status == EFI_NOT_FOUND || + efi_status == EFI_HTTP_ERROR || + efi_status == EFI_TFTP_ERROR)) { console_print( L"start_image() returned %r, falling back to default loader\n", efi_status); @@ -1286,7 +1260,7 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) EFI_STATUS efi_status; EFI_LOADED_IMAGE *li = NULL; - second_stage = DEFAULT_LOADER; + second_stage = (optional_second_stage) ? optional_second_stage : DEFAULT_LOADER; load_options = NULL; load_options_size = 0; @@ -1373,13 +1347,17 @@ install_shim_protocols(void) if (!EFI_ERROR(efi_status)) uninstall_shim_protocols(); + init_image_loader(); + /* * Install the protocol */ - efi_status = BS->InstallProtocolInterface(&shim_lock_handle, - &SHIM_LOCK_GUID, - EFI_NATIVE_INTERFACE, - &shim_lock_interface); + efi_status = BS->InstallMultipleProtocolInterfaces(&shim_lock_handle, + &SHIM_LOCK_GUID, + &shim_lock_interface, + &SHIM_IMAGE_LOADER_GUID, + &shim_image_loader_interface, + NULL); if (EFI_ERROR(efi_status)) { console_error(L"Could not install security protocol", efi_status); @@ -1405,8 +1383,12 @@ uninstall_shim_protocols(void) /* * If we're back here then clean everything up before exiting */ - BS->UninstallProtocolInterface(shim_lock_handle, &SHIM_LOCK_GUID, - &shim_lock_interface); + BS->UninstallMultipleProtocolInterfaces(shim_lock_handle, + &SHIM_LOCK_GUID, + &shim_lock_interface, + &SHIM_IMAGE_LOADER_GUID, + &shim_image_loader_interface, + NULL); if (!secure_mode()) return; @@ -1444,7 +1426,7 @@ check_section_helper(char *section_name, int len, void **pointer, section, data, datasize, minsize) EFI_STATUS -load_revocations_file(EFI_HANDLE image_handle, CHAR16 *PathName) +load_revocations_file(EFI_HANDLE image_handle, CHAR16 *FileName, CHAR16 *PathName) { EFI_STATUS efi_status = EFI_SUCCESS; PE_COFF_LOADER_IMAGE_CONTEXT context; @@ -1459,12 +1441,12 @@ load_revocations_file(EFI_HANDLE image_handle, CHAR16 *PathName) uint8_t *ssps_latest = NULL; uint8_t *sspv_latest = NULL; - efi_status = read_image(image_handle, L"revocations.efi", &PathName, - &data, &datasize); - if (EFI_ERROR(efi_status)) - return efi_status; + efi_status = read_image(image_handle, FileName, &PathName, + &data, &datasize, + SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE); + if (!EFI_ERROR(efi_status)) + efi_status = verify_image(data, datasize, shim_li, &context); - efi_status = verify_image(data, datasize, shim_li, &context); if (EFI_ERROR(efi_status)) { dprint(L"revocations failed to verify\n"); return efi_status; @@ -1510,7 +1492,8 @@ load_revocations_file(EFI_HANDLE image_handle, CHAR16 *PathName) } EFI_STATUS -load_cert_file(EFI_HANDLE image_handle, CHAR16 *filename, CHAR16 *PathName) +load_cert_file(EFI_HANDLE image_handle, CHAR16 *filename, CHAR16 *PathName, + int flags) { EFI_STATUS efi_status; PE_COFF_LOADER_IMAGE_CONTEXT context; @@ -1518,37 +1501,58 @@ load_cert_file(EFI_HANDLE image_handle, CHAR16 *filename, CHAR16 *PathName) EFI_SIGNATURE_LIST *certlist; void *pointer; UINT32 original; + UINT32 offset; int datasize = 0; void *data = NULL; int i; efi_status = read_image(image_handle, filename, &PathName, - &data, &datasize); + &data, &datasize, flags); if (EFI_ERROR(efi_status)) return efi_status; efi_status = verify_image(data, datasize, shim_li, &context); - if (EFI_ERROR(efi_status)) + if (EFI_ERROR(efi_status)) { + FreePool(data); return efi_status; + } Section = context.FirstSection; for (i = 0; i < context.NumberOfSections; i++, Section++) { + UINT32 sec_size = MIN(Section->Misc.VirtualSize, Section->SizeOfRawData); + if (CompareMem(Section->Name, ".db\0\0\0\0\0", 8) == 0) { - original = user_cert_size; - if (Section->SizeOfRawData < sizeof(EFI_SIGNATURE_LIST)) { - continue; + offset = 0; + while ((sec_size - offset) >= sizeof(EFI_SIGNATURE_LIST)) { + UINT8 *tmp; + + original = user_cert_size; + pointer = ImageAddress(data, datasize, + Section->PointerToRawData + offset); + if (!pointer) { + break; + } + certlist = pointer; + + if (certlist->SignatureListSize < sizeof(EFI_SIGNATURE_LIST) || + checked_add(offset, certlist->SignatureListSize, &offset) || + offset > sec_size || + checked_add(user_cert_size, certlist->SignatureListSize, + &user_cert_size)) { + break; + } + + tmp = ReallocatePool(user_cert, original, + user_cert_size); + if (!tmp) { + FreePool(data); + return EFI_OUT_OF_RESOURCES; + } + user_cert = tmp; + + CopyMem(user_cert + original, pointer, + certlist->SignatureListSize); } - pointer = ImageAddress(data, datasize, - Section->PointerToRawData); - if (!pointer) { - continue; - } - certlist = pointer; - user_cert_size += certlist->SignatureListSize;; - user_cert = ReallocatePool(user_cert, original, - user_cert_size); - CopyMem(user_cert + original, pointer, - certlist->SignatureListSize); } } FreePool(data); @@ -1565,6 +1569,7 @@ load_unbundled_trust(EFI_HANDLE image_handle) EFI_STATUS efi_status; EFI_LOADED_IMAGE *li = NULL; CHAR16 *PathName = NULL; + static CHAR16 FileName[] = L"shim_certificate_0.efi"; EFI_FILE *root, *dir; EFI_FILE_INFO *info; EFI_HANDLE device; @@ -1572,6 +1577,7 @@ load_unbundled_trust(EFI_HANDLE image_handle) UINTN buffersize = 0; void *buffer = NULL; BOOLEAN search_revocations = TRUE; + int i = 0; efi_status = gBS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID, (void **)&li); @@ -1592,11 +1598,17 @@ load_unbundled_trust(EFI_HANDLE image_handle) efi_status); /* * Network boot cases do not support reading a directory. Try - * to read revocations.efi to pull in any unbundled SBATLevel + * to read revocations to pull in any unbundled SBATLevel * updates unconditionally in those cases. This may produce * console noise when the file is not present. */ - load_cert_file(image_handle, REVOCATIONFILE, PathName); + load_revocations_file(image_handle, SKUSIREVOCATIONFILE, PathName); + load_revocations_file(image_handle, SBATREVOCATIONFILE, PathName); + while (load_cert_file(image_handle, FileName, PathName, + SUPPRESS_NETBOOT_OPEN_FAILURE_NOISE) == EFI_SUCCESS + && i++ < 10) { + FileName[17]++; + } goto done; } @@ -1666,17 +1678,17 @@ load_unbundled_trust(EFI_HANDLE image_handle) } /* - * In the event that there are unprocessed revocation + * In the event that there are unprocessed sbat revocation * additions, they could be intended to ban any *new* trust * anchors we find here. With that in mind, we always want to * do a pass of loading revocations before we try to add * anything new to our allowlist. This is done by making two * passes over the directory, first to search for the - * revocations.efi file then to search for shim_certificate.efi + * revocations_sbat.efi file then to search for shim_certificate*.efi */ if (search_revocations && - StrCaseCmp(info->FileName, REVOCATIONFILE) == 0) { - load_revocations_file(image_handle, PathName); + StrCaseCmp(info->FileName, SBATREVOCATIONFILE) == 0) { + load_revocations_file(image_handle, SBATREVOCATIONFILE, PathName); search_revocations = FALSE; efi_status = root->Open(root, &dir, PathName, EFI_FILE_MODE_READ, 0); @@ -1687,9 +1699,14 @@ load_unbundled_trust(EFI_HANDLE image_handle) } } - if (!search_revocations && - StrCaseCmp(info->FileName, L"shim_certificate.efi") == 0) { - load_cert_file(image_handle, info->FileName, PathName); + if (!search_revocations) { + if (StrnCaseCmp(info->FileName, L"shim_certificate", 16) == 0) { + load_cert_file(image_handle, info->FileName, PathName, 0); + } + if (StrCaseCmp(info->FileName, SKUSIREVOCATIONFILE) == 0) { + load_revocations_file(image_handle, + SKUSIREVOCATIONFILE, PathName); + } } } done: @@ -1698,6 +1715,86 @@ done: return efi_status; } +/* Read optional options file */ +EFI_STATUS +load_shim_options(EFI_HANDLE image_handle) +{ + EFI_STATUS efi_status; + EFI_HANDLE device; + EFI_LOADED_IMAGE *li = NULL; + EFI_FILE_IO_INTERFACE *drive; + EFI_FILE *root; + EFI_FILE_HANDLE ofile; + CHAR16 *PathName = NULL; + CHAR16 *buffer; + UINTN comma0; + UINT64 bs; + + efi_status = gBS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID, + (void **)&li); + if (EFI_ERROR(efi_status)) { + perror(L"Unable to init protocol\n"); + return efi_status; + } + + efi_status = generate_path_from_image_path(li, L"options.csv", &PathName); + if (EFI_ERROR(efi_status)) + goto done; + + device = li->DeviceHandle; + + efi_status = BS->HandleProtocol(device, &EFI_SIMPLE_FILE_SYSTEM_GUID, + (void **) &drive); + if (EFI_ERROR(efi_status)) + goto done; + + efi_status = drive->OpenVolume(drive, &root); + if (EFI_ERROR(efi_status)) { + perror(L"Failed to open fs: %r\n", efi_status); + goto done; + } + + efi_status = root->Open(root, &ofile, PathName, EFI_FILE_READ_ONLY, 0); + if (EFI_ERROR(efi_status)) { + if (efi_status != EFI_NOT_FOUND) + perror(L"Failed to open %s - %r\n", PathName, efi_status); + goto done; + } + + dprint(L"Loading optional second stage info from options.csv\n"); + efi_status = read_file(ofile, PathName, &buffer, &bs); + if (EFI_ERROR(efi_status)) { + perror(L"Failed to read file\n"); + goto done; + } + + /* + * This file may or may not start with the Unicode byte order marker. + * Since UEFI is defined as LE, this file must also be LE. + * If we find the LE byte order marker, just skip its. + */ + if (*buffer == 0xfeff) + buffer++; + + comma0 = StrCSpn(buffer, L","); + if (comma0 == 0) { + perror(L"Invalid csv file\n"); + goto done; + } + + /* + * Currently the options.csv file allows one entry for the optional + * secondary boot stage, anything afterwards is skipped. + */ + buffer[comma0] = L'\0'; + dprint(L"Optional 2nd stage:\"%s\"\n", buffer); + optional_second_stage=buffer; + +done: + FreePool(PathName); + return EFI_SUCCESS; +} + EFI_STATUS shim_init(void) { @@ -1720,7 +1817,6 @@ shim_init(void) * validation of the next image. */ hook_system_services(systab); - loader_is_participating = 0; } } @@ -1746,11 +1842,12 @@ shim_fini(void) uninstall_shim_protocols(); if (secure_mode()) { - - /* - * Remove our hooks from system services. - */ - unhook_system_services(); + if (vendor_authorized_size || vendor_deauthorized_size) { + /* + * Remove our hooks from system services. + */ + unhook_system_services(); + } } unhook_exit(); @@ -1860,7 +1957,7 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) L"shim_init() failed", L"import of SBAT data failed", L"SBAT self-check failed", - SBAT_VAR_NAME L" UEFI variable setting failed", + SBAT_VAR_NAME L" UEFI variable setting failed", // NOLINT(bugprone-suspicious-missing-comma) NULL }; enum { @@ -1898,6 +1995,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); @@ -1929,7 +2028,7 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) 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", + perror(L"Verifying shim SBAT data failed: %r\n", efi_status); msg = SBAT_SELF_CHECK; goto die; @@ -1938,6 +2037,7 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) } init_openssl(); + get_hsi_mem_info(); efi_status = load_unbundled_trust(global_image_handle); if (EFI_ERROR(efi_status)) { @@ -1981,6 +2081,8 @@ die: */ (void) del_variable(SHIM_RETAIN_PROTOCOL_VAR_NAME, SHIM_LOCK_GUID); + load_shim_options(image_handle); + efi_status = shim_init(); if (EFI_ERROR(efi_status)) { msg = SHIM_INIT; diff --git a/shim.h b/shim.h index 5791a03..59d9062 100644 --- a/shim.h +++ b/shim.h @@ -55,6 +55,7 @@ #ifndef SHIM_UNIT_TEST #include #include +#include #undef uefi_call_wrapper #include #include @@ -163,7 +164,9 @@ #include "include/configtable.h" #include "include/console.h" #include "include/crypt_blowfish.h" +#include "include/dp.h" #include "include/efiauthenticated.h" +#include "include/errlog.h" #include "include/errors.h" #include "include/execute.h" #include "include/guid.h" @@ -172,12 +175,13 @@ #include "include/ip4config2.h" #include "include/ip6config.h" #include "include/load-options.h" +#include "include/loader-proto.h" +#include "include/memattrs.h" #include "include/mok.h" #include "include/netboot.h" #include "include/passwordcrypt.h" #include "include/peimage.h" #include "include/pe.h" -#include "include/replacements.h" #include "include/sbat.h" #include "include/sbat_var_defs.h" #include "include/ssp.h" @@ -187,6 +191,7 @@ #include "include/simple_file.h" #include "include/str.h" #include "include/tpm.h" +#include "include/utils.h" #include "include/cc.h" #include "include/ucs2.h" #include "include/variables.h" @@ -237,17 +242,11 @@ typedef struct _SHIM_LOCK { extern EFI_STATUS shim_init(void); extern void shim_fini(void); -extern EFI_STATUS EFIAPI LogError_(const char *file, int line, const char *func, - const CHAR16 *fmt, ...); -extern EFI_STATUS EFIAPI VLogError(const char *file, int line, const char *func, - const CHAR16 *fmt, ms_va_list args); -extern VOID LogHexdump_(const char *file, int line, const char *func, - const void *data, size_t sz); -extern VOID PrintErrors(VOID); -extern VOID ClearErrors(VOID); extern VOID restore_loaded_image(VOID); extern EFI_STATUS start_image(EFI_HANDLE image_handle, CHAR16 *ImagePath); extern EFI_STATUS import_mok_state(EFI_HANDLE image_handle); +extern EFI_STATUS install_shim_protocols(void); +extern void uninstall_shim_protocols(void); extern UINT32 vendor_authorized_size; extern UINT8 *vendor_authorized; @@ -324,4 +323,19 @@ verify_buffer (char *data, int datasize, char *translate_slashes(char *out, const char *str); +#include + +typedef struct { + EFI_LOADED_IMAGE li; + EFI_IMAGE_ENTRY_POINT entry_point; + EFI_PHYSICAL_ADDRESS alloc_address; + UINTN alloc_pages; + EFI_STATUS exit_status; + CONST CHAR16 *exit_data; + UINTN exit_data_size; + jmp_buf longjmp_buf; + BOOLEAN started; + EFI_DEVICE_PATH *loaded_image_device_path; +} SHIM_LOADED_IMAGE; + #endif /* SHIM_H_ */ diff --git a/test-load-options.c b/test-load-options.c index daf02d9..dfabe86 100644 --- a/test-load-options.c +++ b/test-load-options.c @@ -68,9 +68,12 @@ test_parse_load_options(char *load_option_data, assert_false_goto(EFI_ERROR(status), err, "\n"); assert_nonzero_goto(second_stage, err, "\n"); - assert_not_equal_goto(second_stage, dummy_second_stage, err, "%p == %p\n"); - assert_zero_goto(StrnCmp(second_stage, target_second_stage, 90), - err_print_second_stage, "%d != 0\n"); + if (target_second_stage) { + assert_not_equal_goto(second_stage, dummy_second_stage, err, "%p == %p\n"); + assert_zero_goto(StrnCmp(second_stage, target_second_stage, 90), + err_print_second_stage, "%d != 0\n"); + } else + assert_equal_goto(second_stage, dummy_second_stage, err, "%p != %p\n"); assert_equal_goto(load_options_size, target_remaining_size, err_remaining, "%zu != %zu\n"); assert_equal_goto(load_options, target_remaining, err_remaining, "%p != %p\n"); @@ -255,6 +258,73 @@ test_bds_2(void) target_remaining_size); } +int +test_multi_end_dp(void) +{ + /* +00000000 01 00 00 00 d0 00 4f 00 6e 00 62 00 6f 00 61 00 |......O.n.b.o.a.| +00000010 72 00 64 00 20 00 4e 00 49 00 43 00 28 00 49 00 |r.d. .N.I.C.(.I.| +00000020 50 00 56 00 34 00 29 00 00 00 02 01 0c 00 d0 41 |P.V.4.)........A| +00000030 03 0a 00 00 00 00 01 01 06 00 06 1f 03 0b 25 00 |..............%.| +00000040 2c ea 7f 0a 9f 69 00 00 00 00 00 00 00 00 00 00 |,....i..........| +00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000060 00 03 0c 1b 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000070 00 00 00 00 00 00 00 00 00 00 00 00 7f ff 04 00 |................| +00000080 01 04 76 00 ef 47 64 2d c9 3b a0 41 ac 19 4d 51 |..v..Gd-.;.A..MQ| +00000090 d0 1b 4c e6 50 00 58 00 45 00 20 00 49 00 50 00 |..L.P.X.E. .I.P.| +000000a0 34 00 20 00 49 00 6e 00 74 00 65 00 6c 00 28 00 |4. .I.n.t.e.l.(.| +000000b0 52 00 29 00 20 00 45 00 74 00 68 00 65 00 72 00 |R.). .E.t.h.e.r.| +000000c0 6e 00 65 00 74 00 20 00 43 00 6f 00 6e 00 6e 00 |n.e.t. .C.o.n.n.| +000000d0 65 00 63 00 74 00 69 00 6f 00 6e 00 20 00 28 00 |e.c.t.i.o.n. .(.| +000000e0 36 00 29 00 20 00 49 00 32 00 31 00 39 00 2d 00 |6.). .I.2.1.9.-.| +000000f0 4c 00 4d 00 00 00 7f ff 04 00 00 00 42 4f |L.M.........BO| + */ + char load_option_data [] = { + 0x01, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x4f, 0x00, + 0x6e, 0x00, 0x62, 0x00, 0x6f, 0x00, 0x61, 0x00, + 0x72, 0x00, 0x64, 0x00, 0x20, 0x00, 0x4e, 0x00, + 0x49, 0x00, 0x43, 0x00, 0x28, 0x00, 0x49, 0x00, + 0x50, 0x00, 0x56, 0x00, 0x34, 0x00, 0x29, 0x00, + 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, + 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x06, 0x00, 0x06, 0x1f, 0x03, 0x0b, 0x25, 0x00, + 0x2c, 0xea, 0x7f, 0x0a, 0x9f, 0x69, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x0c, 0x1b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x04, 0x00, + 0x01, 0x04, 0x76, 0x00, 0xef, 0x47, 0x64, 0x2d, + 0xc9, 0x3b, 0xa0, 0x41, 0xac, 0x19, 0x4d, 0x51, + 0xd0, 0x1b, 0x4c, 0xe6, 0x50, 0x00, 0x58, 0x00, + 0x45, 0x00, 0x20, 0x00, 0x49, 0x00, 0x50, 0x00, + 0x34, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e, 0x00, + 0x74, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x28, 0x00, + 0x52, 0x00, 0x29, 0x00, 0x20, 0x00, 0x45, 0x00, + 0x74, 0x00, 0x68, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x6e, 0x00, 0x65, 0x00, 0x74, 0x00, 0x20, 0x00, + 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x6e, 0x00, + 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x69, 0x00, + 0x6f, 0x00, 0x6e, 0x00, 0x20, 0x00, 0x28, 0x00, + 0x36, 0x00, 0x29, 0x00, 0x20, 0x00, 0x49, 0x00, + 0x32, 0x00, 0x31, 0x00, 0x39, 0x00, 0x2d, 0x00, + 0x4c, 0x00, 0x4d, 0x00, 0x00, 0x00, 0x7f, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x42, 0x4f + }; + size_t load_option_data_size = sizeof(load_option_data); + char *target_remaining = &load_option_data[load_option_data_size - 2]; + size_t target_remaining_size = 2; + + return test_parse_load_options(load_option_data, + load_option_data_size, + "test.efi", + NULL, + target_remaining, + target_remaining_size); +} + int main(void) { @@ -263,6 +333,7 @@ main(void) test(test_bds_0); test(test_bds_1); test(test_bds_2); + test(test_multi_end_dp); return status; } diff --git a/test-mock-variables.c b/test-mock-variables.c index c7e42b0..f869300 100644 --- a/test-mock-variables.c +++ b/test-mock-variables.c @@ -207,19 +207,35 @@ test_gnvn_helper(char *testvars) const char *mok_rt_vars[n_mok_state_variables]; for (size_t i = 0; i < n_mok_state_variables; i++) { + /* + * We don't want to filter out the variables we've added to + * mok mirroring that aren't really from mok; right now + * this is a reasonable heuristic for that. + */ + if (mok_state_variables[i].flags & MOK_VARIABLE_CONFIG_ONLY) + continue; mok_rt_vars[i] = mok_state_variables[i].rtname8; } mock_load_variables(testvars, mok_rt_vars, true); +#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 + dump_mock_variables(__FILE__, __LINE__, __func__); +#endif + + /* + * This tests the sort policy, filtering for only variables in the + * EFI "global" namespace. If ascending the first thing should + * be Boot0000, if descending it should be dbxDefault + */ +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("Testing mock variable sorting in the global namespace\n"); +#endif size = sizeof(buf); buf[0] = L'\0'; status = RT->GetNextVariableName(&size, buf, &GV_GUID); assert_equal_goto(status, EFI_SUCCESS, err, "0x%lx != 0x%lx\n"); -#if defined(SHIM_DEBUG) && SHIM_DEBUG != 0 - dump_mock_variables(__FILE__, __LINE__, __func__); -#endif switch (mock_variable_sort_policy) { case MOCK_SORT_DESCENDING: dump_mock_variables_if_wrong(__FILE__, __LINE__, __func__, @@ -236,6 +252,14 @@ test_gnvn_helper(char *testvars) break; } + /* + * Do it again but test for only variables in the Secure Boot + * policy guid namespace. Ascending should be "db", descending + * "dbx". + */ +#if defined(SHIM_DEBUG) && SHIM_DEBUG >= 1 + printf("Testing mock variable sorting in the Secure Boot GUID namespace\n"); +#endif size = sizeof(buf); buf[0] = 0; status = RT->GetNextVariableName(&size, buf, &EFI_SECURE_BOOT_DB_GUID); @@ -284,6 +308,13 @@ test_get_variable_0(void) const char *mok_rt_vars[n_mok_state_variables]; for (size_t i = 0; i < n_mok_state_variables; i++) { + /* + * We don't want to filter out the variables we've added to + * mok mirroring that aren't really from mok; right now + * this is a reasonable heuristic for that. + */ + if (mok_state_variables[i].flags & MOK_VARIABLE_CONFIG_ONLY) + continue; mok_rt_vars[i] = mok_state_variables[i].rtname8; } diff --git a/test-mok-mirror.c b/test-mok-mirror.c index f34f62a..38b7ed9 100644 --- a/test-mok-mirror.c +++ b/test-mok-mirror.c @@ -8,6 +8,7 @@ #include "mock-variables.h" #include "test-data-efivars-1.h" +#include #include #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -21,21 +22,51 @@ start_image(EFI_HANDLE image_handle UNUSED, CHAR16 *mm) #define N_TEST_VAR_OPS 40 struct test_var { + /* + * The GUID, name, and attributes of the variables + */ EFI_GUID guid; CHAR16 *name; UINT32 attrs; - UINTN n_ops; + /* + * If the variable is required to be present, with the attributes + * specified above, for a test to pass + */ bool must_be_present; + /* + * If the variable is required to be absent, no matter what the + * attributes, for a test to pass + */ bool must_be_absent; + /* + * The number of operations on this variable that we've seen + */ + UINTN n_ops; + /* + * the operations that have occurred on this variable + */ mock_variable_op_t ops[N_TEST_VAR_OPS]; + /* + * the result codes of those operations + */ EFI_STATUS results[N_TEST_VAR_OPS]; }; static struct test_var *test_vars; struct mock_mok_variable_config_entry { + /* + * The name of an entry we expect to see in the MokVars + * configuration table + */ CHAR8 name[256]; + /* + * The size of its data + */ UINT64 data_size; + /* + * A pointer to what the data should be + */ const unsigned char *data; }; @@ -94,16 +125,217 @@ getvar_post(CHAR16 *name, EFI_GUID *guid, } } +static EFI_STATUS +check_variables(struct test_var *vars) +{ + for (size_t i = 0; vars[i].name != NULL; i++) { + struct test_var *tv = &vars[i]; + list_t *pos = NULL; + bool found = false; + char buf[1]; + UINTN size = 0; + UINT32 attrs = 0; + bool present = false; + + list_for_each(pos, &mock_variables) { + struct mock_variable *var; + bool deleted; + bool created; + int gets = 0; + + var = list_entry(pos, struct mock_variable, list); + if (CompareGuid(&tv->guid, &var->guid) != 0 || + StrCmp(var->name, tv->name) != 0) + continue; + found = true; + assert_equal_goto(var->attrs, tv->attrs, err, + "\"%s\": wrong attrs; got %s expected %s\n", + Str2str(tv->name), + format_var_attrs(var->attrs), + format_var_attrs(tv->attrs)); + for (UINTN j = 0; j < N_TEST_VAR_OPS + && tv->ops[j] != NONE; j++) { + switch (tv->ops[j]) { + case NONE: + default: + break; + case CREATE: + if (tv->results[j] == EFI_SUCCESS) + created = true; + break; + case DELETE: + assert_goto(tv->results[j] != EFI_SUCCESS, err, + "Tried to delete absent variable \"%s\"\n", + Str2str(tv->name)); + assert_goto(created == false, err, + "Deleted variable \"%s\" was previously created.\n", + Str2str(tv->name)); + break; + case APPEND: + assert_goto(false, err, + "No append action should have been tested\n"); + break; + case REPLACE: + assert_goto(false, err, + "No replace action should have been tested\n"); + break; + case GET: + if (tv->results[j] == EFI_SUCCESS) + gets += 1; + break; + } + } + assert_goto(gets == 0 || gets == 1, err, + "Variable should not be read %d times.\n", gets); + } + if (tv->must_be_present) { + /* + * This asserts if it isn't present, and if that's + * the case, then the attributes are already + * validated in the search loop + */ + assert_goto(found == true, err, + "variable \"%s\" was not found.\n", + Str2str(tv->name)); + } + + if (tv->must_be_absent) { + /* + * deliberately does not check the attributes at + * this time. + */ + assert_goto(found == false, err, + "variable \"%s\" was found.\n", + Str2str(tv->name)); + } + } + + return EFI_SUCCESS; +err: + return EFI_INVALID_PARAMETER; +} + +static EFI_STATUS +check_config_table(struct mock_mok_variable_config_entry *test_configs, + uint8_t *config_pos) +{ + size_t i = 0; + struct mok_variable_config_entry *mok_entry = NULL; + struct mock_mok_variable_config_entry *mock_entry = NULL; + + while (config_pos) { + mock_entry = &test_configs[i]; + mok_entry = (struct mok_variable_config_entry *)config_pos; + + /* + * If the tables are different lengths, this will trigger. + */ + assert_equal_goto(mok_entry->name[0], mock_entry->name[0], err, + "mok.name[0] %ld != test.name[0] %ld\n"); + if (mock_entry->name[0] == 0) + break; + + assert_nonzero_goto(mok_entry->name[0], err, "%ld != %ld\n"); + assert_zero_goto(strncmp(mok_entry->name, mock_entry->name, + sizeof(mock_entry->name)), + err, "%ld != %ld: strcmp(\"%s\",\"%s\")\n", + mok_entry->name, mock_entry->name); + + /* + * As of 7501b6bb449f ("mok: fix potential buffer overrun in + * import_mok_state"), we should not see any mok config + * variables with data_size == 0. + */ + assert_nonzero_goto(mok_entry->data_size, err, "%ld != 0\n"); + + assert_equal_goto(mok_entry->data_size, mock_entry->data_size, + err, "%ld != %ld\n"); + assert_zero_goto(CompareMem(mok_entry->data, mock_entry->data, + mok_entry->data_size), + err, "%ld != %ld\n"); + config_pos += offsetof(struct mok_variable_config_entry, data) + + mok_entry->data_size; + i += 1; + } + + return EFI_SUCCESS; +err: + warnx("Failed on entry %zu mok.name:\"%s\" mock.name:\"%s\"", i, + mok_entry->name, mock_entry->name); + if ((mok_entry && mock_entry) && (!mok_entry->name[0] || !mock_entry->name[0])) + warnx("Entry is missing in %s variable list.", mok_entry->name[0] ? "expected" : "found"); + + return EFI_INVALID_PARAMETER; +} + static int -test_mok_mirror_0(void) +test_mok_mirror(struct test_var *vars, + struct mock_mok_variable_config_entry *configs, + EFI_STATUS expected_status) +{ + EFI_STATUS status; + EFI_GUID mok_config_guid = MOK_VARIABLE_STORE; + int ret = -1; + + status = import_mok_state(NULL); + assert_equal_goto(status, expected_status, err, + "got 0x%016lx, expected 0x%016lx\n", + expected_status); + + test_vars = vars; + + status = check_variables(vars); + if (EFI_ERROR(status)) + goto err; + + uint8_t *pos = NULL; + for (size_t i = 0; i < ST->NumberOfTableEntries; i++) { + EFI_CONFIGURATION_TABLE *ct = &ST->ConfigurationTable[i]; + + if (CompareGuid(&ct->VendorGuid, &mok_config_guid) != 0) + continue; + + pos = (void *)ct->VendorTable; + break; + } + + assert_nonzero_goto(pos, err, "%p != 0\n"); + + status = check_config_table(configs, pos); + if (EFI_ERROR(status)) + goto err; + + ret = 0; +err: + for (UINTN k = 0; k < n_mok_state_variables; k++) { + struct mok_state_variable *v = + &mok_state_variables[k]; + if (v->data_size && v->data) { + free(v->data); + v->data = NULL; + v->data_size = 0; + } + } + + test_vars = NULL; + + return ret; +} + +/* + * This tests mirroring of mok variables on fairly optimistic conditions: + * there's enough space for everything, and so we expect to see all the + * RT variables for which we have data mirrored + */ +static int +test_mok_mirror_with_enough_space(void) { const char *mok_rt_vars[n_mok_state_variables]; EFI_STATUS status; EFI_GUID guid = SHIM_LOCK_GUID; - EFI_GUID mok_config_guid = MOK_VARIABLE_STORE; int ret = -1; - struct test_var test_mok_mirror_0_vars[] = { + struct test_var test_mok_mirror_with_enough_space_vars[] = { {.guid = SHIM_LOCK_GUID, .name = L"MokList", .must_be_present = true, @@ -166,11 +398,20 @@ test_mok_mirror_0(void) EFI_VARIABLE_RUNTIME_ACCESS, .ops = { NONE, }, }, + {.guid = SHIM_LOCK_GUID, + .name = L"HSIStatus", + .attrs = 0, + .ops = { NONE, }, + }, {.guid = { 0, }, .name = NULL, } }; + /* + * We must see the supplied values of MokListRT, MokListXRT, and + * SbatLevelRT in the config table + */ struct mock_mok_variable_config_entry test_mok_config_table[] = { {.name = "MokListRT", .data_size = sizeof(test_data_efivars_1_MokListRT), @@ -188,6 +429,10 @@ test_mok_mirror_0(void) .data_size = sizeof(test_data_efivars_1_MokListTrustedRT), .data = test_data_efivars_1_MokListTrustedRT }, + {.name = "HSIStatus", + .data_size = sizeof(test_data_efivars_1_HSIStatus), + .data = test_data_efivars_1_HSIStatus + }, {.name = { 0, }, .data_size = 0, .data = NULL, @@ -202,147 +447,214 @@ test_mok_mirror_0(void) mock_set_variable_post_hook = setvar_post; mock_get_variable_post_hook = getvar_post; - test_vars = &test_mok_mirror_0_vars[0]; - import_mok_state(NULL); + ret = test_mok_mirror(&test_mok_mirror_with_enough_space_vars[0], + test_mok_config_table, + EFI_SUCCESS); - for (size_t i = 0; test_mok_mirror_0_vars[i].name != NULL; i++) { - struct test_var *tv = &test_mok_mirror_0_vars[i]; - list_t *pos = NULL; - bool found = false; - char buf[1]; - UINTN size = 0; - UINT32 attrs = 0; - bool present = false; + mock_set_variable_post_hook = NULL; + mock_get_variable_post_hook = NULL; + return ret; +} - list_for_each(pos, &mock_variables) { - struct mock_variable *var; - bool deleted; - bool created; - int gets = 0; +static int +test_mok_mirror_setvar_out_of_resources(void) +{ + const char *mok_rt_vars[n_mok_state_variables]; + EFI_STATUS status; + EFI_GUID guid = SHIM_LOCK_GUID; + EFI_GUID mok_config_guid = MOK_VARIABLE_STORE; + int ret = -1; - var = list_entry(pos, struct mock_variable, list); - if (CompareGuid(&tv->guid, &var->guid) != 0 || - StrCmp(var->name, tv->name) != 0) - continue; - found = true; - assert_equal_goto(var->attrs, tv->attrs, err, - "\"%s\": wrong attrs; got %s expected %s\n", - Str2str(tv->name), - format_var_attrs(var->attrs), - format_var_attrs(tv->attrs)); - for (UINTN j = 0; j < N_TEST_VAR_OPS - && tv->ops[j] != NONE; j++) { - switch (tv->ops[j]) { - case NONE: - default: - break; - case CREATE: - if (tv->results[j] == EFI_SUCCESS) - created = true; - break; - case DELETE: - assert_goto(tv->results[j] != EFI_SUCCESS, err, - "Tried to delete absent variable \"%s\"\n", - Str2str(tv->name)); - assert_goto(created == false, err, - "Deleted variable \"%s\" was previously created.\n", - Str2str(tv->name)); - break; - case APPEND: - assert_goto(false, err, - "No append action should have been tested\n"); - break; - case REPLACE: - assert_goto(false, err, - "No replace action should have been tested\n"); - break; - case GET: - if (tv->results[j] == EFI_SUCCESS) - gets += 1; - break; - } - } - assert_goto(gets == 0 || gets == 1, err, - "Variable should not be read %d times.\n", gets); - } - if (tv->must_be_present) { - assert_goto(found == true, err, - "variable \"%s\" was not found.\n", - Str2str(tv->name)); - } + /* + * These sizes are picked specifically so that MokListRT will fail + * to get mirrored with the test data in test-data/efivars-1 + */ + list_t mock_obnoxious_variable_limits; + UINT64 obnoxious_max_var_storage = 0xffe4; + UINT64 obnoxious_remaining_var_storage = 919+0x3c; + UINT64 obnoxious_max_var_size = 919; - if (tv->must_be_absent) { - assert_goto(found == false, err, - "variable \"%s\" was found.\n", - Str2str(tv->name)); - } - } - - uint8_t *pos = NULL; - for (size_t i = 0; i < ST->NumberOfTableEntries; i++) { - EFI_CONFIGURATION_TABLE *ct = &ST->ConfigurationTable[i]; - - if (CompareGuid(&ct->VendorGuid, &mok_config_guid) != 0) - continue; - - pos = (void *)ct->VendorTable; - break; - } - - assert_nonzero_goto(pos, err, "%p != 0\n"); - - size_t i = 0; - while (pos) { - struct mock_mok_variable_config_entry *mock_entry = - &test_mok_config_table[i]; - struct mok_variable_config_entry *mok_entry = - (struct mok_variable_config_entry *)pos; + struct mock_variable_limits obnoxious_limits[] = { + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS, + .max_var_storage = &obnoxious_max_var_storage, + .remaining_var_storage = &obnoxious_remaining_var_storage, + .max_var_size = &obnoxious_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .max_var_storage = &obnoxious_max_var_storage, + .remaining_var_storage = &obnoxious_remaining_var_storage, + .max_var_size = &obnoxious_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .max_var_storage = &obnoxious_max_var_storage, + .remaining_var_storage = &obnoxious_remaining_var_storage, + .max_var_size = &obnoxious_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .max_var_storage = &obnoxious_max_var_storage, + .remaining_var_storage = &obnoxious_remaining_var_storage, + .max_var_size = &obnoxious_max_var_size, + .status = EFI_SUCCESS, + }, + {.attrs = 0, } + }; + struct test_var test_mok_mirror_enospc_vars[] = { /* - * If the tables are different lengths, this will trigger. + * We must to see a BS|NV MokList */ - assert_equal_goto(mok_entry->name[0], mock_entry->name[0], err, - "mok.name[0] %ld != test.name[0] %ld\n"); - if (mock_entry->name[0] == 0) - break; - - assert_nonzero_goto(mok_entry->name[0], err, "%ld != %ld\n"); - assert_zero_goto(strncmp(mok_entry->name, mock_entry->name, - sizeof(mock_entry->name)), - err, "%ld != %ld: strcmp(\"%s\",\"%s\")\n", - mok_entry->name, mock_entry->name); - + {.guid = SHIM_LOCK_GUID, + .name = L"MokList", + .must_be_present = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .ops = { NONE, }, + }, /* - * As of 7501b6bb449f ("mok: fix potential buffer overrun in - * import_mok_state"), we should not see any mok config - * variables with data_size == 0. + * We must *NOT* see a BS|RT MokListRT */ - assert_nonzero_goto(mok_entry->data_size, err, "%ld != 0\n"); - - assert_equal_goto(mok_entry->data_size, mock_entry->data_size, - err, "%ld != %ld\n"); - assert_zero_goto(CompareMem(mok_entry->data, mock_entry->data, - mok_entry->data_size), - err, "%ld != %ld\n"); - pos += offsetof(struct mok_variable_config_entry, data) - + mok_entry->data_size; - i += 1; - } - - ret = 0; -err: - for (UINTN k = 0; k < n_mok_state_variables; k++) { - struct mok_state_variable *v = - &mok_state_variables[k]; - if (v->data_size && v->data) { - free(v->data); - v->data = NULL; - v->data_size = 0; + {.guid = SHIM_LOCK_GUID, + .name = L"MokListRT", + .must_be_absent = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .ops = { NONE, }, + }, + /* + * We must see a BS|NV MokListX + */ + {.guid = SHIM_LOCK_GUID, + .name = L"MokListX", + .must_be_present = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .ops = { NONE, }, + }, + /* + * We must see a BS|RT MokListXRT + */ + {.guid = SHIM_LOCK_GUID, + .name = L"MokListXRT", + .must_be_present = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .ops = { NONE, }, + }, + /* + * We must see a BS|NV SbatLevel + */ + {.guid = SHIM_LOCK_GUID, + .name = L"SbatLevel", + .must_be_present = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .ops = { NONE, }, + }, + /* + * We must see a BS|RT SbatLevelRT + */ + {.guid = SHIM_LOCK_GUID, + .name = L"SbatLevelRT", + .must_be_present = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .ops = { NONE, }, + }, + /* + * We must not see a MokIgnoreDB + */ + {.guid = SHIM_LOCK_GUID, + .name = L"MokIgnoreDB", + .must_be_absent = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .ops = { NONE, }, + }, + /* + * We must not see MokSBState + */ + {.guid = SHIM_LOCK_GUID, + .name = L"MokSBState", + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_NON_VOLATILE, + .ops = { NONE, }, + }, + /* + * We must not see MokSBStateRT + */ + {.guid = SHIM_LOCK_GUID, + .name = L"MokSBStateRT", + .must_be_absent = true, + .attrs = EFI_VARIABLE_BOOTSERVICE_ACCESS | + EFI_VARIABLE_RUNTIME_ACCESS, + .ops = { NONE, }, + }, + {.guid = { 0, }, + .name = NULL, } + }; + + /* + * We must see the supplied values of MokListRT, MokListXRT, and + * SbatLevelRT in the config table + */ + struct mock_mok_variable_config_entry test_mok_config_table[] = { + {.name = "MokListRT", + .data_size = sizeof(test_data_efivars_1_MokListRT), + .data = test_data_efivars_1_MokListRT + }, + {.name = "MokListXRT", + .data_size = sizeof(test_data_efivars_1_MokListXRT), + .data = test_data_efivars_1_MokListXRT + }, + {.name = "SbatLevelRT", + .data_size = sizeof(test_data_efivars_1_SbatLevelRT), + .data = test_data_efivars_1_SbatLevelRT + }, + {.name = "MokListTrustedRT", + .data_size = sizeof(test_data_efivars_1_MokListTrustedRT), + .data = test_data_efivars_1_MokListTrustedRT + }, + {.name = "HSIStatus", + .data_size = sizeof(test_data_efivars_1_HSIStatus), + .data = test_data_efivars_1_HSIStatus + }, + {.name = { 0, }, + .data_size = 0, + .data = NULL, + } + }; + + UINT64 max_storage_sz = 0; + UINT64 max_var_sz = 0; + UINT64 remaining_sz = 0; + + for (size_t i = 0; i < n_mok_state_variables; i++) { + mok_rt_vars[i] = mok_state_variables[i].rtname8; } - test_vars = NULL; + mock_load_variables("test-data/efivars-1", mok_rt_vars, true); + + mock_set_variable_post_hook = setvar_post; + mock_get_variable_post_hook = getvar_post; + + mock_set_usage_limits(&mock_obnoxious_variable_limits, + &obnoxious_limits[0]); + + ret = test_mok_mirror(&test_mok_mirror_enospc_vars[0], + test_mok_config_table, + EFI_OUT_OF_RESOURCES); + + mock_set_default_usage_limits(); + mock_set_variable_post_hook = NULL; mock_get_variable_post_hook = NULL; return ret; @@ -391,7 +703,10 @@ main(void) del_policy_names[j]); mock_variable_delete_attr_policy = delete_policies[j]; - test(test_mok_mirror_0); + test(test_mok_mirror_with_enough_space); + mock_finalize_vars_and_configs(); + + test(test_mok_mirror_setvar_out_of_resources); mock_finalize_vars_and_configs(); if (delete_policies[j] == 0) diff --git a/test-sbat.c b/test-sbat.c index b37efcd..21f2b24 100644 --- a/test-sbat.c +++ b/test-sbat.c @@ -8,6 +8,7 @@ #include "sbat_var_defs.h" #endif #include "shim.h" +#include "generated_sbat_var_defs.h" #include diff --git a/tpm.c b/tpm.c index 388f8d1..7f4a1b0 100644 --- a/tpm.c +++ b/tpm.c @@ -11,6 +11,7 @@ typedef struct { UINTN measuredcount = 0; VARIABLE_RECORD *measureddata = NULL; static BOOLEAN tpm_defective = FALSE; +static BOOLEAN log_full_already_warned = FALSE; static BOOLEAN tpm_present(efi_tpm_protocol_t *tpm) { @@ -108,6 +109,16 @@ static EFI_STATUS tpm_locate_protocol(efi_tpm_protocol_t **tpm, return EFI_NOT_FOUND; } +static void warn_first_log_full(void) +{ + if (!log_full_already_warned) { + perror(L"TPM extend operation occurred, but the event could" + " not be written to one or more event logs. Applications" + " reliant on a valid event log will not function.\n"); + log_full_already_warned = TRUE; + } +} + static EFI_STATUS cc_log_event_raw(EFI_PHYSICAL_ADDRESS buf, UINTN size, UINT8 pcr, const CHAR8 *log, UINTN logsize, UINT32 type, BOOLEAN is_pe_image) @@ -143,6 +154,14 @@ static EFI_STATUS cc_log_event_raw(EFI_PHYSICAL_ADDRESS buf, UINTN size, CopyMem(event->Event, (VOID *)log, logsize); efi_status = cc->hash_log_extend_event(cc, flags, buf, (UINT64)size, event); + /* Per spec: The extend operation occurred, but the event could + * not be written to one or more event logs. We can still safely + * boot in this case, but also show a warning to let the user know. + */ + if (efi_status == EFI_VOLUME_FULL) { + warn_first_log_full(); + efi_status = EFI_SUCCESS; + } FreePool(event); return efi_status; } @@ -201,11 +220,19 @@ static EFI_STATUS tpm_log_event_raw(EFI_PHYSICAL_ADDRESS buf, UINTN size, */ efi_status = tpm2->hash_log_extend_event(tpm2, PE_COFF_IMAGE, buf, (UINT64) size, event); + if (efi_status == EFI_VOLUME_FULL) { + warn_first_log_full(); + efi_status = EFI_SUCCESS; + } } if (!hash || EFI_ERROR(efi_status)) { efi_status = tpm2->hash_log_extend_event(tpm2, 0, buf, (UINT64) size, event); + if (efi_status == EFI_VOLUME_FULL) { + warn_first_log_full(); + efi_status = EFI_SUCCESS; + } } FreePool(event); return efi_status; @@ -239,10 +266,18 @@ static EFI_STATUS tpm_log_event_raw(EFI_PHYSICAL_ADDRESS buf, UINTN size, CopyMem(event->digest, hash, sizeof(event->digest)); efi_status = tpm->log_extend_event(tpm, 0, 0, TPM_ALG_SHA, event, &eventnum, &lastevent); + if (efi_status == EFI_VOLUME_FULL) { + warn_first_log_full(); + efi_status = EFI_SUCCESS; + } } else { efi_status = tpm->log_extend_event(tpm, buf, (UINT64)size, TPM_ALG_SHA, event, &eventnum, &lastevent); + if (efi_status == EFI_VOLUME_FULL) { + warn_first_log_full(); + efi_status = EFI_SUCCESS; + } } if (efi_status == EFI_UNSUPPORTED) { perror(L"Could not write TPM event: %r. Considering " diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..0272229 --- /dev/null +++ b/utils.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent + +#include "shim.h" + +EFI_STATUS +get_file_size(EFI_FILE_HANDLE fh, UINTN *retsize) +{ + EFI_STATUS efi_status; + void *buffer = NULL; + UINTN bs = 0; + + /* The API here is "Call it once with bs=0, it fills in bs, + * then allocate a buffer and ask again to get it filled. */ + efi_status = fh->GetInfo(fh, &EFI_FILE_INFO_GUID, &bs, NULL); + if (EFI_ERROR(efi_status) && efi_status != EFI_BUFFER_TOO_SMALL) + return efi_status; + if (bs == 0) + return EFI_SUCCESS; + + buffer = AllocateZeroPool(bs); + if (!buffer) { + console_print(L"Could not allocate memory\n"); + return EFI_OUT_OF_RESOURCES; + } + efi_status = fh->GetInfo(fh, &EFI_FILE_INFO_GUID, &bs, buffer); + /* This checks *either* the error from the first GetInfo, if it isn't + * the EFI_BUFFER_TOO_SMALL we're expecting, or the second GetInfo + * call in *any* case. */ + if (EFI_ERROR(efi_status)) { + console_print(L"Could not get file info: %r\n", efi_status); + if (buffer) + FreePool(buffer); + return efi_status; + } + EFI_FILE_INFO *fi = buffer; + *retsize = fi->FileSize; + FreePool(buffer); + return EFI_SUCCESS; +} + +EFI_STATUS +read_file(EFI_FILE_HANDLE fh, CHAR16 *fullpath, CHAR16 **buffer, UINT64 *bs) +{ + EFI_FILE_HANDLE fh2; + EFI_STATUS efi_status; + + efi_status = fh->Open(fh, &fh2, fullpath, EFI_FILE_READ_ONLY, 0); + if (EFI_ERROR(efi_status)) { + console_print(L"Couldn't open \"%s\": %r\n", fullpath, efi_status); + return efi_status; + } + + UINTN len = 0; + CHAR16 *b = NULL; + efi_status = get_file_size(fh2, &len); + if (EFI_ERROR(efi_status)) { + console_print(L"Could not get file size for \"%s\": %r\n", + fullpath, efi_status); + fh2->Close(fh2); + return efi_status; + } + + if (len > 1024 * PAGE_SIZE) { + fh2->Close(fh2); + return EFI_BAD_BUFFER_SIZE; + } + + b = AllocateZeroPool(len + 2); + if (!b) { + console_print(L"Could not allocate memory\n"); + fh2->Close(fh2); + return EFI_OUT_OF_RESOURCES; + } + + efi_status = fh->Read(fh, &len, b); + if (EFI_ERROR(efi_status)) { + FreePool(b); + fh2->Close(fh2); + console_print(L"Could not read file: %r\n", efi_status); + return efi_status; + } + *buffer = b; + *bs = len; + fh2->Close(fh2); + return EFI_SUCCESS; +}