// SPDX-License-Identifier: BSD-2-Clause-Patent /* * pe.c - helper functions for pe binaries. * Copyright Peter Jones */ #include "shim.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Perform basic bounds checking of the intra-image pointers */ void * ImageAddress (void *image, uint64_t size, uint64_t address) { /* ensure our local pointer isn't bigger than our size */ if (address > size) return NULL; /* Insure our math won't overflow */ if (UINT64_MAX - address < (uint64_t)(intptr_t)image) return NULL; /* return the absolute pointer */ return image + address; } /* * Perform the actual relocation */ EFI_STATUS relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, EFI_IMAGE_SECTION_HEADER *Section, void *orig, void *data) { EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd; UINT64 Adjust; UINT16 *Reloc, *RelocEnd; char *Fixup, *FixupBase; UINT16 *Fixup16; UINT32 *Fixup32; UINT64 *Fixup64; int size = context->ImageSize; void *ImageEnd = (char *)orig + size; int n = 0; /* Alright, so here's how this works: * * context->RelocDir gives us two things: * - the VA the table of base relocation blocks are (maybe) to be * mapped at (RelocDir->VirtualAddress) * - the virtual size (RelocDir->Size) * * The .reloc section (Section here) gives us some other things: * - the name! kind of. (Section->Name) * - the virtual size (Section->VirtualSize), which should be the same * as RelocDir->Size * - the virtual address (Section->VirtualAddress) * - the file section size (Section->SizeOfRawData), which is * a multiple of OptHdr->FileAlignment. Only useful for image * validation, not really useful for iteration bounds. * - the file address (Section->PointerToRawData) * - a bunch of stuff we don't use that's 0 in our binaries usually * - Flags (Section->Characteristics) * * and then the thing that's actually at the file address is an array * of EFI_IMAGE_BASE_RELOCATION structs with some values packed behind * them. The SizeOfBlock field of this structure includes the * structure itself, and adding it to that structure's address will * yield the next entry in the array. */ RelocBase = ImageAddress(orig, size, Section->PointerToRawData); /* RelocBaseEnd here is the address of the first entry /past/ the * table. */ RelocBaseEnd = ImageAddress(orig, size, Section->PointerToRawData + 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__) 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 */ 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; /* * XXX Do we need this here, or is it already done in all cases? */ if (context->NumberOfSections == 0 || context->FirstSection == NULL) { uint16_t opthdrsz; uint64_t addr; uint16_t nsections; EFI_IMAGE_SECTION_HEADER *section0, *sectionN; nsections = context->PEHdr->Pe32.FileHeader.NumberOfSections; opthdrsz = context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader; /* Validate section0 is within image */ addr = PEHdr_offset + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER) + opthdrsz; section0 = ImageAddress(data, datasize, addr); if (!section0) { perror(L"Malformed file header.\n"); perror(L"Image address for Section Header 0 is 0x%016llx\n", addr); perror(L"File size is 0x%016llx\n", datasize); efi_status = EFI_INVALID_PARAMETER; goto done; } /* Validate sectionN is within image */ addr += (uint64_t)(intptr_t)§ion0[nsections-1] - (uint64_t)(intptr_t)section0; sectionN = ImageAddress(data, datasize, addr); if (!sectionN) { perror(L"Malformed file header.\n"); perror(L"Image address for Section Header %d is 0x%016llx\n", nsections - 1, addr); perror(L"File size is 0x%016llx\n", datasize); efi_status = EFI_INVALID_PARAMETER; goto done; } context->NumberOfSections = nsections; context->FirstSection = section0; } /* * Allocate a new section table so we can sort them without * modifying the image. */ SectionHeader = AllocateZeroPool (sizeof (EFI_IMAGE_SECTION_HEADER) * context->NumberOfSections); if (SectionHeader == NULL) { perror(L"Unable to allocate section header\n"); efi_status = EFI_OUT_OF_RESOURCES; goto done; } /* * Validate section locations and sizes, and sort the table into * our newly allocated header table */ SumOfSectionBytes = 0; Section = context->FirstSection; for (index = 0; index < context->NumberOfSections; index++) { EFI_IMAGE_SECTION_HEADER *SectionPtr; char *base; size_t size; efi_status = get_section_vma(index, data, datasize, context, &base, &size, &SectionPtr); if (efi_status == EFI_NOT_FOUND) break; if (EFI_ERROR(efi_status)) { perror(L"Malformed section header\n"); 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; 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->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; } EFI_STATUS handle_sbat(char *SBATBase, size_t SBATSize) { unsigned int i; EFI_STATUS efi_status; size_t n; struct sbat_section_entry **entries = NULL; char *sbat_data; size_t sbat_size; if (list_empty(&sbat_var)) return EFI_SUCCESS; if (SBATBase == NULL || SBATSize == 0) { dprint(L"No .sbat section data\n"); return EFI_SECURITY_VIOLATION; } sbat_size = SBATSize + 1; sbat_data = AllocatePool(sbat_size); if (!sbat_data) { console_print(L"Failed to allocate .sbat section buffer\n"); return EFI_OUT_OF_RESOURCES; } CopyMem(sbat_data, SBATBase, SBATSize); sbat_data[SBATSize] = '\0'; efi_status = parse_sbat_section(sbat_data, sbat_size, &n, &entries); if (EFI_ERROR(efi_status)) { perror(L"Could not parse .sbat section data: %r\n", efi_status); goto err; } dprint(L"SBAT section data\n"); for (i = 0; i < n; i++) { dprint(L"%a, %a, %a, %a, %a, %a\n", entries[i]->component_name, entries[i]->component_generation, entries[i]->vendor_name, entries[i]->vendor_package_name, entries[i]->vendor_version, entries[i]->vendor_url); } efi_status = verify_sbat(n, entries); cleanup_sbat_section_entries(n, entries); err: FreePool(sbat_data); return efi_status; } /* * 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 /* 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; char *SBATBase = NULL; size_t SBATSize = 0; /* * 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; } } else if (CompareMem(Section->Name, ".sbat\0\0\0", 8) == 0) { if (SBATBase || SBATSize) { perror(L"Image has multiple SBAT sections\n"); return EFI_UNSUPPORTED; } if (Section->NumberOfRelocations != 0 || Section->PointerToRelocations != 0) { perror(L"SBAT section has relocations\n"); return EFI_UNSUPPORTED; } /* The virtual size corresponds to the size of the SBAT * metadata and isn't necessarily a multiple of the file * alignment. The on-disk size is a multiple of the file * alignment and is zero padded. Make sure that the * on-disk size is at least as large as virtual size, * and ignore the section if it isn't. */ if (Section->SizeOfRawData && Section->SizeOfRawData >= Section->Misc.VirtualSize && base && end) { SBATBase = base; /* +1 because of size vs last byte location */ SBATSize = end - base + 1; } } 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 (secure_mode ()) { efi_status = handle_sbat(SBATBase, SBATSize); if (!EFI_ERROR(efi_status)) 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"); } } 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