mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-06-13 18:40:21 +00:00
Move a bunch of PE-related stuff out of shim.c
Signed-off-by: Peter Jones <pjones@redhat.com>
This commit is contained in:
parent
186595864c
commit
06e98a10f6
4
Makefile
4
Makefile
@ -33,9 +33,9 @@ CFLAGS += -DENABLE_SHIM_CERT
|
||||
else
|
||||
TARGETS += $(MMNAME) $(FBNAME)
|
||||
endif
|
||||
OBJS = shim.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o
|
||||
OBJS = shim.o mok.o netboot.o cert.o replacements.o tpm.o version.o errlog.o sbat.o pe.o
|
||||
KEYS = shim_cert.h ocsp.* ca.* shim.crt shim.csr shim.p12 shim.pem shim.key shim.cer
|
||||
ORIG_SOURCES = shim.c mok.c netboot.c replacements.c tpm.c errlog.c shim.h version.h $(wildcard include/*.h)
|
||||
ORIG_SOURCES = shim.c mok.c netboot.c replacements.c tpm.c errlog.c pe.c shim.h version.h $(wildcard include/*.h)
|
||||
MOK_OBJS = MokManager.o PasswordCrypt.o crypt_blowfish.o errlog.o sbat.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.o
|
||||
|
35
include/pe.h
Normal file
35
include/pe.h
Normal file
@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
/*
|
||||
* pe.h - helper functions for pe binaries.
|
||||
* Copyright Peter Jones <pjones@redhat.com>
|
||||
*/
|
||||
|
||||
#ifndef PE_H_
|
||||
#define PE_H_
|
||||
|
||||
void *
|
||||
ImageAddress (void *image, uint64_t size, uint64_t address);
|
||||
|
||||
EFI_STATUS
|
||||
read_header(void *data, unsigned int datasize,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context);
|
||||
|
||||
EFI_STATUS
|
||||
handle_image (void *data, unsigned int datasize,
|
||||
EFI_LOADED_IMAGE *li,
|
||||
EFI_IMAGE_ENTRY_POINT *entry_point,
|
||||
EFI_PHYSICAL_ADDRESS *alloc_address,
|
||||
UINTN *alloc_pages);
|
||||
|
||||
EFI_STATUS
|
||||
generate_hash (char *data, unsigned int datasize_in,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
UINT8 *sha256hash, UINT8 *sha1hash);
|
||||
|
||||
EFI_STATUS
|
||||
relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
EFI_IMAGE_SECTION_HEADER *Section,
|
||||
void *orig, void *data);
|
||||
|
||||
#endif /* !PE_H_ */
|
||||
// vim:fenc=utf-8:tw=75:noet
|
940
pe.c
Normal file
940
pe.c
Normal file
@ -0,0 +1,940 @@
|
||||
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
||||
/*
|
||||
* pe.c - helper functions for pe binaries.
|
||||
* Copyright Peter Jones <pjones@redhat.com>
|
||||
*/
|
||||
|
||||
#include "shim.h"
|
||||
#include "hexdump.h"
|
||||
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/ocsp.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/dso.h>
|
||||
|
||||
#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) { \
|
||||
efi_status = EFI_INVALID_PARAMETER; \
|
||||
perror(L"shim.c:%d Invalid hash base 0x%016x\n", l, \
|
||||
hashbase); \
|
||||
goto done; \
|
||||
} \
|
||||
if ((unsigned long)hashbase + hashsize > \
|
||||
(unsigned long)data + datasize_in) { \
|
||||
efi_status = EFI_INVALID_PARAMETER; \
|
||||
perror(L"shim.c:%d Invalid hash size 0x%016x\n", l, \
|
||||
hashsize); \
|
||||
goto done; \
|
||||
} \
|
||||
})
|
||||
#define check_size(d, ds, h, hs) check_size_line(d, ds, h, hs, __LINE__)
|
||||
|
||||
/*
|
||||
* Calculate the SHA1 and SHA256 hashes of a binary
|
||||
*/
|
||||
|
||||
EFI_STATUS
|
||||
generate_hash(char *data, unsigned int datasize_in,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context, UINT8 *sha256hash,
|
||||
UINT8 *sha1hash)
|
||||
{
|
||||
unsigned int sha256ctxsize, sha1ctxsize;
|
||||
unsigned int size = datasize_in;
|
||||
void *sha256ctx = NULL, *sha1ctx = NULL;
|
||||
char *hashbase;
|
||||
unsigned int hashsize;
|
||||
unsigned int SumOfBytesHashed, SumOfSectionBytes;
|
||||
unsigned int index, pos;
|
||||
unsigned int datasize;
|
||||
EFI_IMAGE_SECTION_HEADER *Section;
|
||||
EFI_IMAGE_SECTION_HEADER *SectionHeader = NULL;
|
||||
EFI_STATUS efi_status = EFI_SUCCESS;
|
||||
EFI_IMAGE_DOS_HEADER *DosHdr = (void *)data;
|
||||
unsigned int PEHdr_offset = 0;
|
||||
|
||||
size = datasize = datasize_in;
|
||||
|
||||
if (datasize <= sizeof (*DosHdr) ||
|
||||
DosHdr->e_magic != EFI_IMAGE_DOS_SIGNATURE) {
|
||||
perror(L"Invalid signature\n");
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
PEHdr_offset = DosHdr->e_lfanew;
|
||||
|
||||
sha256ctxsize = Sha256GetContextSize();
|
||||
sha256ctx = AllocatePool(sha256ctxsize);
|
||||
|
||||
sha1ctxsize = Sha1GetContextSize();
|
||||
sha1ctx = AllocatePool(sha1ctxsize);
|
||||
|
||||
if (!sha256ctx || !sha1ctx) {
|
||||
perror(L"Unable to allocate memory for hash context\n");
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
if (!Sha256Init(sha256ctx) || !Sha1Init(sha1ctx)) {
|
||||
perror(L"Unable to initialise hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Hash start to checksum */
|
||||
hashbase = data;
|
||||
hashsize = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum -
|
||||
hashbase;
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Hash post-checksum to start of certificate table */
|
||||
hashbase = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum +
|
||||
sizeof (int);
|
||||
hashsize = (char *)context->SecDir - hashbase;
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Hash end of certificate table to end of image header */
|
||||
EFI_IMAGE_DATA_DIRECTORY *dd = context->SecDir + 1;
|
||||
hashbase = (char *)dd;
|
||||
hashsize = context->SizeOfHeaders - (unsigned long)((char *)dd - data);
|
||||
if (hashsize > datasize_in) {
|
||||
perror(L"Data Directory size %d is invalid\n", hashsize);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Sort sections */
|
||||
SumOfBytesHashed = context->SizeOfHeaders;
|
||||
|
||||
/* Validate section locations and sizes */
|
||||
for (index = 0, SumOfSectionBytes = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) {
|
||||
EFI_IMAGE_SECTION_HEADER *SectionPtr;
|
||||
|
||||
/* Validate SectionPtr is within image */
|
||||
SectionPtr = ImageAddress(data, datasize,
|
||||
PEHdr_offset +
|
||||
sizeof (UINT32) +
|
||||
sizeof (EFI_IMAGE_FILE_HEADER) +
|
||||
context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader +
|
||||
(index * sizeof(*SectionPtr)));
|
||||
if (!SectionPtr) {
|
||||
perror(L"Malformed section %d\n", index);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
/* Validate section size is within image. */
|
||||
if (SectionPtr->SizeOfRawData >
|
||||
datasize - SumOfBytesHashed - SumOfSectionBytes) {
|
||||
perror(L"Malformed section %d size\n", index);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
SumOfSectionBytes += SectionPtr->SizeOfRawData;
|
||||
}
|
||||
|
||||
SectionHeader = (EFI_IMAGE_SECTION_HEADER *) AllocateZeroPool (sizeof (EFI_IMAGE_SECTION_HEADER) * context->PEHdr->Pe32.FileHeader.NumberOfSections);
|
||||
if (SectionHeader == NULL) {
|
||||
perror(L"Unable to allocate section header\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Already validated above */
|
||||
Section = ImageAddress(data, datasize,
|
||||
PEHdr_offset +
|
||||
sizeof (UINT32) +
|
||||
sizeof (EFI_IMAGE_FILE_HEADER) +
|
||||
context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader);
|
||||
/* But check it again just for better error messaging, and so
|
||||
* clang-analyzer doesn't get confused. */
|
||||
if (Section == NULL) {
|
||||
uint64_t addr;
|
||||
|
||||
addr = PEHdr_offset + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)
|
||||
+ context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader;
|
||||
perror(L"Malformed file header.\n");
|
||||
perror(L"Image address for Section 0 is 0x%016llx\n", addr);
|
||||
perror(L"File size is 0x%016llx\n", datasize);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Sort the section headers */
|
||||
for (index = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) {
|
||||
pos = index;
|
||||
while ((pos > 0) && (Section->PointerToRawData < SectionHeader[pos - 1].PointerToRawData)) {
|
||||
CopyMem (&SectionHeader[pos], &SectionHeader[pos - 1], sizeof (EFI_IMAGE_SECTION_HEADER));
|
||||
pos--;
|
||||
}
|
||||
CopyMem (&SectionHeader[pos], Section, sizeof (EFI_IMAGE_SECTION_HEADER));
|
||||
Section += 1;
|
||||
}
|
||||
|
||||
/* Hash the sections */
|
||||
for (index = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) {
|
||||
Section = &SectionHeader[index];
|
||||
if (Section->SizeOfRawData == 0) {
|
||||
continue;
|
||||
}
|
||||
hashbase = ImageAddress(data, size, Section->PointerToRawData);
|
||||
|
||||
if (!hashbase) {
|
||||
perror(L"Malformed section header\n");
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Verify hashsize within image. */
|
||||
if (Section->SizeOfRawData >
|
||||
datasize - Section->PointerToRawData) {
|
||||
perror(L"Malformed section raw size %d\n", index);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
hashsize = (unsigned int) Section->SizeOfRawData;
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
SumOfBytesHashed += Section->SizeOfRawData;
|
||||
}
|
||||
|
||||
/* Hash all remaining data up to SecDir if SecDir->Size is not 0 */
|
||||
if (datasize > SumOfBytesHashed && context->SecDir->Size) {
|
||||
hashbase = data + SumOfBytesHashed;
|
||||
hashsize = datasize - context->SecDir->Size - SumOfBytesHashed;
|
||||
|
||||
if ((datasize - SumOfBytesHashed < context->SecDir->Size) ||
|
||||
(SumOfBytesHashed + hashsize != context->SecDir->VirtualAddress)) {
|
||||
perror(L"Malformed binary after Attribute Certificate Table\n");
|
||||
console_print(L"datasize: %u SumOfBytesHashed: %u SecDir->Size: %lu\n",
|
||||
datasize, SumOfBytesHashed, context->SecDir->Size);
|
||||
console_print(L"hashsize: %u SecDir->VirtualAddress: 0x%08lx\n",
|
||||
hashsize, context->SecDir->VirtualAddress);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if 1
|
||||
}
|
||||
#else // we have to migrate to doing this later :/
|
||||
SumOfBytesHashed += hashsize;
|
||||
}
|
||||
|
||||
/* Hash all remaining data */
|
||||
if (datasize > SumOfBytesHashed) {
|
||||
hashbase = data + SumOfBytesHashed;
|
||||
hashsize = datasize - SumOfBytesHashed;
|
||||
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
SumOfBytesHashed += hashsize;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!(Sha256Final(sha256ctx, sha256hash)) ||
|
||||
!(Sha1Final(sha1ctx, sha1hash))) {
|
||||
perror(L"Unable to finalise hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
dprint(L"sha1 authenticode hash:\n");
|
||||
dhexdumpat(sha1hash, SHA1_DIGEST_SIZE, 0);
|
||||
dprint(L"sha256 authenticode hash:\n");
|
||||
dhexdumpat(sha256hash, SHA256_DIGEST_SIZE, 0);
|
||||
|
||||
done:
|
||||
if (SectionHeader)
|
||||
FreePool(SectionHeader);
|
||||
if (sha1ctx)
|
||||
FreePool(sha1ctx);
|
||||
if (sha256ctx)
|
||||
FreePool(sha256ctx);
|
||||
|
||||
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;
|
||||
|
||||
if (datasize < sizeof (PEHdr->Pe32)) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE)
|
||||
PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew);
|
||||
|
||||
if (!image_is_loadable(PEHdr)) {
|
||||
perror(L"Platform does not support this image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (image_is_64_bit(PEHdr)) {
|
||||
context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
|
||||
context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders;
|
||||
context->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage;
|
||||
context->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment;
|
||||
FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment;
|
||||
OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64);
|
||||
} else {
|
||||
context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes;
|
||||
context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders;
|
||||
context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage;
|
||||
context->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment;
|
||||
FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment;
|
||||
OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32);
|
||||
}
|
||||
|
||||
if (FileAlignment % 2 != 0) {
|
||||
perror(L"File Alignment is invalid (%d)\n", FileAlignment);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (FileAlignment == 0)
|
||||
FileAlignment = 0x200;
|
||||
if (context->SectionAlignment == 0)
|
||||
context->SectionAlignment = PAGE_SIZE;
|
||||
if (context->SectionAlignment < FileAlignment)
|
||||
context->SectionAlignment = FileAlignment;
|
||||
|
||||
context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections;
|
||||
|
||||
if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) {
|
||||
perror(L"Image header too small\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
HeaderWithoutDataDir = OptHeaderSize
|
||||
- sizeof (EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES;
|
||||
if (((UINT32)PEHdr->Pe32.FileHeader.SizeOfOptionalHeader - HeaderWithoutDataDir) !=
|
||||
context->NumberOfRvaAndSizes * sizeof (EFI_IMAGE_DATA_DIRECTORY)) {
|
||||
perror(L"Image header overflows data directory\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SectionHeaderOffset = DosHdr->e_lfanew
|
||||
+ sizeof (UINT32)
|
||||
+ sizeof (EFI_IMAGE_FILE_HEADER)
|
||||
+ PEHdr->Pe32.FileHeader.SizeOfOptionalHeader;
|
||||
if (((UINT32)context->ImageSize - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER
|
||||
<= context->NumberOfSections) {
|
||||
perror(L"Image sections overflow image size\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if ((context->SizeOfHeaders - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER
|
||||
< (UINT32)context->NumberOfSections) {
|
||||
perror(L"Image sections overflow section headers\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) {
|
||||
perror(L"Unsupported image type\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) {
|
||||
perror(L"Unsupported image - Relocations have been stripped\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
context->PEHdr = PEHdr;
|
||||
|
||||
if (image_is_64_bit(PEHdr)) {
|
||||
context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase;
|
||||
context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint;
|
||||
context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||||
} else {
|
||||
context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase;
|
||||
context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint;
|
||||
context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||||
}
|
||||
|
||||
context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER));
|
||||
|
||||
if (context->ImageSize < context->SizeOfHeaders) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if ((unsigned long)((UINT8 *)context->SecDir - (UINT8 *)data) >
|
||||
(datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Once the image has been loaded it needs to be validated and relocated
|
||||
*/
|
||||
EFI_STATUS
|
||||
handle_image (void *data, unsigned int datasize,
|
||||
EFI_LOADED_IMAGE *li,
|
||||
EFI_IMAGE_ENTRY_POINT *entry_point,
|
||||
EFI_PHYSICAL_ADDRESS *alloc_address,
|
||||
UINTN *alloc_pages)
|
||||
{
|
||||
EFI_STATUS efi_status;
|
||||
char *buffer;
|
||||
int i;
|
||||
EFI_IMAGE_SECTION_HEADER *Section;
|
||||
char *base, *end;
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT context;
|
||||
unsigned int alignment, alloc_size;
|
||||
int found_entry_point = 0;
|
||||
UINT8 sha1hash[SHA1_DIGEST_SIZE];
|
||||
UINT8 sha256hash[SHA256_DIGEST_SIZE];
|
||||
|
||||
/*
|
||||
* The binary header contains relevant context and section pointers
|
||||
*/
|
||||
efi_status = read_header(data, datasize, &context);
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
perror(L"Failed to read header: %r\n", efi_status);
|
||||
return efi_status;
|
||||
}
|
||||
|
||||
/*
|
||||
* We only need to verify the binary if we're in secure mode
|
||||
*/
|
||||
efi_status = generate_hash(data, datasize, &context, sha256hash,
|
||||
sha1hash);
|
||||
if (EFI_ERROR(efi_status))
|
||||
return efi_status;
|
||||
|
||||
/* Measure the binary into the TPM */
|
||||
#ifdef REQUIRE_TPM
|
||||
efi_status =
|
||||
#endif
|
||||
tpm_log_pe((EFI_PHYSICAL_ADDRESS)(UINTN)data, datasize,
|
||||
(EFI_PHYSICAL_ADDRESS)(UINTN)context.ImageAddress,
|
||||
li->FilePath, sha1hash, 4);
|
||||
#ifdef REQUIRE_TPM
|
||||
if (efi_status != EFI_SUCCESS) {
|
||||
return efi_status;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (secure_mode ()) {
|
||||
efi_status = verify_buffer(data, datasize, &context,
|
||||
sha256hash, sha1hash);
|
||||
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
if (verbose)
|
||||
console_print(L"Verification failed: %r\n", efi_status);
|
||||
else
|
||||
console_error(L"Verification failed", efi_status);
|
||||
return efi_status;
|
||||
} else {
|
||||
if (verbose)
|
||||
console_print(L"Verification succeeded\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* The spec says, uselessly, of SectionAlignment:
|
||||
* =====
|
||||
* The alignment (in bytes) of sections when they are loaded into
|
||||
* memory. It must be greater than or equal to FileAlignment. The
|
||||
* default is the page size for the architecture.
|
||||
* =====
|
||||
* Which doesn't tell you whose responsibility it is to enforce the
|
||||
* "default", or when. It implies that the value in the field must
|
||||
* be > FileAlignment (also poorly defined), but it appears visual
|
||||
* studio will happily write 512 for FileAlignment (its default) and
|
||||
* 0 for SectionAlignment, intending to imply PAGE_SIZE.
|
||||
*
|
||||
* We only support one page size, so if it's zero, nerf it to 4096.
|
||||
*/
|
||||
alignment = context.SectionAlignment;
|
||||
if (!alignment)
|
||||
alignment = 4096;
|
||||
|
||||
alloc_size = ALIGN_VALUE(context.ImageSize + context.SectionAlignment,
|
||||
PAGE_SIZE);
|
||||
*alloc_pages = alloc_size / PAGE_SIZE;
|
||||
|
||||
efi_status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderCode,
|
||||
*alloc_pages, alloc_address);
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
perror(L"Failed to allocate image buffer\n");
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
buffer = (void *)ALIGN_VALUE((unsigned long)*alloc_address, alignment);
|
||||
|
||||
CopyMem(buffer, data, context.SizeOfHeaders);
|
||||
|
||||
*entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint);
|
||||
if (!*entry_point) {
|
||||
perror(L"Entry point is invalid\n");
|
||||
gBS->FreePages(*alloc_address, *alloc_pages);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
char *RelocBase, *RelocBaseEnd;
|
||||
/*
|
||||
* These are relative virtual addresses, so we have to check them
|
||||
* against the image size, not the data size.
|
||||
*/
|
||||
RelocBase = ImageAddress(buffer, context.ImageSize,
|
||||
context.RelocDir->VirtualAddress);
|
||||
/*
|
||||
* RelocBaseEnd here is the address of the last byte of the table
|
||||
*/
|
||||
RelocBaseEnd = ImageAddress(buffer, context.ImageSize,
|
||||
context.RelocDir->VirtualAddress +
|
||||
context.RelocDir->Size - 1);
|
||||
|
||||
EFI_IMAGE_SECTION_HEADER *RelocSection = NULL;
|
||||
|
||||
/*
|
||||
* Copy the executable's sections to their desired offsets
|
||||
*/
|
||||
Section = context.FirstSection;
|
||||
for (i = 0; i < context.NumberOfSections; i++, Section++) {
|
||||
/* Don't try to copy discardable sections with zero size */
|
||||
if ((Section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE) &&
|
||||
!Section->Misc.VirtualSize)
|
||||
continue;
|
||||
|
||||
base = ImageAddress (buffer, context.ImageSize,
|
||||
Section->VirtualAddress);
|
||||
end = ImageAddress (buffer, context.ImageSize,
|
||||
Section->VirtualAddress
|
||||
+ Section->Misc.VirtualSize - 1);
|
||||
|
||||
if (end < base) {
|
||||
perror(L"Section %d has negative size\n", i);
|
||||
gBS->FreePages(*alloc_address, *alloc_pages);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (Section->VirtualAddress <= context.EntryPoint &&
|
||||
(Section->VirtualAddress + Section->SizeOfRawData - 1)
|
||||
> context.EntryPoint)
|
||||
found_entry_point++;
|
||||
|
||||
/* We do want to process .reloc, but it's often marked
|
||||
* discardable, so we don't want to memcpy it. */
|
||||
if (CompareMem(Section->Name, ".reloc\0\0", 8) == 0) {
|
||||
if (RelocSection) {
|
||||
perror(L"Image has multiple relocation sections\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
/* If it has nonzero sizes, and our bounds check
|
||||
* made sense, and the VA and size match RelocDir's
|
||||
* versions, then we believe in this section table. */
|
||||
if (Section->SizeOfRawData &&
|
||||
Section->Misc.VirtualSize &&
|
||||
base && end &&
|
||||
RelocBase == base &&
|
||||
RelocBaseEnd == end) {
|
||||
RelocSection = Section;
|
||||
}
|
||||
}
|
||||
|
||||
if (Section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!base) {
|
||||
perror(L"Section %d has invalid base address\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (!end) {
|
||||
perror(L"Section %d has zero size\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (!(Section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) &&
|
||||
(Section->VirtualAddress < context.SizeOfHeaders ||
|
||||
Section->PointerToRawData < context.SizeOfHeaders)) {
|
||||
perror(L"Section %d is inside image headers\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (Section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) {
|
||||
ZeroMem(base, Section->Misc.VirtualSize);
|
||||
} else {
|
||||
if (Section->PointerToRawData < context.SizeOfHeaders) {
|
||||
perror(L"Section %d is inside image headers\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (Section->SizeOfRawData > 0)
|
||||
CopyMem(base, data + Section->PointerToRawData,
|
||||
Section->SizeOfRawData);
|
||||
|
||||
if (Section->SizeOfRawData < Section->Misc.VirtualSize)
|
||||
ZeroMem(base + Section->SizeOfRawData,
|
||||
Section->Misc.VirtualSize - Section->SizeOfRawData);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
|
||||
perror(L"Image has no relocation entry\n");
|
||||
FreePool(buffer);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (context.RelocDir->Size && RelocSection) {
|
||||
/*
|
||||
* Run the relocation fixups
|
||||
*/
|
||||
efi_status = relocate_coff(&context, RelocSection, data,
|
||||
buffer);
|
||||
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
perror(L"Relocation failed: %r\n", efi_status);
|
||||
FreePool(buffer);
|
||||
return efi_status;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* grub needs to know its location and size in memory, so fix up
|
||||
* the loaded image protocol values
|
||||
*/
|
||||
li->ImageBase = buffer;
|
||||
li->ImageSize = context.ImageSize;
|
||||
|
||||
/* Pass the load options to the second stage loader */
|
||||
if ( load_options ) {
|
||||
li->LoadOptions = load_options;
|
||||
li->LoadOptionsSize = load_options_size;
|
||||
}
|
||||
|
||||
if (!found_entry_point) {
|
||||
perror(L"Entry point is not within sections\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (found_entry_point > 1) {
|
||||
perror(L"%d sections contain entry point\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// vim:fenc=utf-8:tw=75:noet
|
925
shim.c
925
shim.c
@ -62,8 +62,8 @@ static EFI_SYSTEM_TABLE *systab;
|
||||
static EFI_HANDLE global_image_handle;
|
||||
|
||||
static CHAR16 *second_stage;
|
||||
static void *load_options;
|
||||
static UINT32 load_options_size;
|
||||
void *load_options;
|
||||
UINT32 load_options_size;
|
||||
|
||||
/*
|
||||
* The vendor certificate used for validating the second stage loader
|
||||
@ -108,250 +108,6 @@ typedef struct {
|
||||
UINT8 *Mok;
|
||||
} MokListNode;
|
||||
|
||||
/*
|
||||
* Perform basic bounds checking of the intra-image pointers
|
||||
*/
|
||||
static void *ImageAddress (void *image, uint64_t size, uint64_t address)
|
||||
{
|
||||
/* ensure our local pointer isn't bigger than our size */
|
||||
if (address > size)
|
||||
return NULL;
|
||||
|
||||
/* Insure our math won't overflow */
|
||||
if (UINT64_MAX - address < (uint64_t)(intptr_t)image)
|
||||
return NULL;
|
||||
|
||||
/* return the absolute pointer */
|
||||
return image + address;
|
||||
}
|
||||
|
||||
/* here's a chart:
|
||||
* i686 x86_64 aarch64
|
||||
* 64-on-64: nyet yes yes
|
||||
* 64-on-32: nyet yes nyet
|
||||
* 32-on-32: yes yes no
|
||||
*/
|
||||
static int
|
||||
allow_64_bit(void)
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__aarch64__)
|
||||
return 1;
|
||||
#elif defined(__i386__) || defined(__i686__)
|
||||
/* Right now blindly assuming the kernel will correctly detect this
|
||||
* and /halt the system/ if you're not really on a 64-bit cpu */
|
||||
if (in_protocol)
|
||||
return 1;
|
||||
return 0;
|
||||
#else /* assuming everything else is 32-bit... */
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
allow_32_bit(void)
|
||||
{
|
||||
#if defined(__x86_64__)
|
||||
#if defined(ALLOW_32BIT_KERNEL_ON_X64)
|
||||
if (in_protocol)
|
||||
return 1;
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
#elif defined(__i386__) || defined(__i686__)
|
||||
return 1;
|
||||
#elif defined(__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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform the actual relocation
|
||||
*/
|
||||
static EFI_STATUS relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
EFI_IMAGE_SECTION_HEADER *Section,
|
||||
void *orig, void *data)
|
||||
{
|
||||
EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd;
|
||||
UINT64 Adjust;
|
||||
UINT16 *Reloc, *RelocEnd;
|
||||
char *Fixup, *FixupBase;
|
||||
UINT16 *Fixup16;
|
||||
UINT32 *Fixup32;
|
||||
UINT64 *Fixup64;
|
||||
int size = context->ImageSize;
|
||||
void *ImageEnd = (char *)orig + size;
|
||||
int n = 0;
|
||||
|
||||
/* Alright, so here's how this works:
|
||||
*
|
||||
* context->RelocDir gives us two things:
|
||||
* - the VA the table of base relocation blocks are (maybe) to be
|
||||
* mapped at (RelocDir->VirtualAddress)
|
||||
* - the virtual size (RelocDir->Size)
|
||||
*
|
||||
* The .reloc section (Section here) gives us some other things:
|
||||
* - the name! kind of. (Section->Name)
|
||||
* - the virtual size (Section->VirtualSize), which should be the same
|
||||
* as RelocDir->Size
|
||||
* - the virtual address (Section->VirtualAddress)
|
||||
* - the file section size (Section->SizeOfRawData), which is
|
||||
* a multiple of OptHdr->FileAlignment. Only useful for image
|
||||
* validation, not really useful for iteration bounds.
|
||||
* - the file address (Section->PointerToRawData)
|
||||
* - a bunch of stuff we don't use that's 0 in our binaries usually
|
||||
* - Flags (Section->Characteristics)
|
||||
*
|
||||
* and then the thing that's actually at the file address is an array
|
||||
* of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind
|
||||
* them. The SizeOfBlock field of this structure includes the
|
||||
* structure itself, and adding it to that structure's address will
|
||||
* yield the next entry in the array.
|
||||
*/
|
||||
RelocBase = ImageAddress(orig, size, Section->PointerToRawData);
|
||||
/* RelocBaseEnd here is the address of the first entry /past/ the
|
||||
* table. */
|
||||
RelocBaseEnd = ImageAddress(orig, size, Section->PointerToRawData +
|
||||
Section->Misc.VirtualSize);
|
||||
|
||||
if (!RelocBase && !RelocBaseEnd)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
if (!RelocBase || !RelocBaseEnd) {
|
||||
perror(L"Reloc table overflows binary\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
Adjust = (UINTN)data - context->ImageAddress;
|
||||
|
||||
if (Adjust == 0)
|
||||
return EFI_SUCCESS;
|
||||
|
||||
while (RelocBase < RelocBaseEnd) {
|
||||
Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION));
|
||||
|
||||
if (RelocBase->SizeOfBlock == 0) {
|
||||
perror(L"Reloc %d block size 0 is invalid\n", n);
|
||||
return EFI_UNSUPPORTED;
|
||||
} else if (RelocBase->SizeOfBlock > context->RelocDir->Size) {
|
||||
perror(L"Reloc %d block size %d greater than reloc dir"
|
||||
"size %d, which is invalid\n", n,
|
||||
RelocBase->SizeOfBlock,
|
||||
context->RelocDir->Size);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock);
|
||||
if ((void *)RelocEnd < orig || (void *)RelocEnd > ImageEnd) {
|
||||
perror(L"Reloc %d entry overflows binary\n", n);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress);
|
||||
if (!FixupBase) {
|
||||
perror(L"Reloc %d Invalid fixupbase\n", n);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
while (Reloc < RelocEnd) {
|
||||
Fixup = FixupBase + (*Reloc & 0xFFF);
|
||||
switch ((*Reloc) >> 12) {
|
||||
case EFI_IMAGE_REL_BASED_ABSOLUTE:
|
||||
break;
|
||||
|
||||
case EFI_IMAGE_REL_BASED_HIGH:
|
||||
Fixup16 = (UINT16 *) Fixup;
|
||||
*Fixup16 = (UINT16) (*Fixup16 + ((UINT16) ((UINT32) Adjust >> 16)));
|
||||
break;
|
||||
|
||||
case EFI_IMAGE_REL_BASED_LOW:
|
||||
Fixup16 = (UINT16 *) Fixup;
|
||||
*Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust);
|
||||
break;
|
||||
|
||||
case EFI_IMAGE_REL_BASED_HIGHLOW:
|
||||
Fixup32 = (UINT32 *) Fixup;
|
||||
*Fixup32 = *Fixup32 + (UINT32) Adjust;
|
||||
break;
|
||||
|
||||
case EFI_IMAGE_REL_BASED_DIR64:
|
||||
Fixup64 = (UINT64 *) Fixup;
|
||||
*Fixup64 = *Fixup64 + (UINT64) Adjust;
|
||||
break;
|
||||
|
||||
default:
|
||||
perror(L"Reloc %d Unknown relocation\n", n);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
Reloc += 1;
|
||||
}
|
||||
RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd;
|
||||
n++;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static void
|
||||
drain_openssl_errors(void)
|
||||
{
|
||||
@ -709,8 +465,7 @@ static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert,
|
||||
/*
|
||||
* Check whether we're in Secure Boot and user mode
|
||||
*/
|
||||
|
||||
static BOOLEAN secure_mode (void)
|
||||
BOOLEAN secure_mode (void)
|
||||
{
|
||||
static int first = 1;
|
||||
if (user_insecure_mode)
|
||||
@ -740,288 +495,6 @@ static BOOLEAN secure_mode (void)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#define check_size_line(data, datasize_in, hashbase, hashsize, l) ({ \
|
||||
if ((unsigned long)hashbase > \
|
||||
(unsigned long)data + datasize_in) { \
|
||||
efi_status = EFI_INVALID_PARAMETER; \
|
||||
perror(L"shim.c:%d Invalid hash base 0x%016x\n", l, \
|
||||
hashbase); \
|
||||
goto done; \
|
||||
} \
|
||||
if ((unsigned long)hashbase + hashsize > \
|
||||
(unsigned long)data + datasize_in) { \
|
||||
efi_status = EFI_INVALID_PARAMETER; \
|
||||
perror(L"shim.c:%d Invalid hash size 0x%016x\n", l, \
|
||||
hashsize); \
|
||||
goto done; \
|
||||
} \
|
||||
})
|
||||
#define check_size(d, ds, h, hs) check_size_line(d, ds, h, hs, __LINE__)
|
||||
|
||||
/*
|
||||
* Calculate the SHA1 and SHA256 hashes of a binary
|
||||
*/
|
||||
|
||||
static EFI_STATUS generate_hash (char *data, unsigned int datasize_in,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
UINT8 *sha256hash, UINT8 *sha1hash)
|
||||
|
||||
{
|
||||
unsigned int sha256ctxsize, sha1ctxsize;
|
||||
unsigned int size = datasize_in;
|
||||
void *sha256ctx = NULL, *sha1ctx = NULL;
|
||||
char *hashbase;
|
||||
unsigned int hashsize;
|
||||
unsigned int SumOfBytesHashed, SumOfSectionBytes;
|
||||
unsigned int index, pos;
|
||||
unsigned int datasize;
|
||||
EFI_IMAGE_SECTION_HEADER *Section;
|
||||
EFI_IMAGE_SECTION_HEADER *SectionHeader = NULL;
|
||||
EFI_STATUS efi_status = EFI_SUCCESS;
|
||||
EFI_IMAGE_DOS_HEADER *DosHdr = (void *)data;
|
||||
unsigned int PEHdr_offset = 0;
|
||||
|
||||
size = datasize = datasize_in;
|
||||
|
||||
if (datasize <= sizeof (*DosHdr) ||
|
||||
DosHdr->e_magic != EFI_IMAGE_DOS_SIGNATURE) {
|
||||
perror(L"Invalid signature\n");
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
PEHdr_offset = DosHdr->e_lfanew;
|
||||
|
||||
sha256ctxsize = Sha256GetContextSize();
|
||||
sha256ctx = AllocatePool(sha256ctxsize);
|
||||
|
||||
sha1ctxsize = Sha1GetContextSize();
|
||||
sha1ctx = AllocatePool(sha1ctxsize);
|
||||
|
||||
if (!sha256ctx || !sha1ctx) {
|
||||
perror(L"Unable to allocate memory for hash context\n");
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
if (!Sha256Init(sha256ctx) || !Sha1Init(sha1ctx)) {
|
||||
perror(L"Unable to initialise hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Hash start to checksum */
|
||||
hashbase = data;
|
||||
hashsize = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum -
|
||||
hashbase;
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Hash post-checksum to start of certificate table */
|
||||
hashbase = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum +
|
||||
sizeof (int);
|
||||
hashsize = (char *)context->SecDir - hashbase;
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Hash end of certificate table to end of image header */
|
||||
EFI_IMAGE_DATA_DIRECTORY *dd = context->SecDir + 1;
|
||||
hashbase = (char *)dd;
|
||||
hashsize = context->SizeOfHeaders - (unsigned long)((char *)dd - data);
|
||||
if (hashsize > datasize_in) {
|
||||
perror(L"Data Directory size %d is invalid\n", hashsize);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Sort sections */
|
||||
SumOfBytesHashed = context->SizeOfHeaders;
|
||||
|
||||
/* Validate section locations and sizes */
|
||||
for (index = 0, SumOfSectionBytes = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) {
|
||||
EFI_IMAGE_SECTION_HEADER *SectionPtr;
|
||||
|
||||
/* Validate SectionPtr is within image */
|
||||
SectionPtr = ImageAddress(data, datasize,
|
||||
PEHdr_offset +
|
||||
sizeof (UINT32) +
|
||||
sizeof (EFI_IMAGE_FILE_HEADER) +
|
||||
context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader +
|
||||
(index * sizeof(*SectionPtr)));
|
||||
if (!SectionPtr) {
|
||||
perror(L"Malformed section %d\n", index);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
/* Validate section size is within image. */
|
||||
if (SectionPtr->SizeOfRawData >
|
||||
datasize - SumOfBytesHashed - SumOfSectionBytes) {
|
||||
perror(L"Malformed section %d size\n", index);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
SumOfSectionBytes += SectionPtr->SizeOfRawData;
|
||||
}
|
||||
|
||||
SectionHeader = (EFI_IMAGE_SECTION_HEADER *) AllocateZeroPool (sizeof (EFI_IMAGE_SECTION_HEADER) * context->PEHdr->Pe32.FileHeader.NumberOfSections);
|
||||
if (SectionHeader == NULL) {
|
||||
perror(L"Unable to allocate section header\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Already validated above */
|
||||
Section = ImageAddress(data, datasize,
|
||||
PEHdr_offset +
|
||||
sizeof (UINT32) +
|
||||
sizeof (EFI_IMAGE_FILE_HEADER) +
|
||||
context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader);
|
||||
/* But check it again just for better error messaging, and so
|
||||
* clang-analyzer doesn't get confused. */
|
||||
if (Section == NULL) {
|
||||
uint64_t addr;
|
||||
|
||||
addr = PEHdr_offset + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)
|
||||
+ context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader;
|
||||
perror(L"Malformed file header.\n");
|
||||
perror(L"Image address for Section 0 is 0x%016llx\n", addr);
|
||||
perror(L"File size is 0x%016llx\n", datasize);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Sort the section headers */
|
||||
for (index = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) {
|
||||
pos = index;
|
||||
while ((pos > 0) && (Section->PointerToRawData < SectionHeader[pos - 1].PointerToRawData)) {
|
||||
CopyMem (&SectionHeader[pos], &SectionHeader[pos - 1], sizeof (EFI_IMAGE_SECTION_HEADER));
|
||||
pos--;
|
||||
}
|
||||
CopyMem (&SectionHeader[pos], Section, sizeof (EFI_IMAGE_SECTION_HEADER));
|
||||
Section += 1;
|
||||
}
|
||||
|
||||
/* Hash the sections */
|
||||
for (index = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++) {
|
||||
Section = &SectionHeader[index];
|
||||
if (Section->SizeOfRawData == 0) {
|
||||
continue;
|
||||
}
|
||||
hashbase = ImageAddress(data, size, Section->PointerToRawData);
|
||||
|
||||
if (!hashbase) {
|
||||
perror(L"Malformed section header\n");
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Verify hashsize within image. */
|
||||
if (Section->SizeOfRawData >
|
||||
datasize - Section->PointerToRawData) {
|
||||
perror(L"Malformed section raw size %d\n", index);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
hashsize = (unsigned int) Section->SizeOfRawData;
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
SumOfBytesHashed += Section->SizeOfRawData;
|
||||
}
|
||||
|
||||
/* Hash all remaining data up to SecDir if SecDir->Size is not 0 */
|
||||
if (datasize > SumOfBytesHashed && context->SecDir->Size) {
|
||||
hashbase = data + SumOfBytesHashed;
|
||||
hashsize = datasize - context->SecDir->Size - SumOfBytesHashed;
|
||||
|
||||
if ((datasize - SumOfBytesHashed < context->SecDir->Size) ||
|
||||
(SumOfBytesHashed + hashsize != context->SecDir->VirtualAddress)) {
|
||||
perror(L"Malformed binary after Attribute Certificate Table\n");
|
||||
console_print(L"datasize: %u SumOfBytesHashed: %u SecDir->Size: %lu\n",
|
||||
datasize, SumOfBytesHashed, context->SecDir->Size);
|
||||
console_print(L"hashsize: %u SecDir->VirtualAddress: 0x%08lx\n",
|
||||
hashsize, context->SecDir->VirtualAddress);
|
||||
efi_status = EFI_INVALID_PARAMETER;
|
||||
goto done;
|
||||
}
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if 1
|
||||
}
|
||||
#else // we have to migrate to doing this later :/
|
||||
SumOfBytesHashed += hashsize;
|
||||
}
|
||||
|
||||
/* Hash all remaining data */
|
||||
if (datasize > SumOfBytesHashed) {
|
||||
hashbase = data + SumOfBytesHashed;
|
||||
hashsize = datasize - SumOfBytesHashed;
|
||||
|
||||
check_size(data, datasize_in, hashbase, hashsize);
|
||||
|
||||
if (!(Sha256Update(sha256ctx, hashbase, hashsize)) ||
|
||||
!(Sha1Update(sha1ctx, hashbase, hashsize))) {
|
||||
perror(L"Unable to generate hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
SumOfBytesHashed += hashsize;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!(Sha256Final(sha256ctx, sha256hash)) ||
|
||||
!(Sha1Final(sha1ctx, sha1hash))) {
|
||||
perror(L"Unable to finalise hash\n");
|
||||
efi_status = EFI_OUT_OF_RESOURCES;
|
||||
goto done;
|
||||
}
|
||||
|
||||
dprint(L"sha1 authenticode hash:\n");
|
||||
dhexdumpat(sha1hash, SHA1_DIGEST_SIZE, 0);
|
||||
dprint(L"sha256 authenticode hash:\n");
|
||||
dhexdumpat(sha256hash, SHA256_DIGEST_SIZE, 0);
|
||||
|
||||
done:
|
||||
if (SectionHeader)
|
||||
FreePool(SectionHeader);
|
||||
if (sha1ctx)
|
||||
FreePool(sha1ctx);
|
||||
if (sha256ctx)
|
||||
FreePool(sha256ctx);
|
||||
|
||||
return efi_status;
|
||||
}
|
||||
|
||||
static EFI_STATUS
|
||||
verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig,
|
||||
UINT8 *sha256hash, UINT8 *sha1hash)
|
||||
@ -1122,9 +595,10 @@ verify_one_signature(WIN_CERTIFICATE_EFI_PKCS *sig,
|
||||
/*
|
||||
* Check that the signature is valid and matches the binary
|
||||
*/
|
||||
static EFI_STATUS verify_buffer (char *data, int datasize,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
UINT8 *sha256hash, UINT8 *sha1hash)
|
||||
EFI_STATUS
|
||||
verify_buffer (char *data, int datasize,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
UINT8 *sha256hash, UINT8 *sha1hash)
|
||||
{
|
||||
EFI_STATUS ret_efi_status;
|
||||
size_t size = datasize;
|
||||
@ -1257,391 +731,6 @@ static EFI_STATUS verify_buffer (char *data, int datasize,
|
||||
return ret_efi_status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the binary header and grab appropriate information from it
|
||||
*/
|
||||
static EFI_STATUS read_header(void *data, unsigned int datasize,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context)
|
||||
{
|
||||
EFI_IMAGE_DOS_HEADER *DosHdr = data;
|
||||
EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data;
|
||||
unsigned long HeaderWithoutDataDir, SectionHeaderOffset, OptHeaderSize;
|
||||
unsigned long FileAlignment = 0;
|
||||
|
||||
if (datasize < sizeof (PEHdr->Pe32)) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE)
|
||||
PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew);
|
||||
|
||||
if (!image_is_loadable(PEHdr)) {
|
||||
perror(L"Platform does not support this image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (image_is_64_bit(PEHdr)) {
|
||||
context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes;
|
||||
context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders;
|
||||
context->ImageSize = PEHdr->Pe32Plus.OptionalHeader.SizeOfImage;
|
||||
context->SectionAlignment = PEHdr->Pe32Plus.OptionalHeader.SectionAlignment;
|
||||
FileAlignment = PEHdr->Pe32Plus.OptionalHeader.FileAlignment;
|
||||
OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER64);
|
||||
} else {
|
||||
context->NumberOfRvaAndSizes = PEHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes;
|
||||
context->SizeOfHeaders = PEHdr->Pe32.OptionalHeader.SizeOfHeaders;
|
||||
context->ImageSize = (UINT64)PEHdr->Pe32.OptionalHeader.SizeOfImage;
|
||||
context->SectionAlignment = PEHdr->Pe32.OptionalHeader.SectionAlignment;
|
||||
FileAlignment = PEHdr->Pe32.OptionalHeader.FileAlignment;
|
||||
OptHeaderSize = sizeof(EFI_IMAGE_OPTIONAL_HEADER32);
|
||||
}
|
||||
|
||||
if (FileAlignment % 2 != 0) {
|
||||
perror(L"File Alignment is invalid (%d)\n", FileAlignment);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (FileAlignment == 0)
|
||||
FileAlignment = 0x200;
|
||||
if (context->SectionAlignment == 0)
|
||||
context->SectionAlignment = PAGE_SIZE;
|
||||
if (context->SectionAlignment < FileAlignment)
|
||||
context->SectionAlignment = FileAlignment;
|
||||
|
||||
context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections;
|
||||
|
||||
if (EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES < context->NumberOfRvaAndSizes) {
|
||||
perror(L"Image header too small\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
HeaderWithoutDataDir = OptHeaderSize
|
||||
- sizeof (EFI_IMAGE_DATA_DIRECTORY) * EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES;
|
||||
if (((UINT32)PEHdr->Pe32.FileHeader.SizeOfOptionalHeader - HeaderWithoutDataDir) !=
|
||||
context->NumberOfRvaAndSizes * sizeof (EFI_IMAGE_DATA_DIRECTORY)) {
|
||||
perror(L"Image header overflows data directory\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
SectionHeaderOffset = DosHdr->e_lfanew
|
||||
+ sizeof (UINT32)
|
||||
+ sizeof (EFI_IMAGE_FILE_HEADER)
|
||||
+ PEHdr->Pe32.FileHeader.SizeOfOptionalHeader;
|
||||
if (((UINT32)context->ImageSize - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER
|
||||
<= context->NumberOfSections) {
|
||||
perror(L"Image sections overflow image size\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if ((context->SizeOfHeaders - SectionHeaderOffset) / EFI_IMAGE_SIZEOF_SECTION_HEADER
|
||||
< (UINT32)context->NumberOfSections) {
|
||||
perror(L"Image sections overflow section headers\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if ((((UINT8 *)PEHdr - (UINT8 *)data) + sizeof(EFI_IMAGE_OPTIONAL_HEADER_UNION)) > datasize) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) {
|
||||
perror(L"Unsupported image type\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) {
|
||||
perror(L"Unsupported image - Relocations have been stripped\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
context->PEHdr = PEHdr;
|
||||
|
||||
if (image_is_64_bit(PEHdr)) {
|
||||
context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase;
|
||||
context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint;
|
||||
context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
context->SecDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||||
} else {
|
||||
context->ImageAddress = PEHdr->Pe32.OptionalHeader.ImageBase;
|
||||
context->EntryPoint = PEHdr->Pe32.OptionalHeader.AddressOfEntryPoint;
|
||||
context->RelocDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
context->SecDir = &PEHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY];
|
||||
}
|
||||
|
||||
context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER));
|
||||
|
||||
if (context->ImageSize < context->SizeOfHeaders) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if ((unsigned long)((UINT8 *)context->SecDir - (UINT8 *)data) >
|
||||
(datasize - sizeof(EFI_IMAGE_DATA_DIRECTORY))) {
|
||||
perror(L"Invalid image\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Once the image has been loaded it needs to be validated and relocated
|
||||
*/
|
||||
static EFI_STATUS handle_image (void *data, unsigned int datasize,
|
||||
EFI_LOADED_IMAGE *li,
|
||||
EFI_IMAGE_ENTRY_POINT *entry_point,
|
||||
EFI_PHYSICAL_ADDRESS *alloc_address,
|
||||
UINTN *alloc_pages)
|
||||
{
|
||||
EFI_STATUS efi_status;
|
||||
char *buffer;
|
||||
int i;
|
||||
EFI_IMAGE_SECTION_HEADER *Section;
|
||||
char *base, *end;
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT context;
|
||||
unsigned int alignment, alloc_size;
|
||||
int found_entry_point = 0;
|
||||
UINT8 sha1hash[SHA1_DIGEST_SIZE];
|
||||
UINT8 sha256hash[SHA256_DIGEST_SIZE];
|
||||
|
||||
/*
|
||||
* The binary header contains relevant context and section pointers
|
||||
*/
|
||||
efi_status = read_header(data, datasize, &context);
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
perror(L"Failed to read header: %r\n", efi_status);
|
||||
return efi_status;
|
||||
}
|
||||
|
||||
/*
|
||||
* We only need to verify the binary if we're in secure mode
|
||||
*/
|
||||
efi_status = generate_hash(data, datasize, &context, sha256hash,
|
||||
sha1hash);
|
||||
if (EFI_ERROR(efi_status))
|
||||
return efi_status;
|
||||
|
||||
/* Measure the binary into the TPM */
|
||||
#ifdef REQUIRE_TPM
|
||||
efi_status =
|
||||
#endif
|
||||
tpm_log_pe((EFI_PHYSICAL_ADDRESS)(UINTN)data, datasize,
|
||||
(EFI_PHYSICAL_ADDRESS)(UINTN)context.ImageAddress,
|
||||
li->FilePath, sha1hash, 4);
|
||||
#ifdef REQUIRE_TPM
|
||||
if (efi_status != EFI_SUCCESS) {
|
||||
return efi_status;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (secure_mode ()) {
|
||||
efi_status = verify_buffer(data, datasize, &context,
|
||||
sha256hash, sha1hash);
|
||||
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
if (verbose)
|
||||
console_print(L"Verification failed: %r\n", efi_status);
|
||||
else
|
||||
console_error(L"Verification failed", efi_status);
|
||||
return efi_status;
|
||||
} else {
|
||||
if (verbose)
|
||||
console_print(L"Verification succeeded\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* The spec says, uselessly, of SectionAlignment:
|
||||
* =====
|
||||
* The alignment (in bytes) of sections when they are loaded into
|
||||
* memory. It must be greater than or equal to FileAlignment. The
|
||||
* default is the page size for the architecture.
|
||||
* =====
|
||||
* Which doesn't tell you whose responsibility it is to enforce the
|
||||
* "default", or when. It implies that the value in the field must
|
||||
* be > FileAlignment (also poorly defined), but it appears visual
|
||||
* studio will happily write 512 for FileAlignment (its default) and
|
||||
* 0 for SectionAlignment, intending to imply PAGE_SIZE.
|
||||
*
|
||||
* We only support one page size, so if it's zero, nerf it to 4096.
|
||||
*/
|
||||
alignment = context.SectionAlignment;
|
||||
if (!alignment)
|
||||
alignment = 4096;
|
||||
|
||||
alloc_size = ALIGN_VALUE(context.ImageSize + context.SectionAlignment,
|
||||
PAGE_SIZE);
|
||||
*alloc_pages = alloc_size / PAGE_SIZE;
|
||||
|
||||
efi_status = gBS->AllocatePages(AllocateAnyPages, EfiLoaderCode,
|
||||
*alloc_pages, alloc_address);
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
perror(L"Failed to allocate image buffer\n");
|
||||
return EFI_OUT_OF_RESOURCES;
|
||||
}
|
||||
|
||||
buffer = (void *)ALIGN_VALUE((unsigned long)*alloc_address, alignment);
|
||||
|
||||
CopyMem(buffer, data, context.SizeOfHeaders);
|
||||
|
||||
*entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint);
|
||||
if (!*entry_point) {
|
||||
perror(L"Entry point is invalid\n");
|
||||
gBS->FreePages(*alloc_address, *alloc_pages);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
char *RelocBase, *RelocBaseEnd;
|
||||
/*
|
||||
* These are relative virtual addresses, so we have to check them
|
||||
* against the image size, not the data size.
|
||||
*/
|
||||
RelocBase = ImageAddress(buffer, context.ImageSize,
|
||||
context.RelocDir->VirtualAddress);
|
||||
/*
|
||||
* RelocBaseEnd here is the address of the last byte of the table
|
||||
*/
|
||||
RelocBaseEnd = ImageAddress(buffer, context.ImageSize,
|
||||
context.RelocDir->VirtualAddress +
|
||||
context.RelocDir->Size - 1);
|
||||
|
||||
EFI_IMAGE_SECTION_HEADER *RelocSection = NULL;
|
||||
|
||||
/*
|
||||
* Copy the executable's sections to their desired offsets
|
||||
*/
|
||||
Section = context.FirstSection;
|
||||
for (i = 0; i < context.NumberOfSections; i++, Section++) {
|
||||
/* Don't try to copy discardable sections with zero size */
|
||||
if ((Section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE) &&
|
||||
!Section->Misc.VirtualSize)
|
||||
continue;
|
||||
|
||||
base = ImageAddress (buffer, context.ImageSize,
|
||||
Section->VirtualAddress);
|
||||
end = ImageAddress (buffer, context.ImageSize,
|
||||
Section->VirtualAddress
|
||||
+ Section->Misc.VirtualSize - 1);
|
||||
|
||||
if (end < base) {
|
||||
perror(L"Section %d has negative size\n", i);
|
||||
gBS->FreePages(*alloc_address, *alloc_pages);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (Section->VirtualAddress <= context.EntryPoint &&
|
||||
(Section->VirtualAddress + Section->SizeOfRawData - 1)
|
||||
> context.EntryPoint)
|
||||
found_entry_point++;
|
||||
|
||||
/* We do want to process .reloc, but it's often marked
|
||||
* discardable, so we don't want to memcpy it. */
|
||||
if (CompareMem(Section->Name, ".reloc\0\0", 8) == 0) {
|
||||
if (RelocSection) {
|
||||
perror(L"Image has multiple relocation sections\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
/* If it has nonzero sizes, and our bounds check
|
||||
* made sense, and the VA and size match RelocDir's
|
||||
* versions, then we believe in this section table. */
|
||||
if (Section->SizeOfRawData &&
|
||||
Section->Misc.VirtualSize &&
|
||||
base && end &&
|
||||
RelocBase == base &&
|
||||
RelocBaseEnd == end) {
|
||||
RelocSection = Section;
|
||||
}
|
||||
}
|
||||
|
||||
if (Section->Characteristics & EFI_IMAGE_SCN_MEM_DISCARDABLE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!base) {
|
||||
perror(L"Section %d has invalid base address\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (!end) {
|
||||
perror(L"Section %d has zero size\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (!(Section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) &&
|
||||
(Section->VirtualAddress < context.SizeOfHeaders ||
|
||||
Section->PointerToRawData < context.SizeOfHeaders)) {
|
||||
perror(L"Section %d is inside image headers\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (Section->Characteristics & EFI_IMAGE_SCN_CNT_UNINITIALIZED_DATA) {
|
||||
ZeroMem(base, Section->Misc.VirtualSize);
|
||||
} else {
|
||||
if (Section->PointerToRawData < context.SizeOfHeaders) {
|
||||
perror(L"Section %d is inside image headers\n", i);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (Section->SizeOfRawData > 0)
|
||||
CopyMem(base, data + Section->PointerToRawData,
|
||||
Section->SizeOfRawData);
|
||||
|
||||
if (Section->SizeOfRawData < Section->Misc.VirtualSize)
|
||||
ZeroMem(base + Section->SizeOfRawData,
|
||||
Section->Misc.VirtualSize - Section->SizeOfRawData);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) {
|
||||
perror(L"Image has no relocation entry\n");
|
||||
FreePool(buffer);
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (context.RelocDir->Size && RelocSection) {
|
||||
/*
|
||||
* Run the relocation fixups
|
||||
*/
|
||||
efi_status = relocate_coff(&context, RelocSection, data,
|
||||
buffer);
|
||||
|
||||
if (EFI_ERROR(efi_status)) {
|
||||
perror(L"Relocation failed: %r\n", efi_status);
|
||||
FreePool(buffer);
|
||||
return efi_status;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* grub needs to know its location and size in memory, so fix up
|
||||
* the loaded image protocol values
|
||||
*/
|
||||
li->ImageBase = buffer;
|
||||
li->ImageSize = context.ImageSize;
|
||||
|
||||
/* Pass the load options to the second stage loader */
|
||||
if ( load_options ) {
|
||||
li->LoadOptions = load_options;
|
||||
li->LoadOptionsSize = load_options_size;
|
||||
}
|
||||
|
||||
if (!found_entry_point) {
|
||||
perror(L"Entry point is not within sections\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (found_entry_point > 1) {
|
||||
perror(L"%d sections contain entry point\n");
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static int
|
||||
should_use_fallback(EFI_HANDLE image_handle)
|
||||
{
|
||||
|
11
shim.h
11
shim.h
@ -29,6 +29,7 @@
|
||||
#undef uefi_call_wrapper
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define nonnull(...) __attribute__((__nonnull__(__VA_ARGS__)))
|
||||
|
||||
@ -135,6 +136,7 @@
|
||||
#include "include/netboot.h"
|
||||
#include "include/passwordcrypt.h"
|
||||
#include "include/peimage.h"
|
||||
#include "include/pe.h"
|
||||
#include "include/replacements.h"
|
||||
#if defined(OVERRIDE_SECURITY_POLICY)
|
||||
#include "include/security_policy.h"
|
||||
@ -204,6 +206,15 @@ extern UINT8 *build_cert;
|
||||
extern UINT8 user_insecure_mode;
|
||||
extern UINT8 ignore_db;
|
||||
extern UINT8 in_protocol;
|
||||
extern void *load_options;
|
||||
extern UINT32 load_options_size;
|
||||
|
||||
BOOLEAN secure_mode (void);
|
||||
|
||||
EFI_STATUS
|
||||
verify_buffer (char *data, int datasize,
|
||||
PE_COFF_LOADER_IMAGE_CONTEXT *context,
|
||||
UINT8 *sha256hash, UINT8 *sha1hash);
|
||||
|
||||
#define perror_(file, line, func, fmt, ...) ({ \
|
||||
UINTN __perror_ret = 0; \
|
||||
|
Loading…
Reference in New Issue
Block a user