CloverBootloader/MdePkg/Library/BasePeCoffLib2/PeCoffHash.c

476 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @file
Implements APIs to verify the Authenticode Signature of PE/COFF Images.
Copyright (c) 2020 - 2021, Marvin Häuser. All rights reserved.<BR>
Copyright (c) 2020, Vitaly Cheptsov. All rights reserved.<BR>
Copyright (c) 2020, ISP RAS. All rights reserved.<BR>
Portions copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR>
Portions copyright (c) 2016, Hewlett Packard Enterprise Development LP. All rights reserved.<BR>
SPDX-License-Identifier: BSD-3-Clause
**/
#include <Base.h>
#include <IndustryStandard/PeImage2.h>
#include <Guid/WinCertificate.h>
#include <Library/BaseOverflowLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/PeCoffLib2.h>
#include "BasePeCoffLib2Internals.h"
/**
Hashes the Image section data in ascending order of raw file appearance.
@param[in] Context The context describing the Image. Must have been
initialised by PeCoffInitializeContext().
@param[in] HashUpdate The data hashing function.
@param[in,out] HashContext The context of the current hash.
@returns Whether hashing has been successful.
**/
STATIC
BOOLEAN
InternalHashSections (
IN CONST PE_COFF_LOADER_IMAGE_CONTEXT *Context,
IN OUT VOID *HashContext,
IN PE_COFF_LOADER_HASH_UPDATE HashUpdate,
IN OUT UINT32 *SumBytesHashed
)
{
BOOLEAN Result;
BOOLEAN Overflow;
CONST EFI_IMAGE_SECTION_HEADER *Sections;
CONST EFI_IMAGE_SECTION_HEADER **SortedSections;
UINT16 SectionIndex;
UINT16 SectionPos;
UINT32 SectionTop;
UINT32 CurHashSize;
//
// 9. Build a temporary table of pointers to all of the section headers in the
// image. The NumberOfSections field of COFF File Header indicates how big
// the table should be. Do not include any section headers in the table
// whose SizeOfRawData field is zero.
//
SortedSections = AllocatePool (
(UINT32) Context->NumberOfSections * sizeof (*SortedSections)
);
if (SortedSections == NULL) {
return FALSE;
}
Sections = (CONST EFI_IMAGE_SECTION_HEADER *) (CONST VOID *) (
(CONST CHAR8 *) Context->FileBuffer + Context->SectionsOffset
);
//
// 10. Using the PointerToRawData field (offset 20) in the referenced
// SectionHeader structure as a key, arrange the table's elements in
// ascending order. In other words, sort the section headers in ascending
// order according to the disk-file offset of the sections.
//
SortedSections[0] = Sections;
//
// Perform Insertion Sort to order the Sections by their raw file offset.
//
for (SectionIndex = 1; SectionIndex < Context->NumberOfSections; ++SectionIndex) {
for (SectionPos = SectionIndex;
0 < SectionPos
&& SortedSections[SectionPos - 1]->PointerToRawData > Sections[SectionIndex].PointerToRawData;
--SectionPos) {
SortedSections[SectionPos] = SortedSections[SectionPos - 1];
}
SortedSections[SectionPos] = Sections + SectionIndex;
}
Result = TRUE;
SectionTop = 0;
CurHashSize = 0;
//
// 13. Repeat steps 11 and 12 for all of the sections in the sorted table.
//
for (SectionIndex = 0; SectionIndex < Context->NumberOfSections; ++SectionIndex) {
//
// Verify the Image section does not overlap with the previous one if the
// policy demands it. Overlapping Sections could dramatically increase the
// hashing time.
// FIXME: Move to init, along with a trailing data policy.
//
if (PcdGetBool (PcdImageLoaderHashProhibitOverlap)) {
if (SectionTop > SortedSections[SectionIndex]->PointerToRawData) {
Result = FALSE;
break;
}
SectionTop = SortedSections[SectionIndex]->PointerToRawData + SortedSections[SectionIndex]->SizeOfRawData;
}
//
// Skip Sections that contain no data.
//
if (SortedSections[SectionIndex]->SizeOfRawData > 0) {
//
// 11. Walk through the sorted table, load the corresponding section into
// memory, and hash the entire section. Use the SizeOfRawData field in the
// SectionHeader structure to determine the amount of data to hash.
//
Result = HashUpdate (
HashContext,
(CONST CHAR8 *) Context->FileBuffer + SortedSections[SectionIndex]->PointerToRawData,
SortedSections[SectionIndex]->SizeOfRawData
);
if (!Result) {
break;
}
//
// 12. Add the sections SizeOfRawData value to SUM_OF_BYTES_HASHED.
//
// If and only if the Sections do not overlap, we know their sizes are at
// most MAX_UINT32 in sum because the file size is at most MAX_UINT32.
//
if (PcdGetBool (PcdImageLoaderHashProhibitOverlap)) {
CurHashSize += SortedSections[SectionIndex]->SizeOfRawData;
} else {
//
// Verify the hash size does not overflow.
//
Overflow = BaseOverflowAddU32 (
CurHashSize,
SortedSections[SectionIndex]->SizeOfRawData,
&CurHashSize
);
if (Overflow) {
Result = FALSE;
break;
}
}
}
}
*SumBytesHashed = CurHashSize;
FreePool ((VOID *) SortedSections);
return Result;
}
BOOLEAN
PeCoffHashImageAuthenticode (
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
IN OUT VOID *HashContext,
IN PE_COFF_LOADER_HASH_UPDATE HashUpdate
)
{
BOOLEAN Result;
BOOLEAN Overflow;
UINT32 NumberOfRvaAndSizes;
UINT32 ChecksumOffset;
UINT32 SecurityDirOffset;
UINT32 SecurityDirSize;
UINT32 CurrentOffset;
UINT32 HashSize;
CONST EFI_IMAGE_NT_HEADERS32 *Pe32;
CONST EFI_IMAGE_NT_HEADERS64 *Pe32Plus;
UINT32 SumBytesHashed;
UINT32 FileSize;
//
// These conditions must be met by the caller prior to calling this function.
//
// 1. Load the image header into memory.
// 2. Initialize a hash algorithm context.
//
//
// This step can be moved here because steps 1 to 5 do not modify the Image.
//
// 6. Get the Attribute Certificate Table address and size from the
// Certificate Table entry. For details, see section 5.7 of the PE/COFF
// specification.
//
// Additionally, retrieve important offsets for later steps.
//
switch (Context->ImageType) {
case PeCoffLoaderTypePe32:
Pe32 = (CONST EFI_IMAGE_NT_HEADERS32 *) (CONST VOID *) (
(CONST CHAR8 *) Context->FileBuffer + Context->ExeHdrOffset
);
ChecksumOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS32, CheckSum);
SecurityDirOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS32, DataDirectory) + (UINT32) (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY * sizeof (EFI_IMAGE_DATA_DIRECTORY));
NumberOfRvaAndSizes = Pe32->NumberOfRvaAndSizes;
//
// Retrieve the Security Directory size depending on existence.
//
if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) {
SecurityDirSize = Pe32->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size;
} else {
SecurityDirSize = 0;
}
break;
case PeCoffLoaderTypePe32Plus:
Pe32Plus = (CONST EFI_IMAGE_NT_HEADERS64 *) (CONST VOID *) (
(CONST CHAR8 *) Context->FileBuffer + Context->ExeHdrOffset
);
ChecksumOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS64, CheckSum);
SecurityDirOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS64, DataDirectory) + (UINT32) (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY * sizeof (EFI_IMAGE_DATA_DIRECTORY));
NumberOfRvaAndSizes = Pe32Plus->NumberOfRvaAndSizes;
//
// Retrieve the Security Directory size depending on existence.
//
if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) {
SecurityDirSize = Pe32Plus->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size;
} else {
SecurityDirSize = 0;
}
break;
default:
ASSERT (FALSE);
return FALSE;
}
//
// 3. Hash the image header from its base to immediately before the start of
// the checksum address, as specified in Optional Header Windows-Specific
// Fields.
//
Result = HashUpdate (HashContext, Context->FileBuffer, ChecksumOffset);
if (!Result) {
DEBUG_RAISE ();
return FALSE;
}
//
// 4. Skip over the checksum, which is a 4-byte field.
//
CurrentOffset = ChecksumOffset + sizeof (UINT32);
//
// 5. Hash everything from the end of the checksum field to immediately before
// the start of the Certificate Table entry, as specified in Optional
// Header Data Directories.
//
if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) {
HashSize = SecurityDirOffset - CurrentOffset;
Result = HashUpdate (
HashContext,
(CONST CHAR8 *) Context->FileBuffer + CurrentOffset,
HashSize
);
if (!Result) {
DEBUG_RAISE ();
return FALSE;
}
//
// Skip over the Security Directory.
//
CurrentOffset = SecurityDirOffset + sizeof (EFI_IMAGE_DATA_DIRECTORY);
}
//
// 7. Exclude the Certificate Table entry from the calculation and hash
// everything from the end of the Certificate Table entry to the end of
// image header, including Section Table (headers).The Certificate Table
// entry is 8 Bytes long, as specified in Optional Header Data Directories.
//
HashSize = Context->SizeOfHeaders - CurrentOffset;
Result = HashUpdate (
HashContext,
(CONST CHAR8 *) Context->FileBuffer + CurrentOffset,
HashSize
);
if (!Result) {
DEBUG_RAISE ();
return FALSE;
}
//
// Perform the Section-related steps of the algorithm.
//
Result = InternalHashSections (
Context,
HashContext,
HashUpdate,
&SumBytesHashed
);
if (!Result) {
DEBUG_RAISE ();
return FALSE;
}
//
// 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the
// signature. Set this counter to the SizeOfHeaders field, as specified in
// Optional Header Windows-Specific Field.
//
Overflow = BaseOverflowAddU32 (
SumBytesHashed,
Context->SizeOfHeaders,
&SumBytesHashed
);
if (Overflow) {
DEBUG_RAISE ();
return FALSE;
}
//
// 14. Create a value called FILE_SIZE, which is not part of the signature.
// Set this value to the images file size, acquired from the underlying
// file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the file
// contains extra data that must be added to the hash. This data begins at
// the SUM_OF_BYTES_HASHED file offset, and its length is:
// (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
//
// Note: The size of Attribute Certificate Table is specified in the
// second ULONG value in the Certificate Table entry (32 bit: offset 132,
// 64 bit: offset 148) in Optional Header Data Directories.
//
FileSize = Context->FileSize - SecurityDirSize;
if (SumBytesHashed < FileSize) {
Result = HashUpdate (
HashContext,
(CONST CHAR8 *) Context->FileBuffer + SumBytesHashed,
FileSize - SumBytesHashed
);
}
//
// This step must be performed by the caller after this function succeeded.
//
// 15. Finalize the hash algorithm context.
//
return Result;
}
RETURN_STATUS
PeCoffGetFirstCertificate (
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
OUT CONST WIN_CERTIFICATE **Certificate
)
{
CONST WIN_CERTIFICATE *WinCertificate;
//
// These conditions are verified by PeCoffInitializeContext().
//
ASSERT (Context->SecDirOffset % ALIGNOF (WIN_CERTIFICATE));
ASSERT (Context->SecDirSize == 0 || sizeof (WIN_CERTIFICATE) <= Context->SecDirSize);
//
// Verify the Security Directory is not empty.
//
if (Context->SecDirSize == 0) {
return RETURN_NOT_FOUND;
}
//
// Verify the certificate size is well-formed and that it is in bounds of the
// Security Directory.
//
WinCertificate = (CONST WIN_CERTIFICATE *) (CONST VOID *) (
(CONST UINT8 *) Context->FileBuffer + Context->SecDirOffset
);
if (WinCertificate->dwLength < sizeof (WIN_CERTIFICATE)
|| WinCertificate->dwLength > Context->SecDirSize) {
DEBUG_RAISE ();
return RETURN_VOLUME_CORRUPTED;
}
//
// Verify the certificate size is sufficiently aligned, if the policy demands
// it. This has been observed to not be the case with images signed with
// pesign, such as GRUB.
//
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CERTIFICATE_SIZES) == 0) {
if (!IS_ALIGNED (WinCertificate->dwLength, IMAGE_CERTIFICATE_ALIGN)) {
DEBUG_RAISE ();
return RETURN_VOLUME_CORRUPTED;
}
}
*Certificate = WinCertificate;
return RETURN_SUCCESS;
}
RETURN_STATUS
PeCoffGetNextCertificate (
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
IN OUT CONST WIN_CERTIFICATE **Certificate
)
{
BOOLEAN Overflow;
UINT32 CertOffset;
UINT32 CertSize;
UINT32 CertEnd;
CONST WIN_CERTIFICATE *WinCertificate;
//
// This condition is verified by PeCoffInitializeContext().
//
ASSERT (IS_ALIGNED (Context->SecDirSize, IMAGE_CERTIFICATE_ALIGN));
//
// Retrieve the current certificate.
//
WinCertificate = *Certificate;
CertOffset = (UINT32) ((UINTN) WinCertificate - ((UINTN) Context->FileBuffer + Context->SecDirOffset));
//
// Retrieve the offset of the next certificate.
//
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CERTIFICATE_SIZES) == 0) {
CertSize = WinCertificate->dwLength;
} else {
CertSize = ALIGN_VALUE (WinCertificate->dwLength, IMAGE_CERTIFICATE_ALIGN);
}
CertOffset += CertSize;
//
// This invariant is ensured by the certificate iteration functions.
//
ASSERT (CertOffset <= Context->SecDirSize);
//
// If the next offset is the end of the Directory, signal it's the end of
// the certificate list.
//
if (CertOffset == Context->SecDirSize) {
return RETURN_NOT_FOUND;
}
//
// Verify the Directory fits another certificate.
//
if (Context->SecDirSize - CertOffset < sizeof (WIN_CERTIFICATE)) {
DEBUG_RAISE ();
return RETURN_VOLUME_CORRUPTED;
}
//
// Verify the certificate has a well-formed size.
//
WinCertificate = (CONST WIN_CERTIFICATE *) (CONST VOID *) (
(CONST UINT8 *) Context->FileBuffer + Context->SecDirOffset + CertOffset
);
if (WinCertificate->dwLength < sizeof (WIN_CERTIFICATE)) {
DEBUG_RAISE ();
return RETURN_VOLUME_CORRUPTED;
}
//
// Verify the certificate size is sufficiently aligned, if the policy demands
// it.
//
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CERTIFICATE_SIZES) == 0) {
if (!IS_ALIGNED (WinCertificate->dwLength, IMAGE_CERTIFICATE_ALIGN)) {
DEBUG_RAISE ();
return RETURN_VOLUME_CORRUPTED;
}
}
//
// Verify the certificate is in bounds of the Security Directory.
//
Overflow = BaseOverflowAddU32 (
CertOffset,
WinCertificate->dwLength,
&CertEnd
);
if (Overflow || CertEnd > Context->SecDirSize) {
DEBUG_RAISE ();
return RETURN_VOLUME_CORRUPTED;
}
*Certificate = WinCertificate;
return RETURN_SUCCESS;
}