diff options
Diffstat (limited to 'shim.c')
| -rw-r--r-- | shim.c | 943 |
1 files changed, 943 insertions, 0 deletions
@@ -0,0 +1,943 @@ +/* + * shim - trivial UEFI first-stage bootloader + * + * Copyright 2012 Red Hat, Inc <mjg@redhat.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Significant portions of this code are derived from Tianocore + * (http://tianocore.sf.net) and are Copyright 2009-2012 Intel + * Corporation. + */ + +#include <efi.h> +#include <efilib.h> +#include <Library/BaseCryptLib.h> +#include "PeImage.h" +#include "shim.h" +#include "signature.h" + +#define SECOND_STAGE L"\\grub.efi" + +static EFI_SYSTEM_TABLE *systab; +static EFI_STATUS (EFIAPI *entry_point) (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table); + +/* + * The vendor certificate used for validating the second stage loader + */ + +#include "cert.h" + +#define EFI_IMAGE_SECURITY_DATABASE_GUID { 0xd719b2cb, 0x3d3a, 0x4596, { 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f }} + +typedef enum { + DATA_FOUND, + DATA_NOT_FOUND, + VAR_NOT_FOUND +} CHECK_STATUS; + +static EFI_STATUS get_variable (CHAR16 *name, EFI_GUID guid, + UINTN *size, void **buffer) +{ + EFI_STATUS efi_status; + UINT32 attributes; + char allocate = !(*size); + + efi_status = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, + &attributes, size, buffer); + + if (efi_status != EFI_BUFFER_TOO_SMALL || !allocate) { + return efi_status; + } + + if (allocate) + *buffer = AllocatePool(*size); + + if (!*buffer) { + Print(L"Unable to allocate variable buffer\n"); + return EFI_OUT_OF_RESOURCES; + } + + efi_status = uefi_call_wrapper(RT->GetVariable, 5, name, &guid, + &attributes, size, *buffer); + + return efi_status; +} + +/* + * Perform basic bounds checking of the intra-image pointers + */ +static void *ImageAddress (void *image, int size, unsigned int address) +{ + if (address > size) + return NULL; + + return image + address; +} + +/* + * Perform the actual relocation + */ +static EFI_STATUS relocate_coff (PE_COFF_LOADER_IMAGE_CONTEXT *context, + void *data) +{ + EFI_IMAGE_BASE_RELOCATION *RelocBase, *RelocBaseEnd; + UINT64 Adjust; + UINT16 *Reloc, *RelocEnd; + char *Fixup, *FixupBase, *FixupData = NULL; + UINT16 *Fixup16; + UINT32 *Fixup32; + UINT64 *Fixup64; + int size = context->ImageSize; + void *ImageEnd = (char *)data + size; + + context->PEHdr->Pe32Plus.OptionalHeader.ImageBase = (UINT64)data; + + if (context->NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC) { + Print(L"Image has no relocation entry\n"); + return EFI_UNSUPPORTED; + } + + RelocBase = ImageAddress(data, size, context->RelocDir->VirtualAddress); + RelocBaseEnd = ImageAddress(data, size, context->RelocDir->VirtualAddress + context->RelocDir->Size - 1); + + if (!RelocBase || !RelocBaseEnd) { + Print(L"Reloc table overflows binary\n"); + return EFI_UNSUPPORTED; + } + + Adjust = (UINT64)data - context->ImageAddress; + + while (RelocBase < RelocBaseEnd) { + Reloc = (UINT16 *) ((char *) RelocBase + sizeof (EFI_IMAGE_BASE_RELOCATION)); + RelocEnd = (UINT16 *) ((char *) RelocBase + RelocBase->SizeOfBlock); + + if ((void *)RelocEnd < data || (void *)RelocEnd > ImageEnd) { + Print(L"Reloc entry overflows binary\n"); + return EFI_UNSUPPORTED; + } + + FixupBase = ImageAddress(data, size, RelocBase->VirtualAddress); + if (!FixupBase) { + Print(L"Invalid fixupbase\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))); + if (FixupData != NULL) { + *(UINT16 *) FixupData = *Fixup16; + FixupData = FixupData + sizeof (UINT16); + } + break; + + case EFI_IMAGE_REL_BASED_LOW: + Fixup16 = (UINT16 *) Fixup; + *Fixup16 = (UINT16) (*Fixup16 + (UINT16) Adjust); + if (FixupData != NULL) { + *(UINT16 *) FixupData = *Fixup16; + FixupData = FixupData + sizeof (UINT16); + } + break; + + case EFI_IMAGE_REL_BASED_HIGHLOW: + Fixup32 = (UINT32 *) Fixup; + *Fixup32 = *Fixup32 + (UINT32) Adjust; + if (FixupData != NULL) { + FixupData = ALIGN_POINTER (FixupData, sizeof (UINT32)); + *(UINT32 *)FixupData = *Fixup32; + FixupData = FixupData + sizeof (UINT32); + } + break; + + case EFI_IMAGE_REL_BASED_DIR64: + Fixup64 = (UINT64 *) Fixup; + *Fixup64 = *Fixup64 + (UINT64) Adjust; + if (FixupData != NULL) { + FixupData = ALIGN_POINTER (FixupData, sizeof(UINT64)); + *(UINT64 *)(FixupData) = *Fixup64; + FixupData = FixupData + sizeof(UINT64); + } + break; + + default: + Print(L"Unknown relocation\n"); + return EFI_UNSUPPORTED; + } + Reloc += 1; + } + RelocBase = (EFI_IMAGE_BASE_RELOCATION *) RelocEnd; + } + + return EFI_SUCCESS; +} + +static CHECK_STATUS check_db_cert(CHAR16 *dbname, WIN_CERTIFICATE_EFI_PKCS *data, UINT8 *hash) +{ + EFI_STATUS efi_status; + EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; + EFI_SIGNATURE_LIST *CertList; + EFI_SIGNATURE_DATA *Cert; + UINTN dbsize = 0; + UINTN CertCount, Index; + BOOLEAN IsFound = FALSE; + void *db; + EFI_GUID CertType = EfiCertX509Guid; + + efi_status = get_variable(dbname, secure_var, &dbsize, &db); + + if (efi_status != EFI_SUCCESS) + return VAR_NOT_FOUND; + + CertList = db; + + while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { + if (CompareGuid (&CertList->SignatureType, &CertType) == 0) { + CertCount = (CertList->SignatureListSize - CertList->SignatureHeaderSize) / CertList->SignatureSize; + Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); + for (Index = 0; Index < CertCount; Index++) { + IsFound = AuthenticodeVerify (data->CertData, + data->Hdr.dwLength - sizeof(data->Hdr), + Cert->SignatureData, + CertList->SignatureSize, + hash, SHA256_DIGEST_SIZE); + } + if (IsFound) { + break; + } + + Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize); + } + + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize); + } + + FreePool(db); + + if (IsFound) + return DATA_FOUND; + + return DATA_NOT_FOUND; +} + +static CHECK_STATUS check_db_hash(CHAR16 *dbname, UINT8 *data) +{ + EFI_STATUS efi_status; + EFI_GUID secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; + EFI_SIGNATURE_LIST *CertList; + EFI_SIGNATURE_DATA *Cert; + UINTN dbsize = 0; + UINTN CertCount, Index; + BOOLEAN IsFound = FALSE; + void *db; + unsigned int SignatureSize = SHA256_DIGEST_SIZE; + EFI_GUID CertType = EfiHashSha256Guid; + + efi_status = get_variable(dbname, secure_var, &dbsize, &db); + + if (efi_status != EFI_SUCCESS) { + return VAR_NOT_FOUND; + } + + CertList = db; + + while ((dbsize > 0) && (dbsize >= CertList->SignatureListSize)) { + CertCount = (CertList->SignatureListSize - CertList->SignatureHeaderSize) / CertList->SignatureSize; + Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); + if (CompareGuid(&CertList->SignatureType, &CertType) == 0) { + for (Index = 0; Index < CertCount; Index++) { + if (CompareMem (Cert->SignatureData, data, SignatureSize) == 0) { + // + // Find the signature in database. + // + IsFound = TRUE; + break; + } + + Cert = (EFI_SIGNATURE_DATA *) ((UINT8 *) Cert + CertList->SignatureSize); + } + if (IsFound) { + break; + } + } + + dbsize -= CertList->SignatureListSize; + CertList = (EFI_SIGNATURE_LIST *) ((UINT8 *) CertList + CertList->SignatureListSize); + } + + FreePool(db); + + if (IsFound) + return DATA_FOUND; + + return DATA_NOT_FOUND; +} + +static EFI_STATUS check_blacklist (WIN_CERTIFICATE_EFI_PKCS *cert, UINT8 *hash) +{ + if (check_db_hash(L"dbx", hash) == DATA_FOUND) + return EFI_ACCESS_DENIED; + if (check_db_cert(L"dbx", cert, hash) == DATA_FOUND) + return EFI_ACCESS_DENIED; + + return EFI_SUCCESS; +} + +static EFI_STATUS check_whitelist (WIN_CERTIFICATE_EFI_PKCS *cert, UINT8 *hash) +{ + if (check_db_hash(L"db", hash) == DATA_FOUND) + return EFI_SUCCESS; + if (check_db_cert(L"db", cert, hash) == DATA_FOUND) + return EFI_SUCCESS; + + return EFI_ACCESS_DENIED; +} + +/* + * Check whether we're in Secure Boot and user mode + */ + +static BOOLEAN secure_mode (void) +{ + EFI_STATUS status; + EFI_GUID global_var = EFI_GLOBAL_VARIABLE; + UINTN charsize = sizeof(char); + UINT8 sb, setupmode; + + status = get_variable(L"SecureBoot", global_var, &charsize, (void *)&sb); + + /* FIXME - more paranoia here? */ + if (status != EFI_SUCCESS || sb != 1) { + Print(L"Secure boot not enabled\n"); + return FALSE; + } + + status = get_variable(L"SetupMode", global_var, &charsize, (void *)&setupmode); + + if (status == EFI_SUCCESS && setupmode == 1) { + Print(L"Platform is in setup mode\n"); + return FALSE; + } + + return TRUE; +} + +/* + * 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, int whitelist) +{ + unsigned int size = datasize; + unsigned int ctxsize; + void *ctx = NULL; + UINT8 hash[SHA256_DIGEST_SIZE]; + EFI_STATUS status = EFI_ACCESS_DENIED; + char *hashbase; + unsigned int hashsize; + WIN_CERTIFICATE_EFI_PKCS *cert; + unsigned int SumOfBytesHashed, SumOfSectionBytes; + unsigned int index, pos; + EFI_IMAGE_SECTION_HEADER *Section; + EFI_IMAGE_SECTION_HEADER *SectionHeader = NULL; + EFI_IMAGE_SECTION_HEADER *SectionCache; + + cert = ImageAddress (data, size, context->SecDir->VirtualAddress); + + if (!cert) { + Print(L"Certificate located outside the image\n"); + return EFI_INVALID_PARAMETER; + } + + if (cert->Hdr.wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + Print(L"Unsupported certificate type %x\n", + cert->Hdr.wCertificateType); + return EFI_UNSUPPORTED; + } + + /* FIXME: Check which kind of hash */ + + ctxsize = Sha256GetContextSize(); + ctx = AllocatePool(ctxsize); + + if (!ctx) { + Print(L"Unable to allocate memory for hash context\n"); + return EFI_OUT_OF_RESOURCES; + } + + if (!Sha256Init(ctx)) { + Print(L"Unable to initialise hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + /* Hash start to checksum */ + hashbase = data; + hashsize = (char *)&context->PEHdr->Pe32.OptionalHeader.CheckSum - + hashbase; + + if (!(Sha256Update(ctx, hashbase, hashsize))) { + Print(L"Unable to generate hash\n"); + 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; + + if (!(Sha256Update(ctx, hashbase, hashsize))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + /* Hash end of certificate table to end of image header */ + hashbase = (char *) &context->PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY + 1]; + hashsize = context->PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders - + (int) ((char *) (&context->PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY + 1]) - data); + + if (!(Sha256Update(ctx, hashbase, hashsize))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + /* Sort sections */ + SumOfBytesHashed = context->PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; + + Section = (EFI_IMAGE_SECTION_HEADER *) ( + (char *)context->PEHdr + sizeof (UINT32) + + sizeof (EFI_IMAGE_FILE_HEADER) + + context->PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + ); + + SectionCache = Section; + + for (index = 0, SumOfSectionBytes = 0; index < context->PEHdr->Pe32.FileHeader.NumberOfSections; index++, SectionCache++) { + SumOfSectionBytes += SectionCache->SizeOfRawData; + } + + if (SumOfSectionBytes >= datasize) { + Print(L"Malformed binary: %x %x\n", SumOfSectionBytes, size); + status = EFI_INVALID_PARAMETER; + goto done; + } + + SectionHeader = (EFI_IMAGE_SECTION_HEADER *) AllocateZeroPool (sizeof (EFI_IMAGE_SECTION_HEADER) * context->PEHdr->Pe32.FileHeader.NumberOfSections); + if (SectionHeader == NULL) { + Print(L"Unable to allocate section header\n"); + status = EFI_OUT_OF_RESOURCES; + 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); + hashsize = (unsigned int) Section->SizeOfRawData; + + if (!hashbase) { + Print(L"Malformed section header\n"); + return EFI_INVALID_PARAMETER; + } + + if (!(Sha256Update(ctx, hashbase, hashsize))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + SumOfBytesHashed += Section->SizeOfRawData; + } + + /* Hash all remaining data */ + if (size > SumOfBytesHashed) { + hashbase = data + SumOfBytesHashed; + hashsize = (unsigned int)( + size - + context->PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size - + SumOfBytesHashed); + + if (!(Sha256Update(ctx, hashbase, hashsize))) { + Print(L"Unable to generate hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + } + + if (!(Sha256Final(ctx, hash))) { + Print(L"Unable to finalise hash\n"); + status = EFI_OUT_OF_RESOURCES; + goto done; + } + + status = check_blacklist(cert, hash); + + if (status != EFI_SUCCESS) { + Print(L"Binary is blacklisted\n"); + goto done; + } + + if (whitelist) { + status = check_whitelist(cert, hash); + + if (status == EFI_SUCCESS) { + Print(L"Binary is whitelisted\n"); + goto done; + } + } + + if (!AuthenticodeVerify(cert->CertData, + context->SecDir->Size - sizeof(cert->Hdr), + vendor_cert, sizeof(vendor_cert), hash, + SHA256_DIGEST_SIZE)) { + Print(L"Invalid signature\n"); + status = EFI_ACCESS_DENIED; + } else { + status = EFI_SUCCESS; + } + +done: + if (SectionHeader) + FreePool(SectionHeader); + if (ctx) + FreePool(ctx); + + return status; +} + +/* + * Read the binary header and grab appropriate information from it + */ +static EFI_STATUS read_header(void *data, + PE_COFF_LOADER_IMAGE_CONTEXT *context) +{ + EFI_IMAGE_DOS_HEADER *DosHdr = data; + EFI_IMAGE_OPTIONAL_HEADER_UNION *PEHdr = data; + + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) + PEHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)((char *)data + DosHdr->e_lfanew); + + if (PEHdr->Te.Signature != EFI_IMAGE_NT_SIGNATURE) { + Print(L"Unsupported image type\n"); + return EFI_UNSUPPORTED; + } + + if (PEHdr->Pe32.FileHeader.Characteristics & EFI_IMAGE_FILE_RELOCS_STRIPPED) { + Print(L"Unsupported image - Relocations have been stripped\n"); + return EFI_UNSUPPORTED; + } + + if (PEHdr->Pe32.OptionalHeader.Magic != EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + Print(L"Only 64-bit images supported\n"); + return EFI_UNSUPPORTED; + } + + context->PEHdr = PEHdr; + context->ImageAddress = PEHdr->Pe32Plus.OptionalHeader.ImageBase; + context->ImageSize = (UINT64)PEHdr->Pe32Plus.OptionalHeader.SizeOfImage; + context->SizeOfHeaders = PEHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; + context->EntryPoint = PEHdr->Pe32Plus.OptionalHeader.AddressOfEntryPoint; + context->RelocDir = &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_BASERELOC]; + context->NumberOfRvaAndSizes = PEHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; + context->NumberOfSections = PEHdr->Pe32.FileHeader.NumberOfSections; + context->FirstSection = (EFI_IMAGE_SECTION_HEADER *)((char *)PEHdr + PEHdr->Pe32.FileHeader.SizeOfOptionalHeader + sizeof(UINT32) + sizeof(EFI_IMAGE_FILE_HEADER)); + context->SecDir = (EFI_IMAGE_DATA_DIRECTORY *) &PEHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + + if (context->SecDir->VirtualAddress >= context->ImageSize) { + Print(L"Malformed security header\n"); + return EFI_INVALID_PARAMETER; + } + + if (context->SecDir->Size == 0) { + Print(L"Empty 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_grub (void *data, int datasize, EFI_LOADED_IMAGE *li) +{ + EFI_STATUS efi_status; + char *buffer; + int i, size; + EFI_IMAGE_SECTION_HEADER *Section; + char *base, *end; + PE_COFF_LOADER_IMAGE_CONTEXT context; + + efi_status = read_header(data, &context); + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to read header\n"); + return efi_status; + } + + if (secure_mode ()) { + efi_status = verify_buffer(data, datasize, &context, 0); + + if (efi_status != EFI_SUCCESS) { + Print(L"Verification failed\n"); + return efi_status; + } + } + + buffer = AllocatePool(context.ImageSize); + + if (!buffer) { + Print(L"Failed to allocate image buffer\n"); + return EFI_OUT_OF_RESOURCES; + } + + CopyMem(buffer, data, context.SizeOfHeaders); + + Section = context.FirstSection; + for (i = 0; i < context.NumberOfSections; i++) { + size = Section->Misc.VirtualSize; + + if (size > Section->SizeOfRawData) + size = Section->SizeOfRawData; + + base = ImageAddress (buffer, context.ImageSize, Section->VirtualAddress); + end = ImageAddress (buffer, context.ImageSize, Section->VirtualAddress + size - 1); + + if (!base || !end) { + Print(L"Invalid section size\n"); + return EFI_UNSUPPORTED; + } + + if (Section->SizeOfRawData > 0) + CopyMem(base, data + Section->PointerToRawData, size); + + if (size < Section->Misc.VirtualSize) + ZeroMem (base + size, Section->Misc.VirtualSize - size); + + Section += 1; + } + + efi_status = relocate_coff(&context, buffer); + + if (efi_status != EFI_SUCCESS) { + Print(L"Relocation failed\n"); + FreePool(buffer); + return efi_status; + } + + entry_point = ImageAddress(buffer, context.ImageSize, context.EntryPoint); + li->ImageBase = buffer; + li->ImageSize = context.ImageSize; + + if (!entry_point) { + Print(L"Invalid entry point\n"); + FreePool(buffer); + return EFI_UNSUPPORTED; + } + + return EFI_SUCCESS; +} + +static EFI_STATUS generate_path(EFI_LOADED_IMAGE *li, EFI_DEVICE_PATH **grubpath, CHAR16 **PathName) +{ + EFI_DEVICE_PATH *devpath; + EFI_HANDLE device; + int i; + unsigned int pathlen = 0; + EFI_STATUS efi_status = EFI_SUCCESS; + CHAR16 *bootpath; + + device = li->DeviceHandle; + devpath = li->FilePath; + + bootpath = DevicePathToStr(devpath); + + pathlen = StrLen(bootpath); + + for (i=pathlen; i>0; i--) { + if (bootpath[i] == '\\') + break; + } + + bootpath[i+1] = '\0'; + + if (bootpath[i-i] == '\\') + bootpath[i] = '\0'; + + *PathName = AllocatePool(StrSize(bootpath) + StrSize(SECOND_STAGE)); + + if (!*PathName) { + Print(L"Failed to allocate path buffer\n"); + efi_status = EFI_OUT_OF_RESOURCES; + goto error; + } + + *PathName[0] = '\0'; + StrCat(*PathName, bootpath); + StrCat(*PathName, SECOND_STAGE); + + *grubpath = FileDevicePath(device, *PathName); + +error: + return efi_status; +} + +/* + * Locate the second stage bootloader and read it into a buffer + */ +static EFI_STATUS load_grub (EFI_LOADED_IMAGE *li, void **data, + int *datasize, CHAR16 *PathName) +{ + EFI_GUID simple_file_system_protocol = SIMPLE_FILE_SYSTEM_PROTOCOL; + EFI_GUID file_info_id = EFI_FILE_INFO_ID; + EFI_STATUS efi_status; + EFI_HANDLE device; + EFI_FILE_INFO *fileinfo = NULL; + EFI_FILE_IO_INTERFACE *drive; + EFI_FILE *root, *grub; + UINTN buffersize = sizeof(EFI_FILE_INFO); + + device = li->DeviceHandle; + + efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, device, + &simple_file_system_protocol, &drive); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to find fs\n"); + goto error; + } + + efi_status = uefi_call_wrapper(drive->OpenVolume, 2, drive, &root); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to open fs\n"); + goto error; + } + + efi_status = uefi_call_wrapper(root->Open, 5, root, &grub, PathName, + EFI_FILE_MODE_READ, 0); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to open %s - %lx\n", PathName, efi_status); + goto error; + } + + fileinfo = AllocatePool(buffersize); + + if (!fileinfo) { + Print(L"Unable to allocate file info buffer\n"); + efi_status = EFI_OUT_OF_RESOURCES; + goto error; + } + + efi_status = uefi_call_wrapper(grub->GetInfo, 4, grub, &file_info_id, + &buffersize, fileinfo); + + if (efi_status == EFI_BUFFER_TOO_SMALL) { + fileinfo = AllocatePool(buffersize); + if (!fileinfo) { + Print(L"Unable to allocate file info buffer\n"); + efi_status = EFI_OUT_OF_RESOURCES; + goto error; + } + efi_status = uefi_call_wrapper(grub->GetInfo, 4, grub, + &file_info_id, &buffersize, + fileinfo); + } + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to get file info\n"); + goto error; + } + + buffersize = fileinfo->FileSize; + + *data = AllocatePool(buffersize); + + if (!*data) { + Print(L"Unable to allocate file buffer\n"); + efi_status = EFI_OUT_OF_RESOURCES; + goto error; + } + efi_status = uefi_call_wrapper(grub->Read, 3, grub, &buffersize, + *data); + + if (efi_status == EFI_BUFFER_TOO_SMALL) { + FreePool(*data); + *data = AllocatePool(buffersize); + efi_status = uefi_call_wrapper(grub->Read, 3, grub, + &buffersize, *data); + } + + if (efi_status != EFI_SUCCESS) { + Print(L"Unexpected return from initial read: %x, buffersize %x\n", efi_status, buffersize); + goto error; + } + + *datasize = buffersize; + + return EFI_SUCCESS; +error: + if (*data) { + FreePool(*data); + *data = NULL; + } + if (PathName) + FreePool(PathName); + if (fileinfo) + FreePool(fileinfo); + return efi_status; +} + +EFI_STATUS shim_verify (void *buffer, UINT32 size) +{ + EFI_STATUS status; + PE_COFF_LOADER_IMAGE_CONTEXT context; + + if (!secure_mode()) + return EFI_SUCCESS; + + status = read_header(buffer, &context); + + if (status != EFI_SUCCESS) + return status; + + status = verify_buffer(buffer, size, &context, 1); + + return status; +} + +EFI_STATUS init_grub(EFI_HANDLE image_handle) +{ + EFI_STATUS efi_status; + EFI_HANDLE grub_handle = NULL; + EFI_LOADED_IMAGE *li, li_bak; + EFI_DEVICE_PATH *grubpath; + CHAR16 *PathName; + EFI_GUID loaded_image_protocol = LOADED_IMAGE_PROTOCOL; + void *data = NULL; + int datasize; + + efi_status = uefi_call_wrapper(BS->HandleProtocol, 3, image_handle, + &loaded_image_protocol, &li); + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to init protocol\n"); + return efi_status; + } + + efi_status = generate_path(li, &grubpath, &PathName); + + if (efi_status != EFI_SUCCESS) { + Print(L"Unable to generate grub path\n"); + goto done; + } + + efi_status = uefi_call_wrapper(BS->LoadImage, 6, FALSE, image_handle, + grubpath, NULL, 0, &grub_handle); + + + if (efi_status == EFI_SUCCESS) { + /* Image validates - start it */ + Print(L"Starting file via StartImage\n"); + efi_status = uefi_call_wrapper(BS->StartImage, 3, grub_handle, NULL, + NULL); + uefi_call_wrapper(BS->UnloadImage, 1, grub_handle); + goto done; + } + + efi_status = load_grub(li, &data, &datasize, PathName); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to load grub\n"); + goto done; + } + + CopyMem(&li_bak, li, sizeof(li_bak)); + + efi_status = handle_grub(data, datasize, li); + + if (efi_status != EFI_SUCCESS) { + Print(L"Failed to load grub\n"); + CopyMem(li, &li_bak, sizeof(li_bak)); + goto done; + } + + efi_status = uefi_call_wrapper(entry_point, 3, image_handle, systab); + + CopyMem(li, &li_bak, sizeof(li_bak)); +done: + + return efi_status; +} + +EFI_STATUS efi_main (EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *passed_systab) +{ + EFI_GUID shim_lock_guid = SHIM_LOCK_GUID; + static SHIM_LOCK shim_lock_interface; + EFI_HANDLE handle = NULL; + EFI_STATUS efi_status; + + shim_lock_interface.Verify = shim_verify; + + systab = passed_systab; + + InitializeLib(image_handle, systab); + + uefi_call_wrapper(BS->InstallProtocolInterface, 4, &handle, + &shim_lock_guid, EFI_NATIVE_INTERFACE, + &shim_lock_interface); + + efi_status = init_grub(image_handle); + + uefi_call_wrapper(BS->UninstallProtocolInterface, 3, handle, + &shim_lock_guid, &shim_lock_interface); + + return efi_status; +} |
