diff options
author | Steve McIntyre <steve@einval.com> | 2024-02-17 17:35:37 +0000 |
---|---|---|
committer | Steve McIntyre <steve@einval.com> | 2024-02-17 17:35:37 +0000 |
commit | a075e58606b9affb6dfb176c71caab816737a981 (patch) | |
tree | cb517fcf059f8d9d9dd7f6860a2d77d75ddc4142 | |
parent | 2dd2f7600d41253fe621b8d040ab57f0c202d71b (diff) | |
download | efi-boot-shim-upstream.tar.gz efi-boot-shim-upstream.zip |
New upstream version 15.8upstream/15.8upstream
125 files changed, 2143 insertions, 748 deletions
diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 18fe5645..7a807384 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -136,10 +136,6 @@ jobs: makearch: x86_64 distro: centos8 - arch: amd64 - efiarch: x64 - makearch: x86_64 - distro: centos7 - - arch: amd64 efiarch: ia32 makearch: ia32 distro: f36 @@ -155,10 +151,6 @@ jobs: efiarch: ia32 makearch: ia32 distro: centos8 - - arch: amd64 - efiarch: ia32 - makearch: ia32 - distro: centos7 steps: - name: Checkout @@ -33,7 +33,12 @@ Make.local /.cache/ /certdb/ /compile_commands.json +/compile_commands.events.json /cov-int/ +/crash-* +/fuzz-* +!/fuzz-*.c +/leak-* /post-process-pe /random.bin /sbat.*.csv diff --git a/.gitmodules b/.gitmodules index 78424fbd..d0f1973a 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.6 + branch = shim-15.8 @@ -78,6 +78,9 @@ Variables you could set to customize the build: - OSLABEL This is the label that will be put in BOOT$(EFI_ARCH).CSV for your OS. By default this is the same value as EFIDIR . +- 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. Vendor SBAT data: It will sometimes be requested by reviewers that a build includes extra diff --git a/Cryptlib/Makefile b/Cryptlib/Makefile index 89fd5cdc..626788c9 100644 --- a/Cryptlib/Makefile +++ b/Cryptlib/Makefile @@ -11,7 +11,8 @@ INCLUDES = -I$(CRYPTDIR) -I$(CRYPTDIR)/Include \ -isystem $(TOPDIR)/include/system \ -isystem $(shell $(CC) -print-file-name=include) -WARNFLAGS += -Wno-unused-parameter +WARNFLAGS += -Wno-unused-parameter \ + -Wno-unused-but-set-variable CFLAGS = $(FEATUREFLAGS) \ $(OPTIMIZATIONS) \ diff --git a/Cryptlib/OpenSSL/Makefile b/Cryptlib/OpenSSL/Makefile index 795f471d..d59c5d7a 100644 --- a/Cryptlib/OpenSSL/Makefile +++ b/Cryptlib/OpenSSL/Makefile @@ -23,7 +23,7 @@ FEATUREFLAGS += -nostdinc WARNFLAGS += -Wno-empty-body \ -Wno-implicit-fallthrough \ $(if $(findstring gcc,$(CC)),-Wno-old-style-declaration) \ - $(if $(findstring gcc,$(CC)),-Wno-unused-but-set-variable) \ + -Wno-unused-but-set-variable \ -Wno-unused-parameter CFLAGS = $(FEATUREFLAGS) \ diff --git a/Cryptlib/SysCall/BaseMemAllocation.c b/Cryptlib/SysCall/BaseMemAllocation.c index 792b29e8..7a565ffd 100644 --- a/Cryptlib/SysCall/BaseMemAllocation.c +++ b/Cryptlib/SysCall/BaseMemAllocation.c @@ -16,33 +16,104 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #include <OpenSslSupport.h>
//
+// Extra header to record the memory buffer size from malloc routine.
+//
+#define CRYPTMEM_HEAD_SIGNATURE EFI_SIGNATURE_32('c','m','h','d')
+typedef struct {
+ UINT32 Signature;
+ UINT32 Reserved;
+ UINTN Size;
+} CRYPTMEM_HEAD;
+
+#define CRYPTMEM_OVERHEAD sizeof(CRYPTMEM_HEAD)
+
+#define MIN(a, b) ({(a) < (b) ? (a) : (b);})
+
+//
// -- Memory-Allocation Routines --
//
/* Allocates memory blocks */
void *malloc (size_t size)
{
- return AllocatePool ((UINTN) size);
+ CRYPTMEM_HEAD *PoolHdr;
+ UINTN NewSize;
+ VOID *Data;
+
+ //
+ // Adjust the size by the buffer header overhead
+ //
+ NewSize = (UINTN)(size) + CRYPTMEM_OVERHEAD;
+
+ Data = AllocatePool (NewSize);
+ if (Data != NULL) {
+ PoolHdr = (CRYPTMEM_HEAD *)Data;
+ //
+ // Record the memory brief information
+ //
+ PoolHdr->Signature = CRYPTMEM_HEAD_SIGNATURE;
+ PoolHdr->Size = size;
+
+ return (VOID *)(PoolHdr + 1);
+ } else {
+ //
+ // The buffer allocation failed.
+ //
+ return NULL;
+ }
}
/* Reallocate memory blocks */
void *realloc (void *ptr, size_t size)
{
- //
- // BUG: hardcode OldSize == size! We have no any knowledge about
- // memory size of original pointer ptr.
- //
- return ReallocatePool (ptr, (UINTN) size, (UINTN) size);
+ CRYPTMEM_HEAD *OldPoolHdr;
+ CRYPTMEM_HEAD *NewPoolHdr;
+ UINTN OldSize;
+ UINTN NewSize;
+ VOID *Data;
+
+ NewSize = (UINTN)size + CRYPTMEM_OVERHEAD;
+ Data = AllocatePool (NewSize);
+ if (Data != NULL) {
+ NewPoolHdr = (CRYPTMEM_HEAD *)Data;
+ NewPoolHdr->Signature = CRYPTMEM_HEAD_SIGNATURE;
+ NewPoolHdr->Size = size;
+ if (ptr != NULL) {
+ //
+ // Retrieve the original size from the buffer header.
+ //
+ OldPoolHdr = (CRYPTMEM_HEAD *)ptr - 1;
+ ASSERT (OldPoolHdr->Signature == CRYPTMEM_HEAD_SIGNATURE);
+ OldSize = OldPoolHdr->Size;
+
+ //
+ // Duplicate the buffer content.
+ //
+ CopyMem ((VOID *)(NewPoolHdr + 1), ptr, MIN (OldSize, size));
+ FreePool ((VOID *)OldPoolHdr);
+ }
+
+ return (VOID *)(NewPoolHdr + 1);
+ } else {
+ //
+ // The buffer allocation failed.
+ //
+ return NULL;
+ }
}
/* De-allocates or frees a memory block */
void free (void *ptr)
{
+ CRYPTMEM_HEAD *PoolHdr;
+
//
// In Standard C, free() handles a null pointer argument transparently. This
// is not true of FreePool() below, so protect it.
//
if (ptr != NULL) {
- FreePool (ptr);
+ PoolHdr = (CRYPTMEM_HEAD *)ptr - 1;
+ ASSERT (PoolHdr->Signature == CRYPTMEM_HEAD_SIGNATURE);
+ FreePool (PoolHdr);
}
}
diff --git a/Make.defaults b/Make.defaults index c46164a3..e75cd3cd 100644 --- a/Make.defaults +++ b/Make.defaults @@ -139,6 +139,8 @@ CFLAGS = $(FEATUREFLAGS) \ $(INCLUDES) \ $(DEFINES) +POST_PROCESS_PE_FLAGS = + ifneq ($(origin OVERRIDE_SECURITY_POLICY), undefined) DEFINES += -DOVERRIDE_SECURITY_POLICY endif @@ -186,6 +188,9 @@ endif ifneq ($(origin VENDOR_DBX_FILE), undefined) DEFINES += -DVENDOR_DBX_FILE=\"$(VENDOR_DBX_FILE)\" endif +ifneq ($(origin SBAT_AUTOMATIC_DATE), undefined) +DEFINES += -DSBAT_AUTOMATIC_DATE=$(SBAT_AUTOMATIC_DATE) +endif LDFLAGS = --hash-style=sysv -nostdlib -znocombreloc -T $(EFI_LDS) -shared -Bsymbolic -L$(LOCAL_EFI_PATH) -L$(LIBDIR) -LCryptlib -LCryptlib/OpenSSL $(EFI_CRT_OBJS) --build-id=sha1 $(ARCH_LDFLAGS) --no-undefined @@ -36,6 +36,6 @@ $(strip $(foreach x,$(DEFAULT_$(1)), endef %.o : %.S - $(CC) $(CFLAGS) -c -o $@ $< + $(CC) $(CFLAGS) -c -o $@ $< $(IGNORE_COMPILER_ERRORS) # vim:filetype=make @@ -1,7 +1,7 @@ default : all NAME = shim -VERSION = 15.7 +VERSION = 15.8 ifneq ($(origin RELEASE),undefined) DASHRELEASE ?= -$(RELEASE) else @@ -38,9 +38,9 @@ CFLAGS += -DENABLE_SHIM_CERT else TARGETS += $(MMNAME) $(FBNAME) endif -OBJS = shim.o globals.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o sbat_data.o sbat_var.o pe.o httpboot.o csv.o load-options.o +OBJS = shim.o globals.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o sbat_data.o sbat_var.o pe.o pe-relocate.o httpboot.o csv.o load-options.o KEYS = shim_cert.h ocsp.* ca.* shim.crt shim.csr shim.p12 shim.pem shim.key shim.cer -ORIG_SOURCES = shim.c globals.c mok.c netboot.c replacements.c tpm.c errlog.c sbat.c pe.c httpboot.c shim.h version.h $(wildcard include/*.h) cert.S sbat_var.S +ORIG_SOURCES = shim.c globals.c mok.c netboot.c replacements.c tpm.c errlog.c sbat.c pe.c pe-relocate.c httpboot.c shim.h version.h $(wildcard include/*.h) cert.S sbat_var.S MOK_OBJS = MokManager.o PasswordCrypt.o crypt_blowfish.o errlog.o sbat_data.o globals.o ORIG_MOK_SOURCES = MokManager.c PasswordCrypt.c crypt_blowfish.c shim.h $(wildcard include/*.h) FALLBACK_OBJS = fallback.o tpm.o errlog.o sbat_data.o globals.o @@ -76,6 +76,13 @@ ifneq ($(origin EFI_PATH),undefined) $(error EFI_PATH is no longer supported, you must build using the supplied copy of gnu-efi) endif +compile_commands.json : Makefile Make.rules Make.defaults + make clean + bear -- make COMPILER=clang test all + sed -i \ + -e 's/"-maccumulate-outgoing-args",//g' \ + $@ + update : git submodule update --init --recursive @@ -156,19 +163,19 @@ gnu-efi/$(ARCH_GNUEFI)/gnuefi/libgnuefi.a gnu-efi/$(ARCH_GNUEFI)/lib/libefi.a: ARCH=$(ARCH_GNUEFI) \ TOPDIR=$(TOPDIR)/gnu-efi \ -f $(TOPDIR)/gnu-efi/Makefile \ - lib gnuefi inc + lib gnuefi inc $(IGNORE_COMPILER_ERRORS) Cryptlib/libcryptlib.a: for i in Hash Hmac Cipher Rand Pk Pem SysCall; do mkdir -p Cryptlib/$$i; done - $(MAKE) TOPDIR=$(TOPDIR) VPATH=$(TOPDIR)/Cryptlib -C Cryptlib -f $(TOPDIR)/Cryptlib/Makefile + $(MAKE) TOPDIR=$(TOPDIR) VPATH=$(TOPDIR)/Cryptlib -C Cryptlib -f $(TOPDIR)/Cryptlib/Makefile $(IGNORE_COMPILER_ERRORS) Cryptlib/OpenSSL/libopenssl.a: for i in x509v3 x509 txt_db stack sha rsa rc4 rand pkcs7 pkcs12 pem ocsp objects modes md5 lhash kdf hmac evp err dso dh conf comp cmac buffer bn bio async/arch asn1 aes; do mkdir -p Cryptlib/OpenSSL/crypto/$$i; done - $(MAKE) TOPDIR=$(TOPDIR) VPATH=$(TOPDIR)/Cryptlib/OpenSSL -C Cryptlib/OpenSSL -f $(TOPDIR)/Cryptlib/OpenSSL/Makefile + $(MAKE) TOPDIR=$(TOPDIR) VPATH=$(TOPDIR)/Cryptlib/OpenSSL -C Cryptlib/OpenSSL -f $(TOPDIR)/Cryptlib/OpenSSL/Makefile $(IGNORE_COMPILER_ERRORS) lib/lib.a: | $(TOPDIR)/lib/Makefile $(wildcard $(TOPDIR)/include/*.[ch]) mkdir -p lib - $(MAKE) VPATH=$(TOPDIR)/lib TOPDIR=$(TOPDIR) -C lib -f $(TOPDIR)/lib/Makefile + $(MAKE) VPATH=$(TOPDIR)/lib TOPDIR=$(TOPDIR) -C lib -f $(TOPDIR)/lib/Makefile $(IGNORE_COMPILER_ERRORS) post-process-pe : $(TOPDIR)/post-process-pe.c $(HOSTCC) -std=gnu11 -Og -g3 -Wall -Wextra -Wno-missing-field-initializers -Werror -o $@ $< @@ -255,7 +262,7 @@ endif -j .rela* -j .dyn -j .reloc -j .eh_frame \ -j .vendor_cert -j .sbat -j .sbatlevel \ $(FORMAT) $< $@ - ./post-process-pe -vv $@ + ./post-process-pe -vv $(POST_PROCESS_PE_FLAGS) $@ ifneq ($(origin ENABLE_SHIM_HASH),undefined) %.hash : %.efi @@ -286,6 +293,15 @@ else $(PESIGN) -n certdb -i $< -c "shim" -s -o $@ -f endif +fuzz fuzz-clean fuzz-coverage fuzz-lto : + @make -f $(TOPDIR)/include/fuzz.mk \ + COMPILER="$(COMPILER)" \ + CROSS_COMPILE="$(CROSS_COMPILE)" \ + CLANG_WARNINGS="$(CLANG_WARNINGS)" \ + ARCH_DEFINES="$(ARCH_DEFINES)" \ + EFI_INCLUDES="$(EFI_INCLUDES)" \ + fuzz-clean $@ + test test-clean test-coverage test-lto : @make -f $(TOPDIR)/include/test.mk \ COMPILER="$(COMPILER)" \ @@ -295,14 +311,21 @@ test test-clean test-coverage test-lto : EFI_INCLUDES="$(EFI_INCLUDES)" \ test-clean $@ +$(patsubst %.c,%,$(wildcard fuzz-*.c)) : + @make -f $(TOPDIR)/include/fuzz.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" $@ + $(patsubst %.c,%,$(wildcard test-*.c)) : @make -f $(TOPDIR)/include/test.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" $@ -.PHONY : $(patsubst %.c,%,$(wildcard test-*.c)) test +clean-fuzz-objs: + @make -f $(TOPDIR)/include/fuzz.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" clean clean-test-objs: @make -f $(TOPDIR)/include/test.mk EFI_INCLUDES="$(EFI_INCLUDES)" ARCH_DEFINES="$(ARCH_DEFINES)" clean +.PHONY : $(patsubst %.c,%,$(wildcard fuzz-*.c)) fuzz +.PHONY : $(patsubst %.c,%,$(wildcard test-*.c)) test + clean-gnu-efi: @if [ -d gnu-efi ] ; then \ $(MAKE) -C gnu-efi \ @@ -322,7 +345,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 + @rm -vf *.debug *.so *.efi *.efi.* *.tar.* version.c buildid post-process-pe compile_commands.json @rm -vf Cryptlib/*.[oa] Cryptlib/*/*.[oa] @if [ -d .git ] ; then git clean -f -d -e 'Cryptlib/OpenSSL/*'; fi @@ -336,7 +359,7 @@ clean-cryptlib-objs: $(MAKE) -C Cryptlib -f $(TOPDIR)/Cryptlib/Makefile clean ; \ fi -clean: clean-shim-objs clean-test-objs clean-gnu-efi clean-openssl-objs clean-cryptlib-objs clean-lib-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) diff --git a/MokVars.txt b/MokVars.txt index cdfec2c8..baf8db9a 100644 --- a/MokVars.txt +++ b/MokVars.txt @@ -53,6 +53,11 @@ The hash will be regenerated by MokManager after the user is requested to enter their password to confirm enrolment of the keys. If the hash matches MokAuth, the user will be prompted to enrol the keys. BS,RT,NV +ShimRetainProtocol: UINT8, read by Shim before uninstalling protocol. +If set to non-zero, Shim will keep the protocol in place. It can be +used by second stages to ensure the protocol is still available for +later stages, and can thus be used to verify additional PE files. BS,RT. + State variables: MokList: A list of authorized keys and hashes. An EFI_SIGNATURE_LIST @@ -25,3 +25,8 @@ There are a couple of build options, and a couple of ways to customize the build, described in [BUILDING](BUILDING). See the [test plan](testplan.txt), and file a ticket if anything fails! + +In the event that the developers need to be contacted related to a security +incident or vulnerability, please mail [secalert@redhat.com]. + +[secalert@redhat.com]: mailto:secalert@redhat.com diff --git a/SBAT.example.md b/SBAT.example.md index 930f71a1..fa86e57c 100644 --- a/SBAT.example.md +++ b/SBAT.example.md @@ -5,14 +5,14 @@ SBAT: Current proposal ------------- the `.sbat` section has the following fields: -| field | meaning | -|---|---| -| component_name | the name we're comparing -| component_generation | the generation number for the comparison -| vendor_name | human readable vendor name -| vendor_package_name | human readable package name -| vendor_version | human readable package version (maybe machine parseable too, not specified here) -| vendor_url | url to look stuff up, contact, whatever. +| field | meaning | +|----------------------|----------------------------------------------------------------------------------| +| component_name | the name we're comparing | +| component_generation | the generation number for the comparison | +| vendor_name | human readable vendor name | +| vendor_package_name | human readable package name | +| vendor_version | human readable package version (maybe machine parseable too, not specified here) | +| vendor_url | url to look stuff up, contact, whatever. | `SBAT` EFI variable ----------------- @@ -255,11 +255,11 @@ customer impact with as few re-releases as possible, while not creating an unnecessarily large UEFI revocation variable payload. | | prior to<br>disclosure\* | after<br>disclosure | after Vendor C's<br>first update | after Vendor C's<br>second update | after next global<br>disclosure | -|--------------------------------------------------------------------------------------|------------------------|---------------------|----------------------------------|----------------------------------|---------------------------------| -| GRUB global<br>generation number in<br>artifacts .sbat section | 3 | 4 | 4 | 4 | 5 | -| Vendor C's product-specific<br>generation number in artifact's<br>.sbat section | 1 | 1 | 2 | 3 | 1 | -| GRUB global<br>generation number in<br>UEFI SBAT revocation variable | 3 | 4 | 4 | 4 | 5 | -| Vendor C's product-specific<br>generation number in<br>UEFI SBAT revocation variable | not set | not set | 2 | 3 | not set | +|--------------------------------------------------------------------------------------|--------------------------|---------------------|----------------------------------|-----------------------------------|---------------------------------| +| GRUB global<br>generation number in<br>artifacts .sbat section | 3 | 4 | 4 | 4 | 5 | +| Vendor C's product-specific<br>generation number in artifact's<br>.sbat section | 1 | 1 | 2 | 3 | 1 | +| GRUB global<br>generation number in<br>UEFI SBAT revocation variable | 3 | 4 | 4 | 4 | 5 | +| Vendor C's product-specific<br>generation number in<br>UEFI SBAT revocation 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. @@ -307,7 +307,7 @@ most up to date UEFI metadata. Even prior to or without moving to one-shim, it is desirable to get every vendor onto as few shims as possible. Ideally a vendor would have a single shim signed with their certificate embedded and then use that certificate to sign -additional <Vendor>_key.EFI key files that then contain all the keys that the +additional `<Vendor>_key.EFI` key files that then contain all the keys that the individual components for their products are signed with. This file name needs to be registered at the time of shim review and should not be changed without going back to a shim review. A vendor should be able to store as many @@ -354,14 +354,14 @@ them. Adding a .sbat section containing the SBAT metadata structure to PE images. -| field | meaning | -|---|---| -| component_name | the name we're comparing -| component_generation | the generation number for the comparison -| vendor_name | human readable vendor name -| vendor_package_name | human readable package name -| vendor_version | human readable package version (maybe machine parseable too, not specified here) -| vendor_url | url to look stuff up, contact, whatever. +| field | meaning | +|----------------------|----------------------------------------------------------------------------------| +| component_name | the name we're comparing | +| component_generation | the generation number for the comparison | +| vendor_name | human readable vendor name | +| vendor_package_name | human readable package name | +| vendor_version | human readable package version (maybe machine parseable too, not specified here) | +| vendor_url | url to look stuff up, contact, whatever. | The format of this .sbat section is comma separated values, or more specifically ASCII encoded strings. @@ -448,7 +448,7 @@ fixed. The following show the evolution over a sample set of events: ## Starting point -Before CVEs are encountered, an undesirable moudule was built into the a fedora +Before CVEs are encountered, an undesirable module was built into Fedora's grub, so it's product-specific generation number has been bumped: ``` diff --git a/SbatLevel_Variable.txt b/SbatLevel_Variable.txt new file mode 100644 index 00000000..42a388e4 --- /dev/null +++ b/SbatLevel_Variable.txt @@ -0,0 +1,108 @@ +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: + +Name: SbatLevel +Attributes: (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS) +Namespace Guid: 605dab50-e046-4300-abb6-3dd810dd8b23 + +Variable content: + +Initialized, no revocations: + +sbat,1,2021030218 + +To Revoke GRUB2 binaries impacted by + +* CVE-2021-3695 +* CVE-2021-3696 +* CVE-2021-3697 +* CVE-2022-28733 +* CVE-2022-28734 +* CVE-2022-28735 +* CVE-2022-28736 + +sbat,1,2022052400 +grub,2 + +and shim binaries impacted by + +* CVE-2022-28737 + +sbat,1,2022052400 +shim,2 +grub,2 + +Shim delivered both versions of these revocations with +the same 2022052400 date stamp, once as an opt-in latest +revocation with shim,2 and then as an automatic revocation without +shim,2 + + +To revoke GRUB2 grub binaries impacted by + +* CVE-2022-2601 +* CVE-2022-3775 + +sbat,1,2022111500 +shim,2 +grub,3 + +To revoke Debian's grub.3 which missed +the patches: + +sbat,1,2023012900 +shim,2 +grub,3 +grub.debian,4 + + +An additonal bug was fixed in shim that was not considered exploitable, +can be revoked by setting: + +sbat,1,2023012950 +shim,3 +grub,3 +grub.debian,4 + +shim did not deliver this payload at the time + + +To Revoke GRUB2 binaries impacted by: + +* CVE-2023-4692 +* CVE-2023-4693 + +These CVEs are in the ntfs module and vendors that do and do not +ship this module as part of their signed binary are split. + +sbat,1,2023091900 +shim,2 +grub,4 + +Since not everyone has shipped updated GRUB packages, shim did not +deliver this revocation at the time. + +To Revoke shim binaries impacted by: + +* CVE-2023-40547 +* CVE-2023-40546 +* CVE-2023-40548 +* CVE-2023-40549 +* CVE-2023-40550 +* CVE-2023-40551 + +sbat,1,2024010900 +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: + +sbat,1,2024<date TBD> +shim,4 +grub,4 @@ -52,3 +52,4 @@ vendor_deauthorized: #endif .Lvendor_deauthorized_end: .Lcert_table_end: + .section .note.GNU-stack,"a" diff --git a/commit b/commit deleted file mode 100644 index ba705c69..00000000 --- a/commit +++ /dev/null @@ -1 +0,0 @@ -11491619f4336fef41c3519877ba242161763580
\ No newline at end of file diff --git a/data/sbat.csv b/data/sbat.csv index 2beec754..18293727 100755 --- a/data/sbat.csv +++ b/data/sbat.csv @@ -1,2 +1,2 @@ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md -shim,3,UEFI shim,shim,1,https://github.com/rhboot/shim +shim,4,UEFI shim,shim,1,https://github.com/rhboot/shim @@ -32,6 +32,9 @@ VLogError(const char *file, int line, const char *func, const CHAR16 *fmt, ms_va_list args2; CHAR16 **newerrs; + if (file == NULL || func == NULL || fmt == NULL) + return EFI_INVALID_PARAMETER; + newerrs = ReallocatePool(errs, (nerrs + 1) * sizeof(*errs), (nerrs + 3) * sizeof(*errs)); if (!newerrs) @@ -1006,14 +1006,14 @@ try_start_first_option(EFI_HANDLE parent_image_handle) EFI_HANDLE image_handle; if (get_fallback_verbose()) { - int fallback_verbose_wait = 500000; /* default to 0.5s */ + unsigned long fallback_verbose_wait = 500000; /* default to 0.5s */ #ifdef FALLBACK_VERBOSE_WAIT fallback_verbose_wait = FALLBACK_VERBOSE_WAIT; #endif console_print(L"Verbose enabled, sleeping for %d mseconds... " L"Press the Pause key now to hold for longer.\n", fallback_verbose_wait); - msleep(fallback_verbose_wait); + usleep(fallback_verbose_wait); } if (!first_new_option) { @@ -1036,7 +1036,7 @@ try_start_first_option(EFI_HANDLE parent_image_handle) } console_print(L"\n"); - msleep(500000000); + usleep(500000000); return efi_status; } @@ -1051,7 +1051,7 @@ try_start_first_option(EFI_HANDLE parent_image_handle) efi_status = BS->StartImage(image_handle, NULL, NULL); if (EFI_ERROR(efi_status)) { console_print(L"StartImage failed: %r\n", efi_status); - msleep(500000000); + usleep(500000000); } return efi_status; } @@ -1211,14 +1211,14 @@ reset: console_print(L"Reset System\n"); if (get_fallback_verbose()) { - int fallback_verbose_wait = 500000; /* default to 0.5s */ + unsigned long fallback_verbose_wait = 500000; /* default to 0.5s */ #ifdef FALLBACK_VERBOSE_WAIT fallback_verbose_wait = FALLBACK_VERBOSE_WAIT; #endif console_print(L"Verbose enabled, sleeping for %d mseconds... " L"Press the Pause key now to hold for longer.\n", fallback_verbose_wait); - msleep(fallback_verbose_wait); + usleep(fallback_verbose_wait); } RT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL); diff --git a/fuzz-csv.c b/fuzz-csv.c new file mode 100644 index 00000000..bc701df0 --- /dev/null +++ b/fuzz-csv.c @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * test-csv.c - test our csv parser + */ + +#ifndef SHIM_UNIT_TEST +#define SHIM_UNIT_TEST +#endif +#include "shim.h" + +#include <stdio.h> + +int +test_csv_simple_fuzz(char *random_bin, size_t random_bin_len) +{ + list_t entry_list; + size_t i; + char *current, *end; + list_t *pos = NULL; + EFI_STATUS efi_status; + + INIT_LIST_HEAD(&entry_list); + + current = &random_bin[0]; + current = current + 1 - 1; + end = current + random_bin_len - 1; + *end = '\0'; + + efi_status = parse_csv_data(current, end, 7, &entry_list); + if (efi_status != EFI_SUCCESS) + return 0; + if (list_size(&entry_list) <= 1) + goto fail; + + i = 0; + list_for_each(pos, &entry_list) { + struct csv_row *csv_row; + + csv_row = list_entry(pos, struct csv_row, list); + i++; + } + + free_csv_list(&entry_list); + + return 0; +fail: + free_csv_list(&entry_list); + return -1; +} + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + int rc; + uint8_t *data_copy; + + if (size < 1) + return 0; + + data_copy = malloc(size); + if (!data_copy) + return -1; + + memcpy(data_copy, data, size); + rc = test_csv_simple_fuzz((char *)data_copy, size); + free(data_copy); + + return rc; // Values other than 0 and -1 are reserved for future use. +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/fuzz-pe-relocate.c b/fuzz-pe-relocate.c new file mode 100644 index 00000000..1f62234d --- /dev/null +++ b/fuzz-pe-relocate.c @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * fuzz-pe-relocate.c - fuzz our PE relocation code. + * Copyright Peter Jones <pjones@redhat.com> + */ + +#ifndef SHIM_UNIT_TEST +#define SHIM_UNIT_TEST +#endif +#include "shim.h" + +UINT8 mok_policy = 0; + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + uint8_t *data_copy; + EFI_STATUS status = 0; + size_t n = 0; + PE_COFF_LOADER_IMAGE_CONTEXT context = { 0, }; + + if (size < 1) + return 0; + + data_copy = malloc(size+1); + if (!data_copy) + return -1; + + memcpy(data_copy, data, size); + data_copy[size] = 0; + + status = read_header(data_copy, size, &context); + + free(data_copy); + + return 0; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/fuzz-sbat.c b/fuzz-sbat.c new file mode 100644 index 00000000..74d313bb --- /dev/null +++ b/fuzz-sbat.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * fuzz-sbat-section.c - fuzz our .sbat parsing code + * Copyright Peter Jones <pjones@redhat.com> + */ + +#ifndef SHIM_UNIT_TEST +#define SHIM_UNIT_TEST +#endif +#include "shim.h" + +#include <stdio.h> + +list_t sbat_var; + +BOOLEAN +secure_mode() { + return 1; +} + +int +LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + uint8_t *data_copy; + EFI_STATUS status = 0; + size_t n = 0; + struct sbat_section_entry **entries = NULL; + + if (size < 1) + return 0; + + data_copy = malloc(size+1); + if (!data_copy) + return -1; + + memcpy(data_copy, data, size); + data_copy[size] = 0; + status = parse_sbat_section(data_copy, size, &n, &entries); + cleanup_sbat_section_entries(n, entries); + + free(data_copy); + + return 0; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/gnu-efi/Make.defaults b/gnu-efi/Make.defaults index 3b56150d..59b65080 100755 --- a/gnu-efi/Make.defaults +++ b/gnu-efi/Make.defaults @@ -205,7 +205,7 @@ endif ASFLAGS += $(ARCH3264) LDFLAGS += -nostdlib --warn-common --no-undefined --fatal-warnings \ - --build-id=sha1 + --build-id=sha1 --no-warn-rwx-segments ifneq ($(ARCH),arm) export LIBGCC=$(shell $(CC) $(CFLAGS) $(ARCH3264) -print-libgcc-file-name) diff --git a/gnu-efi/apps/trivial.S b/gnu-efi/apps/trivial.S index 40bc68fe..59db4347 100644 --- a/gnu-efi/apps/trivial.S +++ b/gnu-efi/apps/trivial.S @@ -41,3 +41,4 @@ _start: hello: .byte 'h',0,'e',0,'l',0,'l',0,'o',0,'\n',0,'\r',0,0,0 #endif + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/crt0-efi-aarch64.S b/gnu-efi/gnuefi/crt0-efi-aarch64.S index 0fefec0c..6a45aabe 100644 --- a/gnu-efi/gnuefi/crt0-efi-aarch64.S +++ b/gnu-efi/gnuefi/crt0-efi-aarch64.S @@ -51,3 +51,4 @@ _start: .4byte .dummy1-.dummy0 // Page RVA .4byte 10 // Block Size (2*4+2) .2byte (IMAGE_REL_ABSOLUTE<<12) + 0 // reloc for dummy + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/crt0-efi-arm.S b/gnu-efi/gnuefi/crt0-efi-arm.S index 1efc21c3..2a674382 100644 --- a/gnu-efi/gnuefi/crt0-efi-arm.S +++ b/gnu-efi/gnuefi/crt0-efi-arm.S @@ -191,3 +191,4 @@ _start: .L_DYNAMIC: .word _DYNAMIC - . + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/crt0-efi-ia32.S b/gnu-efi/gnuefi/crt0-efi-ia32.S index c9b3ea67..35c65411 100644 --- a/gnu-efi/gnuefi/crt0-efi-ia32.S +++ b/gnu-efi/gnuefi/crt0-efi-ia32.S @@ -75,3 +75,4 @@ _start: .4byte .dummy1-.dummy0 // Page RVA .4byte 10 // Block Size (2*4+2) .2byte (IMAGE_REL_ABSOLUTE<<12) + 0 // reloc for dummy + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/crt0-efi-ia64.S b/gnu-efi/gnuefi/crt0-efi-ia64.S index 40c3c837..cfe603b1 100644 --- a/gnu-efi/gnuefi/crt0-efi-ia64.S +++ b/gnu-efi/gnuefi/crt0-efi-ia64.S @@ -85,3 +85,4 @@ _start_plabel: data4 12 // Block Size (2*4+2*2) data2 (IMAGE_REL_BASED_DIR64<<12) + 0 // reloc for plabel's entry point data2 (IMAGE_REL_BASED_DIR64<<12) + 8 // reloc for plabel's global pointer + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/crt0-efi-mips64el.S b/gnu-efi/gnuefi/crt0-efi-mips64el.S index 6a62aca9..91696843 100644 --- a/gnu-efi/gnuefi/crt0-efi-mips64el.S +++ b/gnu-efi/gnuefi/crt0-efi-mips64el.S @@ -186,3 +186,4 @@ _pc: .end _start .set pop + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/crt0-efi-x86_64.S b/gnu-efi/gnuefi/crt0-efi-x86_64.S index 0d99c151..8a4aabf5 100644 --- a/gnu-efi/gnuefi/crt0-efi-x86_64.S +++ b/gnu-efi/gnuefi/crt0-efi-x86_64.S @@ -72,3 +72,4 @@ _start: .4byte .dummy1-.dummy0 // Page RVA .4byte 10 // Block Size (2*4+2) .2byte (IMAGE_REL_ABSOLUTE<<12) + 0 // reloc for dummy + .section .note.GNU-stack,"a" diff --git a/gnu-efi/gnuefi/reloc_ia64.S b/gnu-efi/gnuefi/reloc_ia64.S index 40203bfb..6e55a154 100644 --- a/gnu-efi/gnuefi/reloc_ia64.S +++ b/gnu-efi/gnuefi/reloc_ia64.S @@ -225,3 +225,4 @@ apply_FPTR64: fptr_mem_base: .space MAX_FUNCTION_DESCRIPTORS*16 fptr_mem_limit: + .section .note.GNU-stack,"a" diff --git a/gnu-efi/ia32/gnuefi/crt0-efi-ia32.o b/gnu-efi/ia32/gnuefi/crt0-efi-ia32.o Binary files differnew file mode 100644 index 00000000..8dfe2ab3 --- /dev/null +++ b/gnu-efi/ia32/gnuefi/crt0-efi-ia32.o diff --git a/gnu-efi/ia32/gnuefi/libgnuefi.a b/gnu-efi/ia32/gnuefi/libgnuefi.a Binary files differnew file mode 100644 index 00000000..e1a6097c --- /dev/null +++ b/gnu-efi/ia32/gnuefi/libgnuefi.a diff --git a/gnu-efi/ia32/gnuefi/reloc_ia32.o b/gnu-efi/ia32/gnuefi/reloc_ia32.o Binary files differnew file mode 100644 index 00000000..58fb22b5 --- /dev/null +++ b/gnu-efi/ia32/gnuefi/reloc_ia32.o diff --git a/gnu-efi/ia32/lib/boxdraw.o b/gnu-efi/ia32/lib/boxdraw.o Binary files differnew file mode 100644 index 00000000..98274342 --- /dev/null +++ b/gnu-efi/ia32/lib/boxdraw.o diff --git a/gnu-efi/ia32/lib/cmdline.o b/gnu-efi/ia32/lib/cmdline.o Binary files differnew file mode 100644 index 00000000..ef32059e --- /dev/null +++ b/gnu-efi/ia32/lib/cmdline.o diff --git a/gnu-efi/ia32/lib/console.o b/gnu-efi/ia32/lib/console.o Binary files differnew file mode 100644 index 00000000..1d3fe24f --- /dev/null +++ b/gnu-efi/ia32/lib/console.o diff --git a/gnu-efi/ia32/lib/crc.o b/gnu-efi/ia32/lib/crc.o Binary files differnew file mode 100644 index 00000000..ec81bf5b --- /dev/null +++ b/gnu-efi/ia32/lib/crc.o diff --git a/gnu-efi/ia32/lib/data.o b/gnu-efi/ia32/lib/data.o Binary files differnew file mode 100644 index 00000000..59d7aea8 --- /dev/null +++ b/gnu-efi/ia32/lib/data.o diff --git a/gnu-efi/ia32/lib/debug.o b/gnu-efi/ia32/lib/debug.o Binary files differnew file mode 100644 index 00000000..91098aa7 --- /dev/null +++ b/gnu-efi/ia32/lib/debug.o diff --git a/gnu-efi/ia32/lib/dpath.o b/gnu-efi/ia32/lib/dpath.o Binary files differnew file mode 100644 index 00000000..41371ed3 --- /dev/null +++ b/gnu-efi/ia32/lib/dpath.o diff --git a/gnu-efi/ia32/lib/error.o b/gnu-efi/ia32/lib/error.o Binary files differnew file mode 100644 index 00000000..c0d875cb --- /dev/null +++ b/gnu-efi/ia32/lib/error.o diff --git a/gnu-efi/ia32/lib/event.o b/gnu-efi/ia32/lib/event.o Binary files differnew file mode 100644 index 00000000..823f4cd2 --- /dev/null +++ b/gnu-efi/ia32/lib/event.o diff --git a/gnu-efi/ia32/lib/exit.o b/gnu-efi/ia32/lib/exit.o Binary files differnew file mode 100644 index 00000000..689a98b7 --- /dev/null +++ b/gnu-efi/ia32/lib/exit.o diff --git a/gnu-efi/ia32/lib/guid.o b/gnu-efi/ia32/lib/guid.o Binary files differnew file mode 100644 index 00000000..b1d3e956 --- /dev/null +++ b/gnu-efi/ia32/lib/guid.o diff --git a/gnu-efi/ia32/lib/hand.o b/gnu-efi/ia32/lib/hand.o Binary files differnew file mode 100644 index 00000000..dae96ec4 --- /dev/null +++ b/gnu-efi/ia32/lib/hand.o diff --git a/gnu-efi/ia32/lib/hw.o b/gnu-efi/ia32/lib/hw.o Binary files differnew file mode 100644 index 00000000..48ef884b --- /dev/null +++ b/gnu-efi/ia32/lib/hw.o diff --git a/gnu-efi/ia32/lib/ia32/initplat.o b/gnu-efi/ia32/lib/ia32/initplat.o Binary files differnew file mode 100644 index 00000000..5222a6d5 --- /dev/null +++ b/gnu-efi/ia32/lib/ia32/initplat.o diff --git a/gnu-efi/ia32/lib/ia32/math.o b/gnu-efi/ia32/lib/ia32/math.o Binary files differnew file mode 100644 index 00000000..82fd313d --- /dev/null +++ b/gnu-efi/ia32/lib/ia32/math.o diff --git a/gnu-efi/ia32/lib/ia32/setjmp.o b/gnu-efi/ia32/lib/ia32/setjmp.o Binary files differnew file mode 100644 index 00000000..ac753822 --- /dev/null +++ b/gnu-efi/ia32/lib/ia32/setjmp.o diff --git a/gnu-efi/ia32/lib/init.o b/gnu-efi/ia32/lib/init.o Binary files differnew file mode 100644 index 00000000..d785857f --- /dev/null +++ b/gnu-efi/ia32/lib/init.o diff --git a/gnu-efi/ia32/lib/libefi.a b/gnu-efi/ia32/lib/libefi.a Binary files differnew file mode 100644 index 00000000..78a165bf --- /dev/null +++ b/gnu-efi/ia32/lib/libefi.a diff --git a/gnu-efi/ia32/lib/lock.o b/gnu-efi/ia32/lib/lock.o Binary files differnew file mode 100644 index 00000000..cad0d165 --- /dev/null +++ b/gnu-efi/ia32/lib/lock.o diff --git a/gnu-efi/ia32/lib/misc.o b/gnu-efi/ia32/lib/misc.o Binary files differnew file mode 100644 index 00000000..860b8373 --- /dev/null +++ b/gnu-efi/ia32/lib/misc.o diff --git a/gnu-efi/ia32/lib/pause.o b/gnu-efi/ia32/lib/pause.o Binary files differnew file mode 100644 index 00000000..fec13d53 --- /dev/null +++ b/gnu-efi/ia32/lib/pause.o diff --git a/gnu-efi/ia32/lib/print.o b/gnu-efi/ia32/lib/print.o Binary files differnew file mode 100644 index 00000000..3c8dd17d --- /dev/null +++ b/gnu-efi/ia32/lib/print.o diff --git a/gnu-efi/ia32/lib/runtime/efirtlib.o b/gnu-efi/ia32/lib/runtime/efirtlib.o Binary files differnew file mode 100644 index 00000000..80a4d84a --- /dev/null +++ b/gnu-efi/ia32/lib/runtime/efirtlib.o diff --git a/gnu-efi/ia32/lib/runtime/rtdata.o b/gnu-efi/ia32/lib/runtime/rtdata.o Binary files differnew file mode 100644 index 00000000..920475c3 --- /dev/null +++ b/gnu-efi/ia32/lib/runtime/rtdata.o diff --git a/gnu-efi/ia32/lib/runtime/rtlock.o b/gnu-efi/ia32/lib/runtime/rtlock.o Binary files differnew file mode 100644 index 00000000..ba24dd31 --- /dev/null +++ b/gnu-efi/ia32/lib/runtime/rtlock.o diff --git a/gnu-efi/ia32/lib/runtime/rtstr.o b/gnu-efi/ia32/lib/runtime/rtstr.o Binary files differnew file mode 100644 index 00000000..a75652da --- /dev/null +++ b/gnu-efi/ia32/lib/runtime/rtstr.o diff --git a/gnu-efi/ia32/lib/runtime/vm.o b/gnu-efi/ia32/lib/runtime/vm.o Binary files differnew file mode 100644 index 00000000..88183085 --- /dev/null +++ b/gnu-efi/ia32/lib/runtime/vm.o diff --git a/gnu-efi/ia32/lib/smbios.o b/gnu-efi/ia32/lib/smbios.o Binary files differnew file mode 100644 index 00000000..7f406a51 --- /dev/null +++ b/gnu-efi/ia32/lib/smbios.o diff --git a/gnu-efi/ia32/lib/sread.o b/gnu-efi/ia32/lib/sread.o Binary files differnew file mode 100644 index 00000000..102b6652 --- /dev/null +++ b/gnu-efi/ia32/lib/sread.o diff --git a/gnu-efi/ia32/lib/str.o b/gnu-efi/ia32/lib/str.o Binary files differnew file mode 100644 index 00000000..c16826f4 --- /dev/null +++ b/gnu-efi/ia32/lib/str.o diff --git a/gnu-efi/inc/aarch64/efisetjmp_arch.h b/gnu-efi/inc/aarch64/efisetjmp_arch.h index 8dbce078..ecedc406 100644 --- a/gnu-efi/inc/aarch64/efisetjmp_arch.h +++ b/gnu-efi/inc/aarch64/efisetjmp_arch.h @@ -28,6 +28,6 @@ typedef struct { UINT64 D13; UINT64 D14; UINT64 D15; -} ALIGN(JMPBUF_ALIGN) jmp_buf[1]; +} __attribute__((__aligned__(JMPBUF_ALIGN))) jmp_buf[1]; #endif /* GNU_EFI_AARCH64_SETJMP_H */ diff --git a/gnu-efi/inc/arm/efisetjmp_arch.h b/gnu-efi/inc/arm/efisetjmp_arch.h index 17f5dc0f..0f1ca1e2 100644 --- a/gnu-efi/inc/arm/efisetjmp_arch.h +++ b/gnu-efi/inc/arm/efisetjmp_arch.h @@ -16,6 +16,6 @@ typedef struct { UINT32 R12; UINT32 R13; UINT32 R14; -} ALIGN(JMPBUF_ALIGN) jmp_buf[1]; +} __attribute__((__aligned__(JMPBUF_ALIGN))) jmp_buf[1]; #endif /* GNU_EFI_ARM_SETJMP_H */ diff --git a/gnu-efi/inc/ia32/efisetjmp_arch.h b/gnu-efi/inc/ia32/efisetjmp_arch.h index a5c1a81c..35d13b32 100644 --- a/gnu-efi/inc/ia32/efisetjmp_arch.h +++ b/gnu-efi/inc/ia32/efisetjmp_arch.h @@ -10,6 +10,6 @@ typedef struct { UINT32 Ebp; UINT32 Esp; UINT32 Eip; -} ALIGN(JMPBUF_ALIGN) jmp_buf[1]; +} __attribute__((__aligned__(JMPBUF_ALIGN))) jmp_buf[1]; #endif /* GNU_EFI_IA32_SETJMP_H */ diff --git a/gnu-efi/inc/ia64/efisetjmp_arch.h b/gnu-efi/inc/ia64/efisetjmp_arch.h index ceda4481..fe290c92 100644 --- a/gnu-efi/inc/ia64/efisetjmp_arch.h +++ b/gnu-efi/inc/ia64/efisetjmp_arch.h @@ -42,6 +42,6 @@ typedef struct { UINT64 Predicates; UINT64 LoopCount; UINT64 FPSR; -} ALIGN(JMPBUF_ALIGN) jmp_buf[1]; +} __attribute__((__aligned__(JMPBUF_ALIGN))) jmp_buf[1]; #endif /* GNU_EFI_IA64_SETJMP_H */ diff --git a/gnu-efi/inc/mips64el/efisetjmp_arch.h b/gnu-efi/inc/mips64el/efisetjmp_arch.h index 2b8f756e..1991a5c9 100644 --- a/gnu-efi/inc/mips64el/efisetjmp_arch.h +++ b/gnu-efi/inc/mips64el/efisetjmp_arch.h @@ -29,6 +29,6 @@ typedef struct { UINT64 F30; UINT64 F31; #endif -} ALIGN(JMPBUF_ALIGN) jmp_buf[1]; +} __attribute__((__aligned__(JMPBUF_ALIGN))) jmp_buf[1]; #endif /* GNU_EFI_MIPS64EL_SETJMP_H */ diff --git a/gnu-efi/inc/x86_64/efisetjmp_arch.h b/gnu-efi/inc/x86_64/efisetjmp_arch.h index b1ad1fe3..a5229f65 100644 --- a/gnu-efi/inc/x86_64/efisetjmp_arch.h +++ b/gnu-efi/inc/x86_64/efisetjmp_arch.h @@ -17,6 +17,6 @@ typedef struct { UINT64 Rip; UINT64 MxCsr; UINT8 XmmBuffer[160]; // XMM6 - XMM15 -} ALIGN(JMPBUF_ALIGN) jmp_buf[1]; +} __attribute__((__aligned__(JMPBUF_ALIGN))) jmp_buf[1]; #endif /* GNU_EFI_X86_64_SETJMP_H */ diff --git a/gnu-efi/lib/aarch64/efi_stub.S b/gnu-efi/lib/aarch64/efi_stub.S index 464eae58..fa951c92 100644 --- a/gnu-efi/lib/aarch64/efi_stub.S +++ b/gnu-efi/lib/aarch64/efi_stub.S @@ -1 +1,2 @@ /* This stub is a stub to make the build happy */ + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/aarch64/setjmp.S b/gnu-efi/lib/aarch64/setjmp.S index 46c29b16..ce18bd83 100644 --- a/gnu-efi/lib/aarch64/setjmp.S +++ b/gnu-efi/lib/aarch64/setjmp.S @@ -58,3 +58,4 @@ longjmp: mov w0, #1 csel w0, w1, w0, ne br x30 + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/div.S b/gnu-efi/lib/arm/div.S index 71158b6f..86e80695 100644 --- a/gnu-efi/lib/arm/div.S +++ b/gnu-efi/lib/arm/div.S @@ -153,3 +153,4 @@ label1: @ What to do about division by zero? For now, just return. ASM_PFX(__aeabi_idiv0): bx r14 + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/efi_stub.S b/gnu-efi/lib/arm/efi_stub.S index 464eae58..fa951c92 100644 --- a/gnu-efi/lib/arm/efi_stub.S +++ b/gnu-efi/lib/arm/efi_stub.S @@ -1 +1,2 @@ /* This stub is a stub to make the build happy */ + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/ldivmod.S b/gnu-efi/lib/arm/ldivmod.S index edbf89ed..33afa60c 100644 --- a/gnu-efi/lib/arm/ldivmod.S +++ b/gnu-efi/lib/arm/ldivmod.S @@ -59,3 +59,4 @@ L_Exit: + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/llsl.S b/gnu-efi/lib/arm/llsl.S index 0f5c4078..c556cd1b 100644 --- a/gnu-efi/lib/arm/llsl.S +++ b/gnu-efi/lib/arm/llsl.S @@ -39,3 +39,4 @@ ASM_PFX(__aeabi_llsl): lsl r1,r0,r3 mov r0,#0 bx lr + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/llsr.S b/gnu-efi/lib/arm/llsr.S index 432b27d7..096b728b 100644 --- a/gnu-efi/lib/arm/llsr.S +++ b/gnu-efi/lib/arm/llsr.S @@ -39,3 +39,4 @@ ASM_PFX(__aeabi_llsr): lsr r0,r1,r3 mov r1,#0 bx lr + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/mullu.S b/gnu-efi/lib/arm/mullu.S index 39b9a80b..de715519 100644 --- a/gnu-efi/lib/arm/mullu.S +++ b/gnu-efi/lib/arm/mullu.S @@ -31,3 +31,4 @@ ASM_PFX(__aeabi_lmul): mla r1, r2, r1, ip mla r1, r3, lr, r1 ldmia sp!, {pc} + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/setjmp.S b/gnu-efi/lib/arm/setjmp.S index bd61a8d8..851d1d5c 100644 --- a/gnu-efi/lib/arm/setjmp.S +++ b/gnu-efi/lib/arm/setjmp.S @@ -23,3 +23,4 @@ setjmp: .type longjmp, %function longjmp: ldmia r0, {r3-r12,r14} + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/arm/uldiv.S b/gnu-efi/lib/arm/uldiv.S index f478898d..bd2de59c 100644 --- a/gnu-efi/lib/arm/uldiv.S +++ b/gnu-efi/lib/arm/uldiv.S @@ -265,3 +265,4 @@ ASM_PFX(__aeabi_ldiv0): bx r14 + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/ia32/efi_stub.S b/gnu-efi/lib/ia32/efi_stub.S index 464eae58..fa951c92 100644 --- a/gnu-efi/lib/ia32/efi_stub.S +++ b/gnu-efi/lib/ia32/efi_stub.S @@ -1 +1,2 @@ /* This stub is a stub to make the build happy */ + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/ia32/setjmp.S b/gnu-efi/lib/ia32/setjmp.S index aa9c0846..68a00a8f 100644 --- a/gnu-efi/lib/ia32/setjmp.S +++ b/gnu-efi/lib/ia32/setjmp.S @@ -43,3 +43,4 @@ longjmp: movl (%edx), %ebx movl 4(%edx), %esi movl 8(%edx), %edi + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/ia64/palproc.S b/gnu-efi/lib/ia64/palproc.S index c304a78d..8ee6f9c0 100644 --- a/gnu-efi/lib/ia64/palproc.S +++ b/gnu-efi/lib/ia64/palproc.S @@ -159,3 +159,4 @@ StackedComeBackFromPALCall: PROCEDURE_EXIT(MakeStackedPALCall) + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/ia64/setjmp.S b/gnu-efi/lib/ia64/setjmp.S index bbb29d8b..ba0fbd66 100644 --- a/gnu-efi/lib/ia64/setjmp.S +++ b/gnu-efi/lib/ia64/setjmp.S @@ -197,3 +197,4 @@ _skip_flushrs: invala mov ar.rsc = r16 br.ret.sptk b0 + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/mips64el/efi_stub.S b/gnu-efi/lib/mips64el/efi_stub.S index 464eae58..fa951c92 100644 --- a/gnu-efi/lib/mips64el/efi_stub.S +++ b/gnu-efi/lib/mips64el/efi_stub.S @@ -1 +1,2 @@ /* This stub is a stub to make the build happy */ + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/mips64el/setjmp.S b/gnu-efi/lib/mips64el/setjmp.S index 930aca44..a620a6eb 100644 --- a/gnu-efi/lib/mips64el/setjmp.S +++ b/gnu-efi/lib/mips64el/setjmp.S @@ -90,3 +90,4 @@ longjmp: li $v0, 1 movn $v0, $a1, $a1 jr $ra + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/x86_64/efi_stub.S b/gnu-efi/lib/x86_64/efi_stub.S index b4312556..16542e22 100644 --- a/gnu-efi/lib/x86_64/efi_stub.S +++ b/gnu-efi/lib/x86_64/efi_stub.S @@ -187,3 +187,4 @@ ENTRY(efi_call10) ret #endif + .section .note.GNU-stack,"a" diff --git a/gnu-efi/lib/x86_64/setjmp.S b/gnu-efi/lib/x86_64/setjmp.S index e3e51959..56653d70 100644 --- a/gnu-efi/lib/x86_64/setjmp.S +++ b/gnu-efi/lib/x86_64/setjmp.S @@ -46,3 +46,4 @@ longjmp: cmp %rax,%rdx cmove %rcx,%rax jmp *0x38(%rdi) + .section .note.GNU-stack,"a" @@ -578,7 +578,13 @@ receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size) } if (*buf_size == 0) { - perror(L"Failed to get Content-Lenght\n"); + perror(L"Failed to get Content-Length\n"); + goto error; + } + + if (*buf_size < rx_message.BodyLength) { + efi_status = EFI_BAD_BUFFER_SIZE; + perror(L"Invalid Content-Length\n"); goto error; } @@ -713,18 +719,20 @@ error: } EFI_STATUS -httpboot_fetch_buffer (EFI_HANDLE image, VOID **buffer, UINT64 *buf_size) +httpboot_fetch_buffer (EFI_HANDLE image, VOID **buffer, UINT64 *buf_size, + CHAR8 *name) { EFI_STATUS efi_status; EFI_HANDLE nic; - CHAR8 next_loader[sizeof DEFAULT_LOADER_CHAR]; + CHAR8 *next_loader; CHAR8 *next_uri = NULL; CHAR8 *hostname = NULL; if (!uri) return EFI_NOT_READY; - translate_slashes(next_loader, DEFAULT_LOADER_CHAR); + next_loader = (CHAR8 *)AllocatePool((strlen(name) + 1) * sizeof (CHAR8)); + translate_slashes(next_loader, name); /* Create the URI for the next loader based on the original URI */ efi_status = generate_next_uri(uri, next_loader, &next_uri); diff --git a/include/asm.h b/include/asm.h index 03b06557..f5118b23 100644 --- a/include/asm.h +++ b/include/asm.h @@ -40,11 +40,11 @@ static inline void wait_for_debug(void) { uint64_t a, b; int x; - extern void msleep(unsigned long msecs); + extern void usleep(unsigned long usecs); a = read_counter(); for (x = 0; x < 1000; x++) { - msleep(1000); + usleep(1000); b = read_counter(); if (a != b) break; diff --git a/include/compiler.h b/include/compiler.h index b0d595f3..8e8a658d 100644 --- a/include/compiler.h +++ b/include/compiler.h @@ -198,5 +198,55 @@ #error shim has no cache_invalidate() implementation for this compiler #endif /* __GNUC__ */ +#if defined(__GNUC__) && defined(__GNUC_MINOR__) +#define GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +#else +#define GNUC_PREREQ(maj, min) 0 +#endif + +#if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__) +#define CLANG_PREREQ(maj, min) \ + ((__clang_major__ > (maj)) || \ + (__clang_major__ == (maj) && __clang_minor__ >= (min))) +#else +#define CLANG_PREREQ(maj, min) 0 +#endif + +#if GNUC_PREREQ(5, 1) || CLANG_PREREQ(3, 8) +#define checked_add(addend0, addend1, sum) \ + __builtin_add_overflow(addend0, addend1, sum) +#define checked_sub(minuend, subtrahend, difference) \ + __builtin_sub_overflow(minuend, subtrahend, difference) +#define checked_mul(factor0, factor1, product) \ + __builtin_mul_overflow(factor0, factor1, product) +#else +#define checked_add(a0, a1, s) \ + ({ \ + (*s) = ((a0) + (a1)); \ + 0; \ + }) +#define checked_sub(s0, s1, d) \ + ({ \ + (*d) = ((s0) - (s1)); \ + 0; \ + }) +#define checked_mul(f0, f1, p) \ + ({ \ + (*p) = ((f0) * (f1)); \ + 0; \ + }) +#endif + +#define checked_div(dividend, divisor, quotient) \ + ({ \ + bool _ret = True; \ + if ((divisor) != 0) { \ + _ret = False; \ + (quotient) = (dividend) / (divisor); \ + } \ + _ret; \ + }) + #endif /* !COMPILER_H_ */ // vim:fenc=utf-8:tw=75:et diff --git a/include/console.h b/include/console.h index 0c4a5137..7ac4e113 100644 --- a/include/console.h +++ b/include/console.h @@ -106,8 +106,8 @@ extern UINT32 verbose; dprint_(L"%a:%d:%a() " fmt, __FILE__, __LINE__ - 1, __func__, \ ##__VA_ARGS__) #else -#define dprint_(...) -#define dprint(fmt, ...) +#define dprint_(...) ({ ; }) +#define dprint(fmt, ...) ({ ; }) #endif extern EFI_STATUS EFIAPI vdprint_(const CHAR16 *fmt, const char *file, int line, @@ -122,7 +122,9 @@ extern EFI_STATUS EFIAPI vdprint_(const CHAR16 *fmt, const char *file, int line, extern EFI_STATUS print_crypto_errors(EFI_STATUS rc, char *file, const char *func, int line); #define crypterr(rc) print_crypto_errors((rc), __FILE__, __func__, __LINE__) -extern VOID msleep(unsigned long msecs); +#ifndef SHIM_UNIT_TEST +extern VOID usleep(unsigned long usecs); +#endif /* This is used in various things to determine if we should print to the * console */ diff --git a/include/fanalyzer.mk b/include/fanalyzer.mk index e0bf4d75..a0679e3e 100644 --- a/include/fanalyzer.mk +++ b/include/fanalyzer.mk @@ -21,6 +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 : all fanalyzer-no-openssl : | fanalyzer-test diff --git a/include/fuzz.mk b/include/fuzz.mk new file mode 100644 index 00000000..f35df415 --- /dev/null +++ b/include/fuzz.mk @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +# fuzz.mk - makefile to fuzz local test programs +# + +.SUFFIXES: + +include Make.defaults + +CC = clang +VALGRIND ?= +DEBUG_PRINTS ?= 0 +OPTIMIZATIONS ?= -Og -ggdb +FUZZ_ARGS ?= +CFLAGS = $(OPTIMIZATIONS) -std=gnu11 \ + -isystem $(TOPDIR)/include/system \ + $(EFI_INCLUDES) \ + -Iinclude -iquote . \ + -isystem /usr/include \ + -isystem $(shell $(CC) $(ARCH_CFLAGS) -print-file-name=include) \ + $(ARCH_CFLAGS) \ + -fsanitize=fuzzer,address \ + -fshort-wchar \ + -fno-builtin \ + -rdynamic \ + -fno-inline \ + -fno-eliminate-unused-debug-types \ + -fno-eliminate-unused-debug-symbols \ + -gpubnames \ + -grecord-gcc-switches \ + $(if $(findstring clang,$(CC)),-Wno-unknown-warning-option) \ + $(DEFAULT_WARNFLAGS) \ + -Wsign-compare \ + -Wno-deprecated-declarations \ + $(if $(findstring gcc,$(CC)),-Wno-unused-but-set-variable) \ + -Wno-unused-but-set-variable \ + -Wno-unused-variable \ + -Wno-pointer-sign \ + $(DEFAULT_WERRFLAGS) \ + -Werror=nonnull \ + $(shell $(CC) -Werror=nonnull-compare -E -x c /dev/null >/dev/null 2>&1 && echo -Werror=nonnull-compare) \ + $(ARCH_DEFINES) \ + -DEFI_FUNCTION_WRAPPER \ + -DGNU_EFI_USE_MS_ABI -DPAGE_SIZE=4096 \ + -DSHIM_UNIT_TEST \ + -DSHIM_ENABLE_LIBFUZZER \ + "-DDEFAULT_DEBUG_PRINT_STATE=$(DEBUG_PRINTS)" + +# On some systems (e.g. Arch Linux), limits.h is in the "include-fixed" instead +# of the "include" directory +CFLAGS += -isystem $(shell $(CC) $(ARCH_CFLAGS) -print-file-name=include-fixed) + +# And on Debian also check the multi-arch include path +CFLAGS += -isystem /usr/include/$(shell $(CC) $(ARCH_CFLAGS) -print-multiarch) + +libefi-test.a : + $(MAKE) -C gnu-efi \ + COMPILER="$(COMPILER)" \ + CC="$(CC)" \ + ARCH=$(ARCH_GNUEFI) \ + TOPDIR=$(TOPDIR)/gnu-efi \ + -f $(TOPDIR)/gnu-efi/Makefile \ + clean lib + mv gnu-efi/$(ARCH)/lib/libefi.a $@ + $(MAKE) -C gnu-efi \ + COMPILER="$(COMPILER)" \ + ARCH=$(ARCH_GNUEFI) \ + TOPDIR=$(TOPDIR)/gnu-efi \ + -f $(TOPDIR)/gnu-efi/Makefile \ + clean + +fuzz-sbat_FILES = csv.c lib/variables.c lib/guid.c sbat_var.S mock-variables.c +fuzz-sbat :: CFLAGS+=-DHAVE_GET_VARIABLE -DHAVE_GET_VARIABLE_ATTR -DHAVE_SHIM_LOCK_GUID + +fuzzers := $(patsubst %.c,%,$(wildcard fuzz-*.c)) + +$(fuzzers) :: fuzz-% : | libefi-test.a + +$(fuzzers) :: fuzz-% : test.c fuzz-%.c $(fuzz-%_FILES) + $(CC) $(CFLAGS) -o $@ $(sort $^ $(wildcard $*.c) $(fuzz-$*_FILES)) libefi-test.a -lefivar + $(VALGRIND) ./$@ -max_len=4096 -jobs=24 $(FUZZ_ARGS) + +fuzz : $(fuzzers) + $(MAKE) -f include/fuzz.mk fuzz-clean + +fuzz-clean : + @rm -vf random.bin libefi-test.a + @rm -vf vgcore.* fuzz*.log + +clean : fuzz-clean + +all : fuzz-clean fuzz + +.PHONY: $(fuzzers) all fuzz clean +.SECONDARY: random.bin + +# vim:ft=make diff --git a/include/guid.h b/include/guid.h index dad63f0f..898c4fad 100644 --- a/include/guid.h +++ b/include/guid.h @@ -37,5 +37,6 @@ extern EFI_GUID SECURITY2_PROTOCOL_GUID; extern EFI_GUID EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID; extern EFI_GUID SHIM_LOCK_GUID; extern EFI_GUID MOK_VARIABLE_STORE; +extern EFI_GUID SECUREBOOT_EFI_NAMESPACE_GUID; #endif /* SHIM_GUID_H */ diff --git a/include/httpboot.h b/include/httpboot.h index ea9c57fe..119c546d 100644 --- a/include/httpboot.h +++ b/include/httpboot.h @@ -12,6 +12,6 @@ extern BOOLEAN find_httpboot(EFI_HANDLE device); extern EFI_STATUS httpboot_fetch_buffer(EFI_HANDLE image, VOID **buffer, - UINT64 *buf_size); + UINT64 *buf_size, CHAR8 *name); #endif /* SHIM_HTTPBOOT_H */ diff --git a/include/netboot.h b/include/netboot.h index 98b174a3..a7bf6cd8 100644 --- a/include/netboot.h +++ b/include/netboot.h @@ -5,7 +5,7 @@ extern BOOLEAN findNetboot(EFI_HANDLE image_handle); -extern EFI_STATUS parseNetbootinfo(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); diff --git a/include/pe.h b/include/pe.h index ccc8798b..9ea9eb44 100644 --- a/include/pe.h +++ b/include/pe.h @@ -22,6 +22,20 @@ EFI_STATUS verify_sbat_section(char *SBATBase, size_t SBATSize); EFI_STATUS +get_section_vma (UINTN section_num, + char *buffer, size_t bufsz UNUSED, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp); + +EFI_STATUS +get_section_vma_by_name (char *name, size_t namesz, + char *buffer, size_t bufsz, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp); + +EFI_STATUS handle_image (void *data, unsigned int datasize, EFI_LOADED_IMAGE *li, EFI_IMAGE_ENTRY_POINT *entry_point, diff --git a/include/peimage.h b/include/peimage.h index e97b29c4..6eef1051 100644 --- a/include/peimage.h +++ b/include/peimage.h @@ -29,6 +29,9 @@ #define ALIGN_VALUE(Value, Alignment) ((Value) + (((Alignment) - (Value)) & ((Alignment) - 1)))
#define ALIGN_POINTER(Pointer, Alignment) ((VOID *) (ALIGN_VALUE ((UINTN)(Pointer), (Alignment))))
+// Check if `val` is evenly aligned to the page size.
+#define IS_PAGE_ALIGNED(val) (!((val) & EFI_PAGE_MASK))
+
//
// PE32+ Subsystem type for EFI images
//
diff --git a/include/sbat.h b/include/sbat.h index c94c4fba..bb523e7e 100644 --- a/include/sbat.h +++ b/include/sbat.h @@ -30,10 +30,15 @@ #define SBAT_POLICY L"SbatPolicy" #define SBAT_POLICY8 "SbatPolicy" +#define SSP_POLICY L"SSPPolicy" +#define SSP_POLICY8 "SSPPolicy" -#define SBAT_POLICY_LATEST 1 -#define SBAT_POLICY_PREVIOUS 2 -#define SBAT_POLICY_RESET 3 +#define POLICY_LATEST 1 +#define POLICY_AUTOMATIC 2 +#define POLICY_RESET 3 +#define POLICY_NOTREAD 255 + +#define REVOCATIONFILE L"revocations.efi" extern UINTN _sbat, _esbat; @@ -50,9 +55,10 @@ extern list_t sbat_var; #define SBAT_VAR_COLUMNS ((sizeof (struct sbat_var_entry) - sizeof(list_t)) / sizeof(CHAR8 *)) #define SBAT_VAR_REQUIRED_COLUMNS (SBAT_VAR_COLUMNS - 1) -EFI_STATUS parse_sbat_var(list_t *entries); +EFI_STATUS parse_sbat_var(list_t *entries, char *sbat_var_candidate); void cleanup_sbat_var(list_t *entries); -EFI_STATUS set_sbat_uefi_variable(void); +EFI_STATUS set_sbat_uefi_variable_internal(void); +EFI_STATUS set_sbat_uefi_variable(char *, char *); bool preserve_sbat_uefi_variable(UINT8 *sbat, UINTN sbatsize, UINT32 attributes, char *sbar_var); diff --git a/include/sbat_var_defs.h b/include/sbat_var_defs.h index 6b01573e..f8cba029 100644 --- a/include/sbat_var_defs.h +++ b/include/sbat_var_defs.h @@ -3,6 +3,9 @@ #ifndef SBAT_VAR_DEFS_H_ #define SBAT_VAR_DEFS_H_ +#define QUOTEVAL(s) QUOTE(s) +#define QUOTE(s) #s + /* * This is the entry for the sbat data format */ @@ -13,11 +16,9 @@ SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_ORIGINAL_DATE "\n" #if defined(ENABLE_SHIM_DEVEL) -#define SBAT_VAR_PREVIOUS_DATE "2022020101" -#define SBAT_VAR_PREVIOUS_REVOCATIONS "component,2\n" -#define SBAT_VAR_PREVIOUS \ - SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_PREVIOUS_DATE "\n" \ - SBAT_VAR_PREVIOUS_REVOCATIONS +#define SBAT_VAR_AUTOMATIC_DATE "2021030218" +#define SBAT_VAR_AUTOMATIC \ + SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_AUTOMATIC_DATE "\n" #define SBAT_VAR_LATEST_DATE "2022050100" #define SBAT_VAR_LATEST_REVOCATIONS "component,2\nothercomponent,2\n" @@ -25,21 +26,42 @@ SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_LATEST_DATE "\n" \ SBAT_VAR_LATEST_REVOCATIONS #else /* !ENABLE_SHIM_DEVEL */ + /* - * As of 2022-11-16, most folks (including Ubuntu, SUSE, openSUSE) don't have - * a "shim,2" yet, so adding that here would end up unbootable. + * Some distros may want to apply revocations from 2022052400 + * or 2022111500 automatically. They can be selected by setting + * SBAT_AUTOMATIC_DATE=<datestamp> 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. */ -#define SBAT_VAR_PREVIOUS_DATE "2022052400" -#define SBAT_VAR_PREVIOUS_REVOCATIONS "grub,2\n" -#define SBAT_VAR_PREVIOUS \ - SBAT_VAR_SIG SBAT_VAR_VERSION SBAT_VAR_PREVIOUS_DATE "\n" \ - SBAT_VAR_PREVIOUS_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 -#define SBAT_VAR_LATEST_DATE "2022111500" -#define SBAT_VAR_LATEST_REVOCATIONS "shim,2\ngrub,3\n" +/* + * 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/scan-build.mk b/include/scan-build.mk index 3ed7660e..170ba836 100644 --- a/include/scan-build.mk +++ b/include/scan-build.mk @@ -22,6 +22,7 @@ scan-build-unchecked-openssl : Cryptlib/OpenSSL/libopenssl.a scan-build-all : CCACHE_DISABLE=1 scan-build-all : COMPILER=clang +scan-build-all : IGNORE_COMPILER_ERRORS=" || :" scan-build-all : | scan-test scan-build-all : +scan-build -o scan-results make $(MAKEARGS) $(DASHJ) CCACHE_DISABLE=1 all diff --git a/include/ssp.h b/include/ssp.h new file mode 100644 index 00000000..f25590c6 --- /dev/null +++ b/include/ssp.h @@ -0,0 +1,14 @@ +#ifndef SSP_H_ +#define SSP_H_ + +#define SSPVER_VAR_NAME L"SkuSiPolicyVersion" +#define SSPSIG_VAR_NAME L"SkuSiPolicyUpdateSigners" +#define SSP_VAR_ATTRS UEFI_VAR_NV_BS + +#define SSPVER_SIZE 8 +#define SSPSIG_SIZE 131 + +EFI_STATUS set_ssp_uefi_variable_internal(void); +EFI_STATUS set_ssp_uefi_variable(uint8_t*, uint8_t*, uint8_t*, uint8_t*); + +#endif /* !SSP_H_ */ diff --git a/include/ssp_var_defs.h b/include/ssp_var_defs.h new file mode 100644 index 00000000..4bfad878 --- /dev/null +++ b/include/ssp_var_defs.h @@ -0,0 +1,19 @@ +/* + * variable definitions to enable bootmgr self revocation + */ +#ifndef SSP_VAR_DEFS_H_ +#define SSP_VAR_DEFS_H_ + +uint8_t SkuSiPolicyVersion[] = { 0x2,0x0,0x0,0x0,0x0,0x0,0x2,0x0 }; +uint8_t SkuSiPolicyUpdateSigners[] = { +0x01,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00, +0x0b,0x00,0x00,0x00,0xd0,0x91,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00, +0x00,0x00,0x00,0x00,0x54,0xa6,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00, +0x00,0x00,0x00,0x00,0x5c,0xa6,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00, +0x00,0x00,0x00,0x00,0x64,0xa6,0x78,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,0x00,0x00,0x0a,0x2b,0x06,0x01,0x04,0x01,0x82,0x37,0x0a,0x03,0x06,0x00, +0x00,0x00,0x00 }; + +#endif /* !SSP_VAR_DEFS_H_ */ diff --git a/include/test.mk b/include/test.mk index c0e24095..e6d46594 100644 --- a/include/test.mk +++ b/include/test.mk @@ -92,9 +92,12 @@ test-mock-variables: CFLAGS+=-DHAVE_SHIM_LOCK_GUID test-mok-mirror_FILES = mok.c globals.c tpm.c lib/guid.c lib/variables.c mock-variables.c test-mok-mirror: CFLAGS+=-DHAVE_START_IMAGE -DHAVE_SHIM_LOCK_GUID -test-sbat_FILES = csv.c lib/variables.c lib/guid.c sbat_var.S +test-sbat_FILES = csv.c lib/variables.c lib/guid.c sbat_var.S mock-variables.c test-sbat :: CFLAGS+=-DHAVE_GET_VARIABLE -DHAVE_GET_VARIABLE_ATTR -DHAVE_SHIM_LOCK_GUID +test-pe-relocate_FILES = globals.c +test-pe-relocate :: CFLAGS+=-DHAVE_SHIM_LOCK_GUID + test-str_FILES = lib/string.c tests := $(patsubst %.c,%,$(wildcard test-*.c)) diff --git a/lib/console.c b/lib/console.c index 6bbb8a7c..a751f79d 100644 --- a/lib/console.c +++ b/lib/console.c @@ -743,11 +743,13 @@ setup_verbosity(VOID) setup_console(-1); } +#ifndef SHIM_UNIT_TEST VOID -msleep(unsigned long msecs) +usleep(unsigned long usecs) { - BS->Stall(msecs); + BS->Stall(usecs); } +#endif /* This is used in various things to determine if we should print to the * console */ @@ -36,3 +36,4 @@ EFI_GUID SECURITY2_PROTOCOL_GUID = { 0x94ab2f58, 0x1438, 0x4ef1, {0x91, 0x52, 0x 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 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} }; @@ -54,7 +54,7 @@ gcm_gmult_4bit(u64 Xi[2], u128 Htable[16]) } void -msleep(int n) +usleep(int n) { __coverity_sleep__(); } @@ -291,7 +291,7 @@ mirror_one_esl(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, &var, &varsz); if (EFI_ERROR(efi_status) || !var || !varsz) { LogError(L"Couldn't allocate %lu bytes for mok variable \"%s\": %r\n", - varsz, var, efi_status); + varsz, name, efi_status); return efi_status; } @@ -302,7 +302,7 @@ mirror_one_esl(CHAR16 *name, EFI_GUID *guid, UINT32 attrs, FreePool(var); if (EFI_ERROR(efi_status)) { LogError(L"Couldn't create mok variable \"%s\": %r\n", - varsz, var, efi_status); + name, efi_status); return efi_status; } @@ -423,12 +423,20 @@ mirror_mok_db(CHAR16 *name, CHAR8 *name8, EFI_GUID *guid, UINT32 attrs, } /* The name counts towards the size of the variable */ - max_var_sz -= (StrLen(namen) + 1) * 2; + SIZE_T namen_sz = (StrLen(namen) + 1) * 2; + if (max_var_sz > namen_sz) + max_var_sz -= namen_sz; + else + max_var_sz = 0; dprint(L"max_var_sz - name: %lx\n", max_var_sz); SIZE_T howmany; - howmany = MIN((max_var_sz - sizeof(*esl)) / esl->SignatureSize, - (esl_end_pos - pos) / esl->SignatureSize); + if (max_var_sz > sizeof(*esl)) + howmany = MIN((max_var_sz - sizeof(*esl)) / esl->SignatureSize, + (esl_end_pos - pos) / esl->SignatureSize); + else + howmany = 0; + if (howmany == 0) { /* No signatures from this ESL can be mirrored in to a * single variable, so skip it. @@ -160,25 +160,30 @@ static CHAR8 *str2ip6(CHAR8 *str) return (CHAR8 *)ip; } -static BOOLEAN extract_tftp_info(CHAR8 *url) +static BOOLEAN extract_tftp_info(CHAR8 *url, CHAR8 *name) { CHAR8 *start, *end; CHAR8 ip6str[40]; CHAR8 ip6inv[16]; - CHAR8 template[sizeof DEFAULT_LOADER_CHAR]; + int template_len = 0; + CHAR8 *template; - translate_slashes(template, DEFAULT_LOADER_CHAR); + while (name[template_len++] != '\0'); + template = (CHAR8 *)AllocatePool((template_len + 1) * sizeof (CHAR8)); + translate_slashes(template, name); // to check against str2ip6() errors memset(ip6inv, 0, sizeof(ip6inv)); if (strncmp((const char *)url, (const char *)"tftp://", 7)) { console_print(L"URLS MUST START WITH tftp://\n"); + FreePool(template); return FALSE; } start = url + 7; if (*start != '[') { console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n"); + FreePool(template); return FALSE; } @@ -188,22 +193,28 @@ static BOOLEAN extract_tftp_info(CHAR8 *url) end++; if (end - start >= (int)sizeof(ip6str)) { console_print(L"TFTP URL includes malformed IPv6 address\n"); + FreePool(template); return FALSE; } } if (*end == '\0') { console_print(L"TFTP SERVER MUST BE ENCLOSED IN [..]\n"); + FreePool(template); return FALSE; } memset(ip6str, 0, sizeof(ip6str)); memcpy(ip6str, start, end - start); end++; memcpy(&tftp_addr.v6, str2ip6(ip6str), 16); - if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0) + if (memcmp(&tftp_addr.v6, ip6inv, sizeof(ip6inv)) == 0) { + FreePool(template); return FALSE; + } full_path = AllocateZeroPool(strlen(end)+strlen(template)+1); - if (!full_path) + if (!full_path) { + FreePool(template); return FALSE; + } memcpy(full_path, end, strlen(end)); end = (CHAR8 *)strrchr((char *)full_path, '/'); if (!end) @@ -211,10 +222,11 @@ static BOOLEAN extract_tftp_info(CHAR8 *url) memcpy(end, template, strlen(template)); end[strlen(template)] = '\0'; + FreePool(template); return TRUE; } -static EFI_STATUS parseDhcp6() +static EFI_STATUS parseDhcp6(CHAR8 *name) { EFI_PXE_BASE_CODE_DHCPV6_PACKET *packet = (EFI_PXE_BASE_CODE_DHCPV6_PACKET *)&pxe->Mode->DhcpAck.Raw; CHAR8 *bootfile_url; @@ -222,7 +234,7 @@ static EFI_STATUS parseDhcp6() bootfile_url = get_v6_bootfile_url(packet); if (!bootfile_url) return EFI_NOT_FOUND; - if (extract_tftp_info(bootfile_url) == FALSE) { + if (extract_tftp_info(bootfile_url, name) == FALSE) { FreePool(bootfile_url); return EFI_NOT_FOUND; } @@ -230,14 +242,16 @@ static EFI_STATUS parseDhcp6() return EFI_SUCCESS; } -static EFI_STATUS parseDhcp4() +static EFI_STATUS parseDhcp4(CHAR8 *name) { - CHAR8 template[sizeof DEFAULT_LOADER_CHAR]; - INTN template_len; + CHAR8 *template; + INTN template_len = 0; UINTN template_ofs = 0; EFI_PXE_BASE_CODE_DHCPV4_PACKET* pkt_v4 = (EFI_PXE_BASE_CODE_DHCPV4_PACKET *)&pxe->Mode->DhcpAck.Dhcpv4; - translate_slashes(template, DEFAULT_LOADER_CHAR); + while (name[template_len++] != '\0'); + template = (CHAR8 *)AllocatePool((template_len + 1) * sizeof (CHAR8)); + translate_slashes(template, name); template_len = strlen(template) + 1; if(pxe->Mode->ProxyOfferReceived) { @@ -263,30 +277,42 @@ static EFI_STATUS parseDhcp4() UINT8 *dir = pkt_v4->BootpBootFile; for (i = dir_len; i >= 0; i--) { - if (dir[i] == '/') + if ((dir[i] == '/') || (dir[i] == '\\')) break; } dir_len = (i >= 0) ? i + 1 : 0; full_path = AllocateZeroPool(dir_len + template_len); - if (!full_path) + if (!full_path) { + FreePool(template); return EFI_OUT_OF_RESOURCES; + } if (dir_len > 0) { strncpy(full_path, (CHAR8 *)dir, dir_len); if (full_path[dir_len-1] == '/' && template[0] == '/') full_path[dir_len-1] = '\0'; + /* + * If the path from DHCP is using backslash instead of slash, + * accept that and use it in the template in the same position + * as well. + */ + if (full_path[dir_len-1] == '\\' && template[0] == '/') { + full_path[dir_len-1] = '\0'; + template[0] = '\\'; + } } if (dir_len == 0 && dir[0] != '/' && template[0] == '/') template_ofs++; strcat(full_path, template + template_ofs); memcpy(&tftp_addr.v4, pkt_v4->BootpSiAddr, 4); + FreePool(template); return EFI_SUCCESS; } -EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED) +EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED, CHAR8 *netbootname) { EFI_STATUS efi_status; @@ -301,9 +327,9 @@ EFI_STATUS parseNetbootinfo(EFI_HANDLE image_handle UNUSED) * if its ipv4 or ipv6 */ if (pxe->Mode->UsingIpv6){ - efi_status = parseDhcp6(); + efi_status = parseDhcp6(netbootname); } else - efi_status = parseDhcp4(); + efi_status = parseDhcp4(netbootname); return efi_status; } @@ -315,7 +341,7 @@ EFI_STATUS FetchNetbootimage(EFI_HANDLE image_handle UNUSED, VOID **buffer, UINT BOOLEAN nobuffer = FALSE; UINTN blksz = 512; - console_print(L"Fetching Netboot Image\n"); + console_print(L"Fetching Netboot Image %a\n", full_path); if (*buffer == NULL) { *buffer = AllocatePool(4096 * 1024); if (!*buffer) diff --git a/pe-relocate.c b/pe-relocate.c new file mode 100644 index 00000000..bde71729 --- /dev/null +++ b/pe-relocate.c @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * pe-relocate.c - our PE relocation/loading (but not verification) code + * Copyright Peter Jones <pjones@redhat.com> + */ + +#include "shim.h" + +/* + * Perform basic bounds checking of the intra-image pointers + */ +void * +ImageAddress (void *image, uint64_t size, uint64_t address) +{ + uintptr_t img_addr; + + /* ensure our local pointer isn't bigger than our size */ + if (address >= size) + return NULL; + + /* Insure our math won't overflow */ + img_addr = (uintptr_t)image; + if (checked_add(img_addr, address, &img_addr)) + return NULL; + + /* return the absolute pointer */ + return (void *)img_addr; +} + +/* + * Perform the actual relocation + */ +EFI_STATUS +relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, + EFI_IMAGE_SECTION_HEADER *Section, + void *orig, void *data) +{ + EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd; + UINT64 Adjust; + UINT16 *Reloc, *RelocEnd; + char *Fixup, *FixupBase; + UINT16 *Fixup16; + UINT32 *Fixup32; + UINT64 *Fixup64; + int size = context->ImageSize; + void *ImageEnd = (char *)orig + size; + int n = 0; + + /* Alright, so here's how this works: + * + * context->RelocDir gives us two things: + * - the VA the table of base relocation blocks are (maybe) to be + * mapped at (RelocDir->VirtualAddress) + * - the virtual size (RelocDir->Size) + * + * The .reloc section (Section here) gives us some other things: + * - the name! kind of. (Section->Name) + * - the virtual size (Section->VirtualSize), which should be the same + * as RelocDir->Size + * - the virtual address (Section->VirtualAddress) + * - the file section size (Section->SizeOfRawData), which is + * a multiple of OptHdr->FileAlignment. Only useful for image + * validation, not really useful for iteration bounds. + * - the file address (Section->PointerToRawData) + * - a bunch of stuff we don't use that's 0 in our binaries usually + * - Flags (Section->Characteristics) + * + * and then the thing that's actually at the file address is an array + * of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind + * them. The SizeOfBlock field of this structure includes the + * structure itself, and adding it to that structure's address will + * yield the next entry in the array. + */ + RelocBase = ImageAddress(orig, size, Section->PointerToRawData); + /* RelocBaseEnd here is the address of the first entry /past/ the + * table. */ + RelocBaseEnd = ImageAddress(orig, size, Section->PointerToRawData + + context->RelocDir->Size - 1); + + if (!RelocBase && !RelocBaseEnd) + return EFI_SUCCESS; + + if (!RelocBase || !RelocBaseEnd) { + perror(L"Reloc table overflows binary\n"); + return EFI_UNSUPPORTED; + } + + Adjust = (UINTN)data - context->ImageAddress; + + if (Adjust == 0) + return EFI_SUCCESS; + + while (RelocBase < RelocBaseEnd) { + Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION)); + + if (RelocBase->SizeOfBlock == 0) { + perror(L"Reloc %d block size 0 is invalid\n", n); + return EFI_UNSUPPORTED; + } else if (RelocBase->SizeOfBlock > context->RelocDir->Size) { + perror(L"Reloc %d block size %d greater than reloc dir" + "size %d, which is invalid\n", n, + RelocBase->SizeOfBlock, + context->RelocDir->Size); + return EFI_UNSUPPORTED; + } + + RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock); + if ((void *)RelocEnd < orig || (void *)RelocEnd > ImageEnd) { + perror(L"Reloc %d entry overflows binary\n", n); + return EFI_UNSUPPORTED; + } + + FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress); + if (!FixupBase) { + perror(L"Reloc %d Invalid fixupbase\n", n); + return EFI_UNSUPPORTED; + } + + while (Reloc < RelocEnd) { + Fixup = FixupBase + (*Reloc & 0xFFF); + switch ((*Reloc) >> 12) { + case EFI_IMAGE_REL_BASED_ABSOLUTE: + break; + + case EFI_IMAGE_REL_BASED_HIGH: + Fixup16 = (UINT16 *) Fixup; + *Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16))); + break; + + case EFI_IMAGE_REL_BASED_LOW: + Fixup16 = (UINT16 *) Fixup; + *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust); + break; + + case EFI_IMAGE_REL_BASED_HIGHLOW: + Fixup32 = (UINT32 *) Fixup; + *Fixup32 = *Fixup32 + (UINT32) Adjust; + break; + + case EFI_IMAGE_REL_BASED_DIR64: + Fixup64 = (UINT64 *) Fixup; + *Fixup64 = *Fixup64 + (UINT64) Adjust; + break; + + default: + perror(L"Reloc %d Unknown relocation\n", n); + return EFI_UNSUPPORTED; + } + Reloc += 1; + } + RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd; + n++; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +get_section_vma (UINTN section_num, + char *buffer, size_t bufsz UNUSED, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp) +{ + EFI_IMAGE_SECTION_HEADER *sections = context->FirstSection; + EFI_IMAGE_SECTION_HEADER *section; + char *base = NULL, *end = NULL; + + if (section_num >= context->NumberOfSections) + return EFI_NOT_FOUND; + + if (context->FirstSection == NULL) { + perror(L"Invalid section %d requested\n", section_num); + return EFI_UNSUPPORTED; + } + + section = §ions[section_num]; + + base = ImageAddress (buffer, context->ImageSize, section->VirtualAddress); + end = ImageAddress (buffer, context->ImageSize, + section->VirtualAddress + section->Misc.VirtualSize - 1); + + if (!(section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE)) { + if (!base) { + perror(L"Section %d has invalid base address\n", section_num); + return EFI_UNSUPPORTED; + } + if (!end) { + perror(L"Section %d has zero size\n", section_num); + return EFI_UNSUPPORTED; + } + } + + if (!(section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) && + (section->VirtualAddress < context->SizeOfHeaders || + section->PointerToRawData < context->SizeOfHeaders)) { + perror(L"Section %d is inside image headers\n", section_num); + return EFI_UNSUPPORTED; + } + + if (end < base) { + perror(L"Section %d has negative size\n", section_num); + return EFI_UNSUPPORTED; + } + + *basep = base; + *sizep = end - base; + *sectionp = section; + return EFI_SUCCESS; +} + +EFI_STATUS +get_section_vma_by_name (char *name, size_t namesz, + char *buffer, size_t bufsz, + PE_COFF_LOADER_IMAGE_CONTEXT *context, + char **basep, size_t *sizep, + EFI_IMAGE_SECTION_HEADER **sectionp) +{ + UINTN i; + char namebuf[9]; + + if (!name || namesz == 0 || !buffer || bufsz < namesz || !context + || !basep || !sizep || !sectionp) + return EFI_INVALID_PARAMETER; + + /* + * This code currently is only used for ".reloc\0\0" and + * ".sbat\0\0\0", and it doesn't know how to look up longer section + * names. + */ + if (namesz > 8) + return EFI_UNSUPPORTED; + + SetMem(namebuf, sizeof(namebuf), 0); + CopyMem(namebuf, name, MIN(namesz, 8)); + + /* + * Copy the executable's sections to their desired offsets + */ + for (i = 0; i < context->NumberOfSections; i++) { + EFI_STATUS status; + EFI_IMAGE_SECTION_HEADER *section = NULL; + char *base = NULL; + size_t size = 0; + + status = get_section_vma(i, buffer, bufsz, context, &base, &size, §ion); + if (!EFI_ERROR(status)) { + if (CompareMem(section->Name, namebuf, 8) == 0) { + *basep = base; + *sizep = size; + *sectionp = section; + return EFI_SUCCESS; + } + continue; + } + + switch(status) { + case EFI_NOT_FOUND: + break; + } + } + + return EFI_NOT_FOUND; +} + +/* here's a chart: + * i686 x86_64 aarch64 + * 64-on-64: nyet yes yes + * 64-on-32: nyet yes nyet + * 32-on-32: yes yes no + */ +static int +allow_64_bit(void) +{ +#if defined(__x86_64__) || defined(__aarch64__) + return 1; +#elif defined(__i386__) || defined(__i686__) + /* Right now blindly assuming the kernel will correctly detect this + * and /halt the system/ if you're not really on a 64-bit cpu */ + if (in_protocol) + return 1; + return 0; +#else /* assuming everything else is 32-bit... */ + return 0; +#endif +} + +static int +allow_32_bit(void) +{ +#if defined(__x86_64__) +#if defined(ALLOW_32BIT_KERNEL_ON_X64) + if (in_protocol) + return 1; + return 0; +#else + return 0; +#endif +#elif defined(__i386__) || defined(__i686__) + return 1; +#elif defined(__aarch64__) + return 0; +#else /* assuming everything else is 32-bit... */ + return 1; +#endif +} + +static int +image_is_64_bit(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) +{ + /* .Magic is the same offset in all cases */ + if (PEHdr->Pe32.OptionalHeader.Magic + == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) + return 1; + return 0; +} + +static const UINT16 machine_type = +#if defined(__x86_64__) + IMAGE_FILE_MACHINE_X64; +#elif defined(__aarch64__) + IMAGE_FILE_MACHINE_ARM64; +#elif defined(__arm__) + IMAGE_FILE_MACHINE_ARMTHUMB_MIXED; +#elif defined(__i386__) || defined(__i486__) || defined(__i686__) + IMAGE_FILE_MACHINE_I386; +#elif defined(__ia64__) + IMAGE_FILE_MACHINE_IA64; +#else +#error this architecture is not supported by shim +#endif + +static int +image_is_loadable(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) +{ + /* If the machine type doesn't match the binary, bail, unless + * we're in an allowed 64-on-32 scenario */ + if (PEHdr->Pe32.FileHeader.Machine != machine_type) { + if (!(machine_type == IMAGE_FILE_MACHINE_I386 && + PEHdr->Pe32.FileHeader.Machine == IMAGE_FILE_MACHINE_X64 && + allow_64_bit())) { + return 0; + } + } + + /* If it's not a header type we recognize at all, bail */ + switch (PEHdr->Pe32Plus.OptionalHeader.Magic) { + case EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC: + case EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: + break; + default: + return 0; + } + + /* and now just check for general 64-vs-32 compatibility */ + if (image_is_64_bit(PEHdr)) { + if (allow_64_bit()) + return 1; + } else { + if (allow_32_bit()) + return 1; + } + return 0; +} + +/* + * Read the binary header and grab appropriate information from it + */ +EFI_STATUS +read_header(void *data, unsigned int datasize, + PE_COFF_LOADER_IMAGE_CONTEXT *context) +{ + EFI_IMAGE_DOS_HEADER *DosHdr = data; + EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; + unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize; + unsigned long FileAlignment = 0; + UINT16 DllFlags; + size_t dos_sz = 0; + size_t tmpsz0, tmpsz1; + + if (datasize < sizeof (*DosHdr)) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { + if (DosHdr->e_lfanew < sizeof (*DosHdr) || + DosHdr->e_lfanew > datasize - 4) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + dos_sz = DosHdr->e_lfanew; + PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); + } + + if (datasize - dos_sz < sizeof (PEHdr->Pe32)) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (image_is_64_bit(PEHdr) && + (datasize - dos_sz < sizeof (PEHdr->Pe32Plus))) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (!image_is_loadable(PEHdr)) { + perror(L"Platform does not support this image\n"); + return EFI_UNSUPPORTED; + } + + if (image_is_64_bit(PEHdr)) { + context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; + context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; + context->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage; + context->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment; + FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment; + OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64); + } else { + context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes; + context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders; + context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage; + context->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment; + FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment; + OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32); + } + + if (FileAlignment % 2 != 0) { + perror(L"File Alignment is invalid (%d)\n", FileAlignment); + return EFI_UNSUPPORTED; + } + if (FileAlignment == 0) + FileAlignment = 0x200; + if (context->SectionAlignment == 0) + context->SectionAlignment = PAGE_SIZE; + if (context->SectionAlignment < FileAlignment) + context->SectionAlignment = FileAlignment; + + context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections; + + if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) { + perror(L"Image header too large\n"); + return EFI_UNSUPPORTED; + } + + 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) || + checked_mul((size_t)context->NumberOfRvaAndSizes, sizeof (EFI_IMAGE_DATA_DIRECTORY), &tmpsz1) || + (tmpsz0 != tmpsz1)) { + perror(L"Image header overflows data directory\n"); + return EFI_UNSUPPORTED; + } + + 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)) { + perror(L"Image sections overflow image size\n"); + return EFI_UNSUPPORTED; + } + + 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; + } + + 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; + } + + if (checked_mul((size_t)context->NumberOfSections, sizeof(EFI_IMAGE_SECTION_HEADER), &tmpsz0) || + checked_add(tmpsz0, SectionHeaderOffset, &tmpsz0) || + (tmpsz0 > datasize)) { + perror(L"Image sections overflow section headers\n"); + return EFI_UNSUPPORTED; + } + + 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)) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { + perror(L"Unsupported image type\n"); + return EFI_UNSUPPORTED; + } + + if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { + perror(L"Unsupported image - Relocations have been stripped\n"); + return EFI_UNSUPPORTED; + } + + context->PEHdr = PEHdr; + + if (image_is_64_bit(PEHdr)) { + context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase; + context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint; + context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; + context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + DllFlags = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; + } else { + context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase; + context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint; + context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; + context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + DllFlags = PEHdr->Pe32.OptionalHeader.DllCharacteristics; + } + + if ((mok_policy & MOK_POLICY_REQUIRE_NX) && + !(DllFlags & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) { + perror(L"Policy requires NX, but image does not support NX\n"); + return EFI_UNSUPPORTED; + } + + 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; + } + context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)(uintptr_t)tmpsz0; + if ((uint64_t)(uintptr_t)(context->FirstSection) + > (uint64_t)(uintptr_t)data + datasize) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + if (context->ImageSize < context->SizeOfHeaders) { + perror(L"Invalid image\n"); + return EFI_UNSUPPORTED; + } + + 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)) { + perror(L"Malformed security header\n"); + return EFI_INVALID_PARAMETER; + } + return EFI_SUCCESS; +} + +// vim:fenc=utf-8:tw=75:noet @@ -21,152 +21,6 @@ #include <Library/BaseCryptLib.h> -/* - * Perform basic bounds checking of the intra-image pointers - */ -void * -ImageAddress (void *image, uint64_t size, uint64_t address) -{ - /* ensure our local pointer isn't bigger than our size */ - if (address > size) - return NULL; - - /* Insure our math won't overflow */ - if (UINT64_MAX - address < (uint64_t)(intptr_t)image) - return NULL; - - /* return the absolute pointer */ - return image + address; -} - -/* - * Perform the actual relocation - */ -EFI_STATUS -relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, - EFI_IMAGE_SECTION_HEADER *Section, - void *orig, void *data) -{ - EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd; - UINT64 Adjust; - UINT16 *Reloc, *RelocEnd; - char *Fixup, *FixupBase; - UINT16 *Fixup16; - UINT32 *Fixup32; - UINT64 *Fixup64; - int size = context->ImageSize; - void *ImageEnd = (char *)orig + size; - int n = 0; - - /* Alright, so here's how this works: - * - * context->RelocDir gives us two things: - * - the VA the table of base relocation blocks are (maybe) to be - * mapped at (RelocDir->VirtualAddress) - * - the virtual size (RelocDir->Size) - * - * The .reloc section (Section here) gives us some other things: - * - the name! kind of. (Section->Name) - * - the virtual size (Section->VirtualSize), which should be the same - * as RelocDir->Size - * - the virtual address (Section->VirtualAddress) - * - the file section size (Section->SizeOfRawData), which is - * a multiple of OptHdr->FileAlignment. Only useful for image - * validation, not really useful for iteration bounds. - * - the file address (Section->PointerToRawData) - * - a bunch of stuff we don't use that's 0 in our binaries usually - * - Flags (Section->Characteristics) - * - * and then the thing that's actually at the file address is an array - * of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind - * them. The SizeOfBlock field of this structure includes the - * structure itself, and adding it to that structure's address will - * yield the next entry in the array. - */ - RelocBase = ImageAddress(orig, size, Section->PointerToRawData); - /* RelocBaseEnd here is the address of the first entry /past/ the - * table. */ - RelocBaseEnd = ImageAddress(orig, size, Section->PointerToRawData + - Section->Misc.VirtualSize); - - if (!RelocBase && !RelocBaseEnd) - return EFI_SUCCESS; - - if (!RelocBase || !RelocBaseEnd) { - perror(L"Reloc table overflows binary\n"); - return EFI_UNSUPPORTED; - } - - Adjust = (UINTN)data - context->ImageAddress; - - if (Adjust == 0) - return EFI_SUCCESS; - - while (RelocBase < RelocBaseEnd) { - Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION)); - - if (RelocBase->SizeOfBlock == 0) { - perror(L"Reloc %d block size 0 is invalid\n", n); - return EFI_UNSUPPORTED; - } else if (RelocBase->SizeOfBlock > context->RelocDir->Size) { - perror(L"Reloc %d block size %d greater than reloc dir" - "size %d, which is invalid\n", n, - RelocBase->SizeOfBlock, - context->RelocDir->Size); - return EFI_UNSUPPORTED; - } - - RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock); - if ((void *)RelocEnd < orig || (void *)RelocEnd > ImageEnd) { - perror(L"Reloc %d entry overflows binary\n", n); - return EFI_UNSUPPORTED; - } - - FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress); - if (!FixupBase) { - perror(L"Reloc %d Invalid fixupbase\n", n); - return EFI_UNSUPPORTED; - } - - while (Reloc < RelocEnd) { - Fixup = FixupBase + (*Reloc & 0xFFF); - switch ((*Reloc) >> 12) { - case EFI_IMAGE_REL_BASED_ABSOLUTE: - break; - - case EFI_IMAGE_REL_BASED_HIGH: - Fixup16 = (UINT16 *) Fixup; - *Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16))); - break; - - case EFI_IMAGE_REL_BASED_LOW: - Fixup16 = (UINT16 *) Fixup; - *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust); - break; - - case EFI_IMAGE_REL_BASED_HIGHLOW: - Fixup32 = (UINT32 *) Fixup; - *Fixup32 = *Fixup32 + (UINT32) Adjust; - break; - - case EFI_IMAGE_REL_BASED_DIR64: - Fixup64 = (UINT64 *) Fixup; - *Fixup64 = *Fixup64 + (UINT64) Adjust; - break; - - default: - perror(L"Reloc %d Unknown relocation\n", n); - return EFI_UNSUPPORTED; - } - Reloc += 1; - } - RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd; - n++; - } - - return EFI_SUCCESS; -} - #define check_size_line(data, datasize_in, hashbase, hashsize, l) ({ \ if ((unsigned long)hashbase > \ (unsigned long)data + datasize_in) { \ @@ -185,113 +39,6 @@ relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, }) #define check_size(d, ds, h, hs) check_size_line(d, ds, h, hs, __LINE__) -EFI_STATUS -get_section_vma (UINTN section_num, - char *buffer, size_t bufsz UNUSED, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - char **basep, size_t *sizep, - EFI_IMAGE_SECTION_HEADER **sectionp) -{ - EFI_IMAGE_SECTION_HEADER *sections = context->FirstSection; - EFI_IMAGE_SECTION_HEADER *section; - char *base = NULL, *end = NULL; - - if (section_num >= context->NumberOfSections) - return EFI_NOT_FOUND; - - if (context->FirstSection == NULL) { - perror(L"Invalid section %d requested\n", section_num); - return EFI_UNSUPPORTED; - } - - section = §ions[section_num]; - - base = ImageAddress (buffer, context->ImageSize, section->VirtualAddress); - end = ImageAddress (buffer, context->ImageSize, - section->VirtualAddress + section->Misc.VirtualSize - 1); - - if (!(section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE)) { - if (!base) { - perror(L"Section %d has invalid base address\n", section_num); - return EFI_UNSUPPORTED; - } - if (!end) { - perror(L"Section %d has zero size\n", section_num); - return EFI_UNSUPPORTED; - } - } - - if (!(section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) && - (section->VirtualAddress < context->SizeOfHeaders || - section->PointerToRawData < context->SizeOfHeaders)) { - perror(L"Section %d is inside image headers\n", section_num); - return EFI_UNSUPPORTED; - } - - if (end < base) { - perror(L"Section %d has negative size\n", section_num); - return EFI_UNSUPPORTED; - } - - *basep = base; - *sizep = end - base; - *sectionp = section; - return EFI_SUCCESS; -} - -EFI_STATUS -get_section_vma_by_name (char *name, size_t namesz, - char *buffer, size_t bufsz, - PE_COFF_LOADER_IMAGE_CONTEXT *context, - char **basep, size_t *sizep, - EFI_IMAGE_SECTION_HEADER **sectionp) -{ - UINTN i; - char namebuf[9]; - - if (!name || namesz == 0 || !buffer || bufsz < namesz || !context - || !basep || !sizep || !sectionp) - return EFI_INVALID_PARAMETER; - - /* - * This code currently is only used for ".reloc\0\0" and - * ".sbat\0\0\0", and it doesn't know how to look up longer section - * names. - */ - if (namesz > 8) - return EFI_UNSUPPORTED; - - SetMem(namebuf, sizeof(namebuf), 0); - CopyMem(namebuf, name, MIN(namesz, 8)); - - /* - * Copy the executable's sections to their desired offsets - */ - for (i = 0; i < context->NumberOfSections; i++) { - EFI_STATUS status; - EFI_IMAGE_SECTION_HEADER *section = NULL; - char *base = NULL; - size_t size = 0; - - status = get_section_vma(i, buffer, bufsz, context, &base, &size, §ion); - if (!EFI_ERROR(status)) { - if (CompareMem(section->Name, namebuf, 8) == 0) { - *basep = base; - *sizep = size; - *sectionp = section; - return EFI_SUCCESS; - } - continue; - } - - switch(status) { - case EFI_NOT_FOUND: - break; - } - } - - return EFI_NOT_FOUND; -} /* * Calculate the SHA1 and SHA256 hashes of a binary @@ -585,249 +332,6 @@ done: return efi_status; } -/* here's a chart: - * i686 x86_64 aarch64 - * 64-on-64: nyet yes yes - * 64-on-32: nyet yes nyet - * 32-on-32: yes yes no - */ -static int -allow_64_bit(void) -{ -#if defined(__x86_64__) || defined(__aarch64__) - return 1; -#elif defined(__i386__) || defined(__i686__) - /* Right now blindly assuming the kernel will correctly detect this - * and /halt the system/ if you're not really on a 64-bit cpu */ - if (in_protocol) - return 1; - return 0; -#else /* assuming everything else is 32-bit... */ - return 0; -#endif -} - -static int -allow_32_bit(void) -{ -#if defined(__x86_64__) -#if defined(ALLOW_32BIT_KERNEL_ON_X64) - if (in_protocol) - return 1; - return 0; -#else - return 0; -#endif -#elif defined(__i386__) || defined(__i686__) - return 1; -#elif defined(__aarch64__) - return 0; -#else /* assuming everything else is 32-bit... */ - return 1; -#endif -} - -static int -image_is_64_bit(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) -{ - /* .Magic is the same offset in all cases */ - if (PEHdr->Pe32Plus.OptionalHeader.Magic - == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) - return 1; - return 0; -} - -static const UINT16 machine_type = -#if defined(__x86_64__) - IMAGE_FILE_MACHINE_X64; -#elif defined(__aarch64__) - IMAGE_FILE_MACHINE_ARM64; -#elif defined(__arm__) - IMAGE_FILE_MACHINE_ARMTHUMB_MIXED; -#elif defined(__i386__) || defined(__i486__) || defined(__i686__) - IMAGE_FILE_MACHINE_I386; -#elif defined(__ia64__) - IMAGE_FILE_MACHINE_IA64; -#else -#error this architecture is not supported by shim -#endif - -static int -image_is_loadable(EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr) -{ - /* If the machine type doesn't match the binary, bail, unless - * we're in an allowed 64-on-32 scenario */ - if (PEHdr->Pe32.FileHeader.Machine != machine_type) { - if (!(machine_type == IMAGE_FILE_MACHINE_I386 && - PEHdr->Pe32.FileHeader.Machine == IMAGE_FILE_MACHINE_X64 && - allow_64_bit())) { - return 0; - } - } - - /* If it's not a header type we recognize at all, bail */ - switch (PEHdr->Pe32Plus.OptionalHeader.Magic) { - case EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC: - case EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC: - break; - default: - return 0; - } - - /* and now just check for general 64-vs-32 compatibility */ - if (image_is_64_bit(PEHdr)) { - if (allow_64_bit()) - return 1; - } else { - if (allow_32_bit()) - return 1; - } - return 0; -} - -/* - * Read the binary header and grab appropriate information from it - */ -EFI_STATUS -read_header(void *data, unsigned int datasize, - PE_COFF_LOADER_IMAGE_CONTEXT *context) -{ - EFI_IMAGE_DOS_HEADER *DosHdr = data; - EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; - unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize; - unsigned long FileAlignment = 0; - UINT16 DllFlags; - - if (datasize < sizeof (PEHdr->Pe32)) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) - PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); - - if (!image_is_loadable(PEHdr)) { - perror(L"Platform does not support this image\n"); - return EFI_UNSUPPORTED; - } - - if (image_is_64_bit(PEHdr)) { - context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; - context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; - context->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage; - context->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment; - FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment; - OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64); - } else { - context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes; - context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders; - context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage; - context->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment; - FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment; - OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32); - } - - if (FileAlignment % 2 != 0) { - perror(L"File Alignment is invalid (%d)\n", FileAlignment); - return EFI_UNSUPPORTED; - } - if (FileAlignment == 0) - FileAlignment = 0x200; - if (context->SectionAlignment == 0) - context->SectionAlignment = PAGE_SIZE; - if (context->SectionAlignment < FileAlignment) - context->SectionAlignment = FileAlignment; - - context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections; - - if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) { - perror(L"Image header too small\n"); - return EFI_UNSUPPORTED; - } - - HeaderWithoutDataDir = OptHeaderSize - - sizeof (EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES; - if (((UINT32)PEHdr->Pe32.FileHeader.SizeOfOptionalHeader - HeaderWithoutDataDir) != - context->NumberOfRvaAndSizes * sizeof (EFI_IMAGE_DATA_DIRECTORY)) { - perror(L"Image header overflows data directory\n"); - return EFI_UNSUPPORTED; - } - - SectionHeaderOffset = DosHdr->e_lfanew - + sizeof (UINT32) - + sizeof (EFI_IMAGE_FILE_HEADER) - + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader; - if (((UINT32)context->ImageSize - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER - <= context->NumberOfSections) { - perror(L"Image sections overflow image size\n"); - return EFI_UNSUPPORTED; - } - - if ((context->SizeOfHeaders - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER - < (UINT32)context->NumberOfSections) { - perror(L"Image sections overflow section headers\n"); - return EFI_UNSUPPORTED; - } - - if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { - perror(L"Unsupported image type\n"); - return EFI_UNSUPPORTED; - } - - if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { - perror(L"Unsupported image - Relocations have been stripped\n"); - return EFI_UNSUPPORTED; - } - - context->PEHdr = PEHdr; - - if (image_is_64_bit(PEHdr)) { - context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase; - context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint; - context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; - context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - DllFlags = PEHdr->Pe32Plus.OptionalHeader.DllCharacteristics; - } else { - context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase; - context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint; - context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; - context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; - DllFlags = PEHdr->Pe32.OptionalHeader.DllCharacteristics; - } - - if ((mok_policy & MOK_POLICY_REQUIRE_NX) && - !(DllFlags & EFI_IMAGE_DLLCHARACTERISTICS_NX_COMPAT)) { - perror(L"Policy requires NX, but image does not support NX\n"); - return EFI_UNSUPPORTED; - } - - context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)); - - if (context->ImageSize < context->SizeOfHeaders) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if ((unsigned long)((UINT8 *)context->SecDir - (UINT8 *)data) > - (datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) { - perror(L"Invalid image\n"); - return EFI_UNSUPPORTED; - } - - if (context->SecDir->VirtualAddress > datasize || - (context->SecDir->VirtualAddress == datasize && - context->SecDir->Size > 0)) { - perror(L"Malformed security header\n"); - return EFI_INVALID_PARAMETER; - } - return EFI_SUCCESS; -} - EFI_STATUS verify_sbat_section(char *SBATBase, size_t SBATSize) { @@ -851,7 +355,11 @@ verify_sbat_section(char *SBATBase, size_t SBATSize) return in_protocol ? EFI_SUCCESS : EFI_SECURITY_VIOLATION; } - sbat_size = SBATSize + 1; + if (checked_add(SBATSize, 1, &sbat_size)) { + dprint(L"SBATSize + 1 would overflow\n"); + return EFI_SECURITY_VIOLATION; + } + sbat_data = AllocatePool(sbat_size); if (!sbat_data) { console_print(L"Failed to allocate .sbat section buffer\n"); @@ -937,7 +445,7 @@ get_mem_attrs (uintptr_t addr, size_t size, uint64_t *attrs) if (EFI_ERROR(efi_status) || !proto) return efi_status; - if (physaddr & 0xfff || size & 0xfff || size == 0 || attrs == NULL) { + 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), @@ -971,7 +479,7 @@ update_mem_attrs(uintptr_t addr, uint64_t size, (unsigned long long)addr, (unsigned long long)size, &before, efi_status); - if (physaddr & 0xfff || size & 0xfff || size == 0) { + 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), @@ -990,10 +498,20 @@ update_mem_attrs(uintptr_t addr, uint64_t size, 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) + if (uefi_set_attrs) { efi_status = proto->SetMemoryAttributes(proto, physaddr, size, uefi_set_attrs); - if (!EFI_ERROR(efi_status) && uefi_clear_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); @@ -1243,6 +761,7 @@ handle_image (void *data, unsigned int datasize, (Section->Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE) && (mok_policy & MOK_POLICY_REQUIRE_NX)) { perror(L"Section %d is writable and executable\n", i); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } @@ -1268,6 +787,7 @@ handle_image (void *data, unsigned int datasize, if (CompareMem(Section->Name, ".reloc\0\0", 8) == 0) { if (RelocSection) { perror(L"Image has multiple relocation sections\n"); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } /* If it has nonzero sizes, and our bounds check @@ -1277,8 +797,12 @@ handle_image (void *data, unsigned int datasize, Section->Misc.VirtualSize && base && end && RelocBase == base && - RelocBaseEnd == end) { + RelocBaseEnd <= end) { RelocSection = Section; + } else { + perror(L"Relocation section is invalid \n"); + BS->FreePages(*alloc_address, *alloc_pages); + return EFI_UNSUPPORTED; } } @@ -1288,10 +812,12 @@ handle_image (void *data, unsigned int datasize, if (!base) { perror(L"Section %d has invalid base address\n", i); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } if (!end) { perror(L"Section %d has zero size\n", i); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } @@ -1299,6 +825,7 @@ handle_image (void *data, unsigned int datasize, (Section->VirtualAddress < context.SizeOfHeaders || Section->PointerToRawData < context.SizeOfHeaders)) { perror(L"Section %d is inside image headers\n", i); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } @@ -1307,6 +834,7 @@ handle_image (void *data, unsigned int datasize, } else { if (Section->PointerToRawData < context.SizeOfHeaders) { perror(L"Section %d is inside image headers\n", i); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } @@ -1324,7 +852,7 @@ handle_image (void *data, unsigned int datasize, if (context.NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) { perror(L"Image has no relocation entry\n"); - FreePool(buffer); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } @@ -1337,7 +865,7 @@ handle_image (void *data, unsigned int datasize, if (EFI_ERROR(efi_status)) { perror(L"Relocation failed: %r\n", efi_status); - FreePool(buffer); + BS->FreePages(*alloc_address, *alloc_pages); return efi_status; } } @@ -1372,7 +900,11 @@ handle_image (void *data, unsigned int datasize, + Section->Misc.VirtualSize - 1); addr = (uintptr_t)base; - length = (uintptr_t)end - (uintptr_t)base + 1; + // Align the length up to PAGE_SIZE. This is required because + // platforms generally set memory attributes at page + // granularity, but the section length (unlike the section + // address) is not required to be aligned. + length = ALIGN_VALUE((uintptr_t)end - (uintptr_t)base + 1, PAGE_SIZE); if (Section->Characteristics & EFI_IMAGE_SCN_MEM_WRITE) { set_attrs |= MEM_ATTR_W; @@ -1399,10 +931,12 @@ handle_image (void *data, unsigned int datasize, if (!found_entry_point) { perror(L"Entry point is not within sections\n"); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } if (found_entry_point > 1) { perror(L"%d sections contain entry point\n", found_entry_point); + BS->FreePages(*alloc_address, *alloc_pages); return EFI_UNSUPPORTED; } diff --git a/replacements.c b/replacements.c index bf781a8b..469e73aa 100644 --- a/replacements.c +++ b/replacements.c @@ -78,8 +78,27 @@ replacement_start_image(EFI_HANDLE image_handle, UINTN *exit_data_size, CHAR16 * 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; - uninstall_shim_protocols(); + + /* 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)) { @@ -90,7 +109,7 @@ replacement_start_image(EFI_HANDLE image_handle, UINTN *exit_data_size, CHAR16 * console_print(L"Something has gone seriously wrong: %r\n", efi_status2); console_print(L"shim cannot continue, sorry.\n"); - msleep(5000000); + usleep(5000000); RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL); @@ -118,7 +137,7 @@ exit_boot_services(EFI_HANDLE image_key, UINTN map_key) console_print(L"Bootloader has not verified loaded image.\n"); console_print(L"System is compromised. halting.\n"); - msleep(5000000); + usleep(5000000); RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL); return EFI_SECURITY_VIOLATION; } @@ -143,7 +162,7 @@ do_exit(EFI_HANDLE ImageHandle, EFI_STATUS ExitStatus, console_print(L"Something has gone seriously wrong: %r\n", efi_status2); console_print(L"shim cannot continue, sorry.\n"); - msleep(5000000); + usleep(5000000); RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL); } @@ -4,18 +4,23 @@ */ #include "shim.h" +#include "ssp.h" +#include "ssp_var_defs.h" extern struct { - UINT32 previous_offset; + UINT32 automatic_offset; UINT32 latest_offset; } sbat_var_payload_header; +static UINT8 sbat_policy = POLICY_NOTREAD; +static UINT8 ssp_policy = POLICY_NOTREAD; + EFI_STATUS parse_sbat_section(char *section_base, size_t section_size, size_t *n_entries, struct sbat_section_entry ***entriesp) { - struct sbat_section_entry *entry = NULL, **entries; + struct sbat_section_entry *entry = NULL, **entries = NULL; EFI_STATUS efi_status = EFI_SUCCESS; list_t csv, *pos = NULL; char * end = section_base + section_size - 1; @@ -67,6 +72,13 @@ parse_sbat_section(char *section_base, size_t section_size, n++; } + /* + * Not necessarily actually an *error* since we eat newlines and + * the like; it could actually just be /empty/. + */ + if (n == 0) + goto out; + strtab = AllocateZeroPool(allocsz); if (!strtab) { efi_status = EFI_OUT_OF_RESOURCES; @@ -101,6 +113,7 @@ parse_sbat_section(char *section_base, size_t section_size, entry++; n++; } +out: *entriesp = entries; *n_entries = n; err: @@ -289,7 +302,7 @@ err: } EFI_STATUS -parse_sbat_var(list_t *entries) +parse_sbat_var(list_t *entries, char *sbat_var_candidate) { UINT8 *data = 0; UINTN datasize; @@ -301,10 +314,17 @@ parse_sbat_var(list_t *entries) return EFI_INVALID_PARAMETER; } - efi_status = get_variable(SBAT_VAR_NAME, &data, &datasize, SHIM_LOCK_GUID); - if (EFI_ERROR(efi_status)) { - LogError(L"Failed to read SBAT variable\n", efi_status); - return efi_status; + if (sbat_var_candidate == NULL) { + dprint(L"sbat_var_candidate is NULL, reading variable\n"); + efi_status = get_variable(SBAT_VAR_NAME, &data, &datasize, SHIM_LOCK_GUID); + if (EFI_ERROR(efi_status)) { + LogError(L"Failed to read SBAT variable\n", efi_status); + return efi_status; + } + } else { + datasize = strlen(sbat_var_candidate); + data = AllocatePool(datasize + 1); + memcpy(data, sbat_var_candidate, datasize); } /* @@ -312,8 +332,10 @@ parse_sbat_var(list_t *entries) * allocations, so use that here. */ efi_status = parse_sbat_var_data(entries, data, datasize+1); - if (EFI_ERROR(efi_status)) - return efi_status; + if (EFI_ERROR(efi_status)) { + dprint(L"parse_sbat_var_data() failed datasize: %d\n", datasize); + goto out; + } dprint(L"SBAT variable entries:\n"); list_for_each(pos, entries) { @@ -324,6 +346,8 @@ parse_sbat_var(list_t *entries) entry->component_generation, entry->sbat_datestamp); } +out: + FreePool(data); return efi_status; } @@ -388,6 +412,49 @@ preserve_sbat_uefi_variable(UINT8 *sbat, UINTN sbatsize, UINT32 attributes, return false; } +/* + * This looks kind of weird, but it comes directly from the MS + * documentation: + * https://support.microsoft.com/en-us/topic/kb5027455-guidance-for-blocking-vulnerable-windows-boot-managers-522bb851-0a61-44ad-aa94-ad11119c5e91 + */ +static UINT64 +ssp_ver_to_ull(UINT16 *ver) +{ + dprint("major: %u\n", ver[0]); + dprint("minor: %u\n", ver[1]); + dprint("rev: %u\n", ver[2]); + dprint("build: %u\n", ver[3]); + + return ((UINT64)ver[0] << 48) + + ((UINT64)ver[1] << 32) + + ((UINT64)ver[2] << 16) + + ver[3]; +} + +static bool +preserve_ssp_uefi_variable(UINT8 *ssp_applied, UINTN sspversize, UINT32 attributes, + uint8_t *ssp_candidate) +{ + UINT64 old, new; + + if (ssp_applied == NULL || ssp_candidate == NULL) + return false; + + if (sspversize != SSPVER_SIZE) + return false; + + if (!check_sbat_var_attributes(attributes)) + return false; + + old = ssp_ver_to_ull((UINT16 *)ssp_applied); + new = ssp_ver_to_ull((UINT16 *)ssp_candidate); + + if (new > old) + return false; + else + return true; +} + static void clear_sbat_policy() { @@ -399,74 +466,80 @@ clear_sbat_policy() } EFI_STATUS -set_sbat_uefi_variable(void) +set_sbat_uefi_variable(char *sbat_var_automatic, char *sbat_var_latest) { EFI_STATUS efi_status = EFI_SUCCESS; UINT32 attributes = 0; - char *sbat_var_previous; - char *sbat_var_latest; - UINT8 *sbat = NULL; - UINT8 *sbat_policy = NULL; + UINT8 *sbat_policyp = NULL; UINTN sbatsize = 0; UINTN sbat_policysize = 0; - char *sbat_var = NULL; + char *sbat_var_candidate = NULL; bool reset_sbat = false; - sbat_var_previous = (char *)&sbat_var_payload_header + sbat_var_payload_header.previous_offset; - sbat_var_latest = (char *)&sbat_var_payload_header + sbat_var_payload_header.latest_offset; + if (sbat_policy == POLICY_NOTREAD) { + efi_status = get_variable_attr(SBAT_POLICY, &sbat_policyp, + &sbat_policysize, SHIM_LOCK_GUID, + &attributes); + if (!EFI_ERROR(efi_status)) { + sbat_policy = *sbat_policyp; + clear_sbat_policy(); + } + } - efi_status = get_variable_attr(SBAT_POLICY, &sbat_policy, - &sbat_policysize, SHIM_LOCK_GUID, - &attributes); if (EFI_ERROR(efi_status)) { - dprint("Default sbat policy: previous\n"); - sbat_var = sbat_var_previous; + dprint("Default sbat policy: automatic\n"); + if (secure_mode()) { + sbat_var_candidate = sbat_var_automatic; + } else { + reset_sbat = true; + sbat_var_candidate = SBAT_VAR_ORIGINAL; + } } else { - switch (*sbat_policy) { - case SBAT_POLICY_LATEST: - dprint("Custom sbat policy: latest\n"); - sbat_var = sbat_var_latest; - clear_sbat_policy(); - break; - case SBAT_POLICY_PREVIOUS: - dprint("Custom sbat policy: previous\n"); - sbat_var = sbat_var_previous; - break; - case SBAT_POLICY_RESET: - if (secure_mode()) { - console_print(L"Cannot reset SBAT policy: Secure Boot is enabled.\n"); - sbat_var = sbat_var_previous; - } else { - dprint(L"Custom SBAT policy: reset OK\n"); - reset_sbat = true; - sbat_var = SBAT_VAR_ORIGINAL; - } - clear_sbat_policy(); - break; - default: - console_error(L"SBAT policy state %llu is invalid", - EFI_INVALID_PARAMETER); - sbat_var = sbat_var_previous; - clear_sbat_policy(); - break; + switch (sbat_policy) { + case POLICY_LATEST: + dprint("Custom sbat policy: latest\n"); + sbat_var_candidate = sbat_var_latest; + break; + case POLICY_AUTOMATIC: + dprint("Custom sbat policy: automatic\n"); + sbat_var_candidate = sbat_var_automatic; + break; + case POLICY_RESET: + if (secure_mode()) { + console_print(L"Cannot reset SBAT policy: Secure Boot is enabled.\n"); + sbat_var_candidate = sbat_var_automatic; + } else { + dprint(L"Custom SBAT policy: reset OK\n"); + reset_sbat = true; + sbat_var_candidate = SBAT_VAR_ORIGINAL; + } + break; + default: + console_error(L"SBAT policy state %llu is invalid", + EFI_INVALID_PARAMETER); + if (secure_mode()) { + sbat_var_candidate = sbat_var_automatic; + } else { + reset_sbat = true; + sbat_var_candidate = SBAT_VAR_ORIGINAL; + } + break; } } efi_status = get_variable_attr(SBAT_VAR_NAME, &sbat, &sbatsize, - SHIM_LOCK_GUID, &attributes); + SHIM_LOCK_GUID, &attributes); /* * Always set the SbatLevel UEFI variable if it fails to read. - * - * Don't try to set the SbatLevel UEFI variable if attributes match - * and the signature matches. */ 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) - && !reset_sbat) { + } else if (preserve_sbat_uefi_variable(sbat, sbatsize, attributes, + sbat_var_candidate) && + !reset_sbat) { dprint(L"preserving %s variable it is %d bytes, attributes are 0x%08x\n", SBAT_VAR_NAME, sbatsize, attributes); FreePool(sbat); @@ -474,6 +547,29 @@ set_sbat_uefi_variable(void) } else { FreePool(sbat); + /* + * parse the candidate SbatLevel and check that shim will not + * self revoke before writing SbatLevel variable + */ + dprint(L"shim SBAT reparse before application\n"); + efi_status = parse_sbat_var(&sbat_var, sbat_var_candidate); + if (EFI_ERROR(efi_status)) { + dprint(L"proposed SbatLevel failed to parse\n"); + return efi_status; + } + +#ifndef SHIM_UNIT_TEST + char *sbat_start = (char *)&_sbat; + char *sbat_end = (char *)&_esbat; + efi_status = verify_sbat_section(sbat_start, sbat_end - sbat_start - 1); + if (EFI_ERROR(efi_status)) { + CHAR16 *title = L"New SbatLevel would self-revoke current shim. Not applied"; + CHAR16 *message = L"Press any key to continue"; + console_countdown(title, message, 10); + return efi_status; + } +#endif /* SHIM_UNIT_TEST */ + /* delete previous variable */ dprint("%s variable is %d bytes, attributes are 0x%08x\n", SBAT_VAR_NAME, sbatsize, attributes); @@ -489,7 +585,7 @@ set_sbat_uefi_variable(void) /* set variable */ efi_status = set_variable(SBAT_VAR_NAME, SHIM_LOCK_GUID, SBAT_VAR_ATTRS, - strlen(sbat_var), sbat_var); + strlen(sbat_var_candidate), sbat_var_candidate); if (EFI_ERROR(efi_status)) { dprint(L"%s variable writing failed %r\n", SBAT_VAR_NAME, efi_status); @@ -504,18 +600,189 @@ set_sbat_uefi_variable(void) return efi_status; } - if (sbatsize != strlen(sbat_var) || - strncmp((const char *)sbat, sbat_var, strlen(sbat_var)) != 0) { + if (sbatsize != strlen(sbat_var_candidate) || + strncmp((const char *)sbat, sbat_var_candidate, + strlen(sbat_var_candidate)) != 0) { dprint("new sbatsize is %d, expected %d\n", sbatsize, - strlen(sbat_var)); + strlen(sbat_var_candidate)); efi_status = EFI_INVALID_PARAMETER; } else { dprint(L"%s variable initialization succeeded\n", SBAT_VAR_NAME); } FreePool(sbat); + return efi_status; +} + +EFI_STATUS +set_sbat_uefi_variable_internal(void) +{ + char *sbat_var_automatic; + char *sbat_var_latest; + + sbat_var_automatic = (char *)&sbat_var_payload_header + + sbat_var_payload_header.automatic_offset; + sbat_var_latest = (char *)&sbat_var_payload_header + + sbat_var_payload_header.latest_offset; + + return set_sbat_uefi_variable(sbat_var_automatic, sbat_var_latest); +} + +static void +clear_ssp_policy(void) +{ + EFI_STATUS efi_status = EFI_SUCCESS; + + efi_status = del_variable(SSP_POLICY, SHIM_LOCK_GUID); + if (EFI_ERROR(efi_status)) + console_error(L"Could not reset SSP Policy", efi_status); +} + +static EFI_STATUS +clear_ssp_uefi_variables(void) +{ + EFI_STATUS efi_status, rc = EFI_SUCCESS; + + /* delete previous variable */ + dprint("Deleting %s variable.\n", SSPVER_VAR_NAME); + efi_status = del_variable(SSPVER_VAR_NAME, SECUREBOOT_EFI_NAMESPACE_GUID); + if (EFI_ERROR(efi_status)) { + dprint(L"%s variable delete failed %r\n", SSPVER_VAR_NAME, + efi_status); + rc = efi_status; + } + dprint("Deleting %s variable.\n", SSPSIG_VAR_NAME); + efi_status = del_variable(SSPSIG_VAR_NAME, SECUREBOOT_EFI_NAMESPACE_GUID); + if (EFI_ERROR(efi_status)) { + dprint(L"%s variable delete failed %r\n", SSPSIG_VAR_NAME, + efi_status); + rc = efi_status; + } + + return rc; +} + +EFI_STATUS +set_ssp_uefi_variable(uint8_t *ssp_ver_automatic, uint8_t *ssp_sig_automatic, + uint8_t *ssp_ver_latest, uint8_t *ssp_sig_latest) +{ + EFI_STATUS efi_status = EFI_SUCCESS; + UINT32 attributes = 0; + + UINT8 *sspver = NULL; + UINT8 *policyp = NULL; + UINTN sspversize = 0; + UINTN policysize = 0; + + uint8_t *ssp_ver = NULL; + uint8_t *ssp_sig = NULL; + bool reset_ssp = false; + + _Static_assert(sizeof(SkuSiPolicyVersion) == SSPVER_SIZE, + "SkuSiPolicyVersion has unexpected size"); + _Static_assert(sizeof(SkuSiPolicyUpdateSigners) == SSPSIG_SIZE, + "SkuSiPolicyUpdateSigners has unexpected size"); + + if (ssp_policy == POLICY_NOTREAD) { + efi_status = get_variable_attr(SSP_POLICY, &policyp, + &policysize, SHIM_LOCK_GUID, + &attributes); + if (!EFI_ERROR(efi_status)) { + ssp_policy = *policyp; + clear_ssp_policy(); + } + } + + if (EFI_ERROR(efi_status)) { + dprint("Default SSP policy: automatic\n"); + ssp_ver = ssp_ver_automatic; + ssp_sig = ssp_sig_automatic; + } else { + switch (ssp_policy) { + case POLICY_LATEST: + dprint("Custom SSP policy: latest\n");\ + ssp_ver = ssp_ver_latest; + ssp_sig = ssp_sig_latest; + break; + case POLICY_AUTOMATIC: + dprint("Custom SSP policy: automatic\n"); + ssp_ver = ssp_ver_automatic; + ssp_sig = ssp_sig_automatic; + break; + case POLICY_RESET: + if (secure_mode()) { + console_print(L"Cannot reset SSP policy: Secure Boot is enabled.\n"); + ssp_ver = ssp_ver_automatic; + ssp_sig = ssp_sig_automatic; + } else { + dprint(L"Custom SSP policy: reset OK\n"); + reset_ssp = true; + } + break; + default: + console_error(L"SSP policy state %llu is invalid", + EFI_INVALID_PARAMETER); + ssp_ver = ssp_ver_automatic; + ssp_sig = ssp_sig_automatic; + break; + } + } + + if (!ssp_ver && !ssp_sig && !reset_ssp) { + dprint(L"No supplied SSP data, not setting variables\n"); + return EFI_SUCCESS; + } + + efi_status = get_variable_attr(SSPVER_VAR_NAME, &sspver, &sspversize, + SECUREBOOT_EFI_NAMESPACE_GUID, &attributes); + /* + * Since generally we want bootmgr to manage its own revocations, + * we are much less agressive trying to set those variables + */ + if (EFI_ERROR(efi_status)) { + dprint(L"SkuSiPolicyVersion read failed %r\n", efi_status); + } else if (preserve_ssp_uefi_variable(sspver, sspversize, attributes, ssp_ver) + && !reset_ssp) { + FreePool(sspver); + + dprint(L"preserving %s variable it is %d bytes, attributes are 0x%08x\n", + SSPVER_VAR_NAME, sspversize, attributes); + return EFI_SUCCESS; + } else { + FreePool(sspver); + + efi_status = clear_ssp_uefi_variables(); + } + + if (reset_ssp) + return efi_status; + + /* set variable */ + efi_status = set_variable(SSPVER_VAR_NAME, SECUREBOOT_EFI_NAMESPACE_GUID, + SSP_VAR_ATTRS, SSPVER_SIZE, ssp_ver); + if (EFI_ERROR(efi_status)) { + dprint(L"%s variable writing failed %r\n", SSPVER_VAR_NAME, + efi_status); + return efi_status; + } + dprint("done setting %s variable.\n", SSPSIG_VAR_NAME); + + efi_status = set_variable(SSPSIG_VAR_NAME, SECUREBOOT_EFI_NAMESPACE_GUID, + SSP_VAR_ATTRS, SSPSIG_SIZE, ssp_sig); + if (EFI_ERROR(efi_status)) { + dprint(L"%s variable writing failed %r\n", SSPSIG_VAR_NAME, + efi_status); + return efi_status; + } + dprint("done setting %s variable.\n", SSPSIG_VAR_NAME); return efi_status; } +EFI_STATUS +set_ssp_uefi_variable_internal(void) +{ + return set_ssp_uefi_variable(NULL, NULL, SkuSiPolicyVersion, + SkuSiPolicyUpdateSigners); +} // vim:fenc=utf-8:tw=75:noet @@ -9,12 +9,15 @@ .type sbat_var_payload_header, %object .size sbat_var_payload_header, .Lsbat_var_payload_header_end - sbat_var_payload_header sbat_var_payload_header: - .4byte .Lsbat_var_previous - sbat_var_payload_header + .4byte .Lsbat_var_automatic - sbat_var_payload_header .4byte .Lsbat_var_latest - sbat_var_payload_header .Lsbat_var_payload_header_end: .balign 1, 0 -.Lsbat_var_previous: - .asciz SBAT_VAR_PREVIOUS +.Lsbat_var_automatic: + .ascii SBAT_VAR_AUTOMATIC + .byte 0 .balign 1, 0 .Lsbat_var_latest: - .asciz SBAT_VAR_LATEST + .ascii SBAT_VAR_LATEST + .byte 0 + .section .note.GNU-stack,"a" @@ -627,11 +627,13 @@ verify_buffer_authenticode (char *data, int datasize, return EFI_SECURITY_VIOLATION; } - if (context->SecDir->Size >= size) { + if (checked_add(context->SecDir->Size, context->SecDir->VirtualAddress, &offset) || + offset > size) { perror(L"Certificate Database size is too large\n"); return EFI_INVALID_PARAMETER; } + offset = 0; ret_efi_status = EFI_NOT_FOUND; do { WIN_CERTIFICATE_EFI_PKCS *sig = NULL; @@ -642,6 +644,12 @@ verify_buffer_authenticode (char *data, int datasize, if (!sig) break; + if ((uint64_t)(uintptr_t)&sig[1] + > (uint64_t)(uintptr_t)data + datasize) { + perror(L"Certificate size is too large for secruity database"); + return EFI_INVALID_PARAMETER; + } + sz = offset + offsetof(WIN_CERTIFICATE_EFI_PKCS, Hdr.dwLength) + sizeof(sig->Hdr.dwLength); if (sz > context->SecDir->Size) { @@ -709,6 +717,12 @@ verify_buffer_sbat (char *data, int datasize, Section = context->FirstSection; for (i = 0; i < context->NumberOfSections; i++, Section++) { + if ((uint64_t)(uintptr_t)&Section[1] + > (uintptr_t)(uintptr_t)data + datasize) { + perror(L"Section exceeds bounds of image\n"); + return EFI_UNSUPPORTED; + } + if (CompareMem(Section->Name, ".sbat\0\0\0", 8) != 0) continue; @@ -731,11 +745,17 @@ verify_buffer_sbat (char *data, int datasize, * and ignore the section if it isn't. */ if (Section->SizeOfRawData && Section->SizeOfRawData >= Section->Misc.VirtualSize) { + uint64_t boundary; SBATBase = ImageAddress(data, datasize, Section->PointerToRawData); SBATSize = Section->SizeOfRawData; dprint(L"sbat section base:0x%lx size:0x%lx\n", SBATBase, SBATSize); + if (checked_add((uint64_t)(uintptr_t)SBATBase, SBATSize, &boundary) || + (boundary > (uint64_t)(uintptr_t)data + datasize)) { + perror(L"Section exceeds bounds of image\n"); + return EFI_UNSUPPORTED; + } } } @@ -753,11 +773,11 @@ verify_buffer (char *data, int datasize, { EFI_STATUS efi_status; - efi_status = verify_buffer_sbat(data, datasize, context); + efi_status = verify_buffer_authenticode(data, datasize, context, sha256hash, sha1hash); if (EFI_ERROR(efi_status)) return efi_status; - return verify_buffer_authenticode(data, datasize, context, sha256hash, sha1hash); + return verify_buffer_sbat(data, datasize, context); } static int @@ -1050,6 +1070,23 @@ restore_loaded_image(VOID) CopyMem(shim_li, &shim_li_bak, sizeof(shim_li_bak)); } +/* If gets used on static data it probably needs boundary checking */ +void +str16_to_str8(CHAR16 *str16, CHAR8 **str8) +{ + int i = 0; + + while (str16[i++] != '\0'); + *str8 = (CHAR8 *)AllocatePool((i + 1) * sizeof (CHAR8)); + + i = 0; + while (str16[i] != '\0') { + (*str8)[i] = (CHAR8)str16[i]; + i++; + } + (*str8)[i] = '\0'; +} + /* * Load and run an EFI executable */ @@ -1059,6 +1096,7 @@ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, EFI_STATUS efi_status; void *sourcebuffer = NULL; UINT64 sourcesize = 0; + CHAR8 *netbootname; /* * We need to refer to the loaded image protocol on the running @@ -1082,11 +1120,13 @@ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, } if (findNetboot(shim_li->DeviceHandle)) { - efi_status = parseNetbootinfo(image_handle); + str16_to_str8(ImagePath, &netbootname); + efi_status = parseNetbootinfo(image_handle, netbootname); if (EFI_ERROR(efi_status)) { perror(L"Netboot parsing failed: %r\n", efi_status); return EFI_PROTOCOL_ERROR; } + FreePool(netbootname); efi_status = FetchNetbootimage(image_handle, &sourcebuffer, &sourcesize); if (EFI_ERROR(efi_status)) { @@ -1097,12 +1137,14 @@ EFI_STATUS read_image(EFI_HANDLE image_handle, CHAR16 *ImagePath, *data = sourcebuffer; *datasize = sourcesize; } else if (find_httpboot(shim_li->DeviceHandle)) { + str16_to_str8(ImagePath, &netbootname); efi_status = httpboot_fetch_buffer (image_handle, &sourcebuffer, - &sourcesize); + &sourcesize, + netbootname); if (EFI_ERROR(efi_status)) { - perror(L"Unable to fetch HTTP image: %r\n", - efi_status); + perror(L"Unable to fetch HTTP image %a: %r\n", + netbootname, efi_status); return efi_status; } *data = sourcebuffer; @@ -1207,7 +1249,7 @@ EFI_STATUS init_grub(EFI_HANDLE image_handle) efi_status = start_image(image_handle, MOK_MANAGER); if (EFI_ERROR(efi_status)) { console_print(L"start_image() returned %r\n", efi_status); - msleep(2000000); + usleep(2000000); return efi_status; } @@ -1222,7 +1264,7 @@ EFI_STATUS init_grub(EFI_HANDLE image_handle) console_print( L"start_image() returned %r, falling back to default loader\n", efi_status); - msleep(2000000); + usleep(2000000); load_options = NULL; load_options_size = 0; efi_status = start_image(image_handle, DEFAULT_LOADER); @@ -1230,7 +1272,7 @@ EFI_STATUS init_grub(EFI_HANDLE image_handle) if (EFI_ERROR(efi_status)) { console_print(L"start_image() returned %r\n", efi_status); - msleep(2000000); + usleep(2000000); } return efi_status; @@ -1275,24 +1317,10 @@ EFI_STATUS set_second_stage (EFI_HANDLE image_handle) return EFI_SUCCESS; } -static void * -ossl_malloc(size_t num) -{ - return AllocatePool(num); -} - -static void -ossl_free(void *addr) -{ - FreePool(addr); -} - static void init_openssl(void) { - CRYPTO_set_mem_functions(ossl_malloc, NULL, ossl_free); OPENSSL_init(); - CRYPTO_set_mem_functions(ossl_malloc, NULL, ossl_free); ERR_load_ERR_strings(); ERR_load_BN_strings(); ERR_load_RSA_strings(); @@ -1391,6 +1419,96 @@ uninstall_shim_protocols(void) #endif } +static void +check_section_helper(char *section_name, int len, void **pointer, + EFI_IMAGE_SECTION_HEADER *Section, void *data, + int datasize, size_t minsize) +{ + if (CompareMem(Section->Name, section_name, len) == 0) { + *pointer = ImageAddress(data, datasize, Section->PointerToRawData); + if (Section->SizeOfRawData < minsize) { + dprint(L"found and rejected %.*a bad size\n", len, section_name); + dprint(L"minsize: %d\n", minsize); + dprint(L"rawsize: %d\n", Section->SizeOfRawData); + return ; + } + if (!*pointer) { + return ; + } + dprint(L"found %.*a\n", len, section_name); + } +} + +#define check_section(section_name, pointer, section, data, datasize, minsize) \ + check_section_helper(section_name, sizeof(section_name) - 1, pointer, \ + section, data, datasize, minsize) + +EFI_STATUS +load_revocations_file(EFI_HANDLE image_handle, CHAR16 *PathName) +{ + EFI_STATUS efi_status = EFI_SUCCESS; + PE_COFF_LOADER_IMAGE_CONTEXT context; + EFI_IMAGE_SECTION_HEADER *Section; + int datasize = 0; + void *data = NULL; + unsigned int i; + char *sbat_var_automatic = NULL; + char *sbat_var_latest = NULL; + uint8_t *ssps_automatic = NULL; + uint8_t *sspv_automatic = NULL; + 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 = verify_image(data, datasize, shim_li, &context); + if (EFI_ERROR(efi_status)) { + dprint(L"revocations failed to verify\n"); + return efi_status; + } + dprint(L"verified revocations\n"); + + Section = context.FirstSection; + for (i = 0; i < context.NumberOfSections; i++, Section++) { + dprint(L"checking section \"%c%c%c%c%c%c%c%c\"\n", (char *)Section->Name); + check_section(".sbata\0\0", (void **)&sbat_var_automatic, Section, + data, datasize, sizeof(SBAT_VAR_ORIGINAL)); + check_section(".sbatl\0\0", (void **)&sbat_var_latest, Section, + data, datasize, sizeof(SBAT_VAR_ORIGINAL)); + check_section(".sspva\0\0", (void **)&sspv_automatic, Section, + data, datasize, SSPVER_SIZE); + check_section(".sspsa\0\0", (void **)&ssps_automatic, Section, + data, datasize, SSPSIG_SIZE); + check_section(".sspvl\0\0", (void **)&sspv_latest, Section, + data, datasize, SSPVER_SIZE); + check_section(".sspsl\0\0", (void **)&ssps_latest, Section, + data, datasize, SSPSIG_SIZE); + } + + if (sbat_var_latest && sbat_var_automatic) { + dprint(L"attempting to update SBAT_LEVEL\n"); + efi_status = set_sbat_uefi_variable(sbat_var_automatic, + sbat_var_latest); + } else { + dprint(L"no data for SBAT_LEVEL\n"); + } + + if ((sspv_automatic && ssps_automatic) || (sspv_latest && ssps_latest)) { + dprint(L"attempting to update SkuSiPolicy\n"); + efi_status = set_ssp_uefi_variable(sspv_automatic, ssps_automatic, + sspv_latest, ssps_latest); + + } else { + dprint(L"no data for SkuSiPolicy\n"); + } + + FreePool(data); + return efi_status; +} + EFI_STATUS load_cert_file(EFI_HANDLE image_handle, CHAR16 *filename, CHAR16 *PathName) { @@ -1437,9 +1555,12 @@ load_cert_file(EFI_HANDLE image_handle, CHAR16 *filename, CHAR16 *PathName) return EFI_SUCCESS; } -/* Read additional certificates from files (after verifying signatures) */ +/* + * Read additional certificates and SBAT Level requirements from files + * (after verifying signatures) + */ EFI_STATUS -load_certs(EFI_HANDLE image_handle) +load_unbundled_trust(EFI_HANDLE image_handle) { EFI_STATUS efi_status; EFI_LOADED_IMAGE *li = NULL; @@ -1450,6 +1571,7 @@ load_certs(EFI_HANDLE image_handle) EFI_FILE_IO_INTERFACE *drive; UINTN buffersize = 0; void *buffer = NULL; + BOOLEAN search_revocations = TRUE; efi_status = gBS->HandleProtocol(image_handle, &EFI_LOADED_IMAGE_GUID, (void **)&li); @@ -1466,7 +1588,15 @@ load_certs(EFI_HANDLE image_handle) efi_status = gBS->HandleProtocol(device, &EFI_SIMPLE_FILE_SYSTEM_GUID, (void **)&drive); if (EFI_ERROR(efi_status)) { - perror(L"Failed to find fs: %r\n", efi_status); + dprint(L"Failed to find fs on local drive (netboot?): %r \n", + efi_status); + /* + * Network boot cases do not support reading a directory. Try + * to read revocations.efi 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); goto done; } @@ -1482,23 +1612,83 @@ load_certs(EFI_HANDLE image_handle) goto done; } - while (1) { - int old = buffersize; + if (!secure_mode()) + goto done; + + + while (true) { + UINTN old = buffersize; + efi_status = dir->Read(dir, &buffersize, buffer); if (efi_status == EFI_BUFFER_TOO_SMALL) { + if (buffersize == old) { + /* + * Some UEFI drivers or firmwares are not compliant with + * the EFI_FILE_PROTOCOL.Read() specs and do not return the + * required buffer size along with EFI_BUFFER_TOO_SMALL. + * Work around this by progressively increasing the buffer + * size, up to a certain point, until the call succeeds. + */ + perror(L"Error reading directory %s - non-compliant UEFI driver or firmware!\n", + PathName); + buffersize = (buffersize < 4) ? 4 : buffersize * 2; + if (buffersize > 1024) + goto done; + } buffer = ReallocatePool(buffer, old, buffersize); + if (buffer == NULL) { + perror(L"Failed to read directory %s - %r\n", + PathName, EFI_OUT_OF_RESOURCES); + goto done; + } continue; } else if (EFI_ERROR(efi_status)) { perror(L"Failed to read directory %s - %r\n", PathName, - efi_status); + efi_status); goto done; } info = (EFI_FILE_INFO *)buffer; - if (buffersize == 0 || !info) - goto done; + if (buffersize == 0 || !info) { + if (search_revocations) { + search_revocations = FALSE; + efi_status = root->Open(root, &dir, PathName, + EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(efi_status)) { + perror(L"Failed to open %s - %r\n", + PathName, efi_status); + goto done; + } + continue; + } else { + goto done; + } + } - if (StrnCaseCmp(info->FileName, L"shim_certificate", 16) == 0) { + /* + * In the event that there are unprocessed 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 + */ + if (search_revocations && + StrCaseCmp(info->FileName, REVOCATIONFILE) == 0) { + load_revocations_file(image_handle, PathName); + search_revocations = FALSE; + efi_status = root->Open(root, &dir, PathName, + EFI_FILE_MODE_READ, 0); + if (EFI_ERROR(efi_status)) { + perror(L"Failed to open %s - %r\n", + PathName, efi_status); + goto done; + } + } + + if (!search_revocations && + StrCaseCmp(info->FileName, L"shim_certificate.efi") == 0) { load_cert_file(image_handle, info->FileName, PathName); } } @@ -1637,7 +1827,7 @@ devel_egress(devel_egress_action action UNUSED) console_print(L"Waiting to %a...", reasons[action]); for (size_t sleepcount = 0; sleepcount < 10; sleepcount++) { console_print(L"%d...", 10 - sleepcount); - msleep(1000000); + usleep(1000000); } console_print(L"\ndoing %a\n", action); @@ -1708,7 +1898,7 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) */ debug_hook(); - efi_status = set_sbat_uefi_variable(); + efi_status = set_sbat_uefi_variable_internal(); if (EFI_ERROR(efi_status) && secure_mode()) { perror(L"%s variable initialization failed\n", SBAT_VAR_NAME); msg = SET_SBAT; @@ -1717,13 +1907,19 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) dprint(L"%s variable initialization failed: %r\n", SBAT_VAR_NAME, efi_status); } + efi_status = set_ssp_uefi_variable_internal(); + if (EFI_ERROR(efi_status)) { + dprint(L"%s variable initialization failed: %r\n", + SSPVER_VAR_NAME, efi_status); + } + dprint(L"%s variable initialization done\n", SSPVER_VAR_NAME); if (secure_mode()) { char *sbat_start = (char *)&_sbat; char *sbat_end = (char *)&_esbat; INIT_LIST_HEAD(&sbat_var); - efi_status = parse_sbat_var(&sbat_var); + efi_status = parse_sbat_var(&sbat_var, NULL); if (EFI_ERROR(efi_status)) { perror(L"Parsing %s variable failed: %r\n", SBAT_VAR_NAME, efi_status); @@ -1743,11 +1939,9 @@ efi_main (EFI_HANDLE passed_image_handle, EFI_SYSTEM_TABLE *passed_systab) init_openssl(); - if (secure_mode()) { - efi_status = load_certs(global_image_handle); - if (EFI_ERROR(efi_status)) { - LogError(L"Failed to load addon certificates\n"); - } + efi_status = load_unbundled_trust(global_image_handle); + if (EFI_ERROR(efi_status)) { + LogError(L"Failed to load addon certificates / sbat level\n"); } /* @@ -1775,12 +1969,18 @@ die: #if defined(ENABLE_SHIM_DEVEL) devel_egress(COLD_RESET); #else - msleep(5000000); + usleep(5000000); RT->ResetSystem(EfiResetShutdown, EFI_SECURITY_VIOLATION, 0, NULL); #endif } + /* + * This variable is supposed to be set by second stages, so ensure it is + * not set when we are starting up. + */ + (void) del_variable(SHIM_RETAIN_PROTOCOL_VAR_NAME, SHIM_LOCK_GUID); + efi_status = shim_init(); if (EFI_ERROR(efi_status)) { msg = SHIM_INIT; @@ -1792,7 +1992,7 @@ die: */ if (user_insecure_mode) { console_print(L"Booting in insecure mode\n"); - msleep(2000000); + usleep(2000000); } /* @@ -180,6 +180,7 @@ #include "include/replacements.h" #include "include/sbat.h" #include "include/sbat_var_defs.h" +#include "include/ssp.h" #if defined(OVERRIDE_SECURITY_POLICY) #include "include/security_policy.h" #endif @@ -281,18 +282,32 @@ verify_buffer (char *data, int datasize, #ifndef SHIM_UNIT_TEST #define perror_(file, line, func, fmt, ...) ({ \ UINTN __perror_ret = 0; \ + _Static_assert((fmt) != NULL, \ + "format specifier cannot be NULL"); \ if (!in_protocol) \ __perror_ret = console_print((fmt), ##__VA_ARGS__); \ LogError_(file, line, func, fmt, ##__VA_ARGS__); \ __perror_ret; \ }) -#define perror(fmt, ...) \ - perror_(__FILE__, __LINE__ - 1, __func__, fmt, ##__VA_ARGS__) -#define LogError(fmt, ...) \ - LogError_(__FILE__, __LINE__ - 1, __func__, fmt, ##__VA_ARGS__) +#define perror(fmt, ...) ({ \ + _Static_assert((fmt) != NULL, \ + "format specifier cannot be NULL"); \ + perror_(__FILE__, __LINE__ - 1, __func__, fmt, ##__VA_ARGS__); \ + }) +#define LogError(fmt, ...) ({ \ + _Static_assert((fmt) != NULL, \ + "format specifier cannot be NULL"); \ + LogError_(__FILE__, __LINE__ - 1, __func__, fmt, ##__VA_ARGS__);\ + }) #else -#define perror(fmt, ...) -#define LogError(fmt, ...) +#define perror(fmt, ...) ({ \ + _Static_assert((fmt) != NULL, \ + "format specifier cannot be NULL"); \ + }) +#define LogError(fmt, ...) ({ \ + _Static_assert((fmt) != NULL, \ + "format specifier cannot be NULL"); \ + }) #endif #ifdef ENABLE_SHIM_DEVEL @@ -305,6 +320,8 @@ verify_buffer (char *data, int datasize, #define DEBUG_VAR_NAME L"SHIM_DEBUG" #endif +#define SHIM_RETAIN_PROTOCOL_VAR_NAME L"ShimRetainProtocol" + char *translate_slashes(char *out, const char *str); #endif /* SHIM_H_ */ diff --git a/test-data/.gitignore b/test-data/.gitignore new file mode 100644 index 00000000..bbde87b1 --- /dev/null +++ b/test-data/.gitignore @@ -0,0 +1 @@ +!/*.efi diff --git a/test-data/grubx64.0.76.el7.1.efi b/test-data/grubx64.0.76.el7.1.efi Binary files differnew file mode 100755 index 00000000..29f6812a --- /dev/null +++ b/test-data/grubx64.0.76.el7.1.efi diff --git a/test-data/grubx64.0.76.el7.efi b/test-data/grubx64.0.76.el7.efi Binary files differnew file mode 100755 index 00000000..5a198f6e --- /dev/null +++ b/test-data/grubx64.0.76.el7.efi diff --git a/test-data/grubx64.0.80.el7.efi b/test-data/grubx64.0.80.el7.efi Binary files differnew file mode 100755 index 00000000..c5f28d90 --- /dev/null +++ b/test-data/grubx64.0.80.el7.efi diff --git a/test-pe-relocate.c b/test-pe-relocate.c new file mode 100644 index 00000000..55577791 --- /dev/null +++ b/test-pe-relocate.c @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * test-pe-reloc.c - attempt to test relocate_coff() + * Copyright Peter Jones <pjones@redhat.com> + */ + +#ifndef SHIM_UNIT_TEST +#define SHIM_UNIT_TEST +#endif +#include "shim.h" + +static int +test_image_address(void) +{ + char image[4]; + void *ret; + + assert_equal_return(ImageAddress(image, sizeof(image), 0), &image[0], -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress(image, sizeof(image), 4), NULL, -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress((void *)1, 2, 3), NULL, -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress((void *)-1ull, UINT64_MAX, UINT64_MAX), NULL, -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress((void *)0, UINT64_MAX, UINT64_MAX), NULL, -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress((void *)1, UINT64_MAX, UINT64_MAX), NULL, -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress((void *)2, UINT64_MAX, UINT64_MAX), NULL, -1, "got %p expected %p\n"); + assert_equal_return(ImageAddress((void *)3, UINT64_MAX, UINT64_MAX), NULL, -1, "got %p expected %p\n"); + + return 0; +} + +int +main(void) +{ + int status = 0; + test(test_image_address); + + return status; +} + +// vim:fenc=utf-8:tw=75:noet diff --git a/test-pe-util.c b/test-pe-util.c new file mode 100644 index 00000000..d5765488 --- /dev/null +++ b/test-pe-util.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSD-2-Clause-Patent +/* + * test-pe-util.c - test PE utilities + */ + +#ifndef SHIM_UNIT_TEST +#define SHIM_UNIT_TEST +#endif +#include "shim.h" + +static int +test_is_page_aligned(void) +{ + assert_true_return(IS_PAGE_ALIGNED(0), -1, "\n"); + assert_false_return(IS_PAGE_ALIGNED(1), -1, "\n"); + assert_false_return(IS_PAGE_ALIGNED(4095), -1, "\n"); + assert_true_return(IS_PAGE_ALIGNED(4096), -1, "\n"); + assert_false_return(IS_PAGE_ALIGNED(4097), -1, "\n"); + + return 0; +} + +int +main(void) +{ + int status = 0; + test(test_is_page_aligned); + + return status; +} diff --git a/test-sbat.c b/test-sbat.c index 72bebe7a..b37efcdd 100644 --- a/test-sbat.c +++ b/test-sbat.c @@ -5,6 +5,7 @@ #ifndef SHIM_UNIT_TEST #define SHIM_UNIT_TEST +#include "sbat_var_defs.h" #endif #include "shim.h" @@ -196,6 +197,22 @@ free_mock_sbat_entries(list_t *entries) * parse_sbat_section() tests */ int +test_parse_sbat_tiny(void) +{ + char section_base[] = "\0a\00"; + size_t section_size = 2; + struct sbat_section_entry **entries; + size_t n = 0; + EFI_STATUS status; + + status = parse_sbat_section(section_base, section_size, &n, &entries); + assert_equal_return(status, EFI_SUCCESS, -1, "got %#hhx expected %#hhx\n"); + assert_equal_return(n, 0, -1, "got %#hhx expected %#hhx\n"); + + return 0; +} + +int test_parse_sbat_section_null_sbat_base(void) { char *section_base = NULL; @@ -362,7 +379,7 @@ test_parse_sbat_var_null_list(void) EFI_STATUS status; INIT_LIST_HEAD(&sbat_var); - status = parse_sbat_var(NULL); + status = parse_sbat_var(NULL, NULL); cleanup_sbat_var(&sbat_var); assert_equal_return(status, EFI_INVALID_PARAMETER, -1, "got %#hhx expected %#hhx\n"); @@ -1107,11 +1124,43 @@ test_preserve_sbat_uefi_variable_bad_short(void) return 0; } +static int +test_sbat_var_asciz(void) +{ + EFI_STATUS status; + char buf[1024] = ""; + UINT32 attrs = 0; + UINTN size = sizeof(buf); + char expected[] = SBAT_VAR_AUTOMATIC; + + status = set_sbat_uefi_variable(SBAT_VAR_AUTOMATIC, SBAT_VAR_AUTOMATIC); + if (status != EFI_SUCCESS) + return -1; + + status = RT->GetVariable(SBAT_VAR_NAME, &SHIM_LOCK_GUID, &attrs, &size, buf); + if (status != EFI_SUCCESS) + return -1; + + /* + * this should be enough to get past "sbat,", which handles the + * first error. + */ + if (size < (strlen(SBAT_VAR_SIG) + 2) || size != strlen(expected)) + return -1; + + if (strncmp(expected, buf, size) != 0) + return -1; + + return 0; +} + int main(void) { int status = 0; + // parse_sbat section tests + test(test_parse_sbat_tiny); test(test_parse_sbat_section_null_sbat_base); test(test_parse_sbat_section_zero_sbat_size); test(test_parse_sbat_section_null_entries); @@ -1155,7 +1204,9 @@ main(void) test(test_preserve_sbat_uefi_variable_version_older); test(test_preserve_sbat_uefi_variable_version_olderlonger); - return 0; + test(test_sbat_var_asciz); + + return status; } // vim:fenc=utf-8:tw=75:noet |