/** @file | |
This module implements measuring PeCoff image for Tcg2 Protocol. | |
Caution: This file requires additional review when modified. | |
This driver will have external input - PE/COFF image. | |
This external input must be validated carefully to avoid security issue like | |
buffer overflow, integer overflow. | |
Copyright (c) 2015 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <PiDxe.h> | |
#include <Library/BaseLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/DevicePathLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/PeCoffLib.h> | |
#include <Library/Tpm2CommandLib.h> | |
#include <Library/HashLib.h> | |
UINTN mTcg2DxeImageSize = 0; | |
/** | |
Reads contents of a PE/COFF image in memory buffer. | |
Caution: This function may receive untrusted input. | |
PE/COFF image is external input, so this function will make sure the PE/COFF image content | |
read is within the image buffer. | |
@param FileHandle Pointer to the file handle to read the PE/COFF image. | |
@param FileOffset Offset into the PE/COFF image to begin the read operation. | |
@param ReadSize On input, the size in bytes of the requested read operation. | |
On output, the number of bytes actually read. | |
@param Buffer Output buffer that contains the data read from the PE/COFF image. | |
@retval EFI_SUCCESS The specified portion of the PE/COFF image was read and the size | |
**/ | |
EFI_STATUS | |
EFIAPI | |
Tcg2DxeImageRead ( | |
IN VOID *FileHandle, | |
IN UINTN FileOffset, | |
IN OUT UINTN *ReadSize, | |
OUT VOID *Buffer | |
) | |
{ | |
UINTN EndPosition; | |
if ((FileHandle == NULL) || (ReadSize == NULL) || (Buffer == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (MAX_ADDRESS - FileOffset < *ReadSize) { | |
return EFI_INVALID_PARAMETER; | |
} | |
EndPosition = FileOffset + *ReadSize; | |
if (EndPosition > mTcg2DxeImageSize) { | |
*ReadSize = (UINT32)(mTcg2DxeImageSize - FileOffset); | |
} | |
if (FileOffset >= mTcg2DxeImageSize) { | |
*ReadSize = 0; | |
} | |
CopyMem (Buffer, (UINT8 *)((UINTN)FileHandle + FileOffset), *ReadSize); | |
return EFI_SUCCESS; | |
} | |
/** | |
Measure PE image into TPM log based on the authenticode image hashing in | |
PE/COFF Specification 8.0 Appendix A. | |
Caution: This function may receive untrusted input. | |
PE/COFF image is external input, so this function will validate its data structure | |
within this image buffer before use. | |
Notes: PE/COFF image is checked by BasePeCoffLib PeCoffLoaderGetImageInfo(). | |
@param[in] PCRIndex TPM PCR index | |
@param[in] ImageAddress Start address of image buffer. | |
@param[in] ImageSize Image size | |
@param[out] DigestList Digest list of this image. | |
@retval EFI_SUCCESS Successfully measure image. | |
@retval EFI_OUT_OF_RESOURCES No enough resource to measure image. | |
@retval other error value | |
**/ | |
EFI_STATUS | |
MeasurePeImageAndExtend ( | |
IN UINT32 PCRIndex, | |
IN EFI_PHYSICAL_ADDRESS ImageAddress, | |
IN UINTN ImageSize, | |
OUT TPML_DIGEST_VALUES *DigestList | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_IMAGE_DOS_HEADER *DosHdr; | |
UINT32 PeCoffHeaderOffset; | |
EFI_IMAGE_SECTION_HEADER *Section; | |
UINT8 *HashBase; | |
UINTN HashSize; | |
UINTN SumOfBytesHashed; | |
EFI_IMAGE_SECTION_HEADER *SectionHeader; | |
UINTN Index; | |
UINTN Pos; | |
EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr; | |
UINT32 NumberOfRvaAndSizes; | |
UINT32 CertSize; | |
HASH_HANDLE HashHandle; | |
PE_COFF_LOADER_IMAGE_CONTEXT ImageContext; | |
HashHandle = 0xFFFFFFFF; // Know bad value | |
Status = EFI_UNSUPPORTED; | |
SectionHeader = NULL; | |
// | |
// Check PE/COFF image | |
// | |
ZeroMem (&ImageContext, sizeof (ImageContext)); | |
ImageContext.Handle = (VOID *)(UINTN)ImageAddress; | |
mTcg2DxeImageSize = ImageSize; | |
ImageContext.ImageRead = (PE_COFF_LOADER_READ_FILE)Tcg2DxeImageRead; | |
// | |
// Get information about the image being loaded | |
// | |
Status = PeCoffLoaderGetImageInfo (&ImageContext); | |
if (EFI_ERROR (Status)) { | |
// | |
// The information can't be got from the invalid PeImage | |
// | |
DEBUG ((DEBUG_INFO, "Tcg2Dxe: PeImage invalid. Cannot retrieve image information.\n")); | |
goto Finish; | |
} | |
DosHdr = (EFI_IMAGE_DOS_HEADER *)(UINTN)ImageAddress; | |
PeCoffHeaderOffset = 0; | |
if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { | |
PeCoffHeaderOffset = DosHdr->e_lfanew; | |
} | |
Hdr.Pe32 = (EFI_IMAGE_NT_HEADERS32 *)((UINT8 *)(UINTN)ImageAddress + PeCoffHeaderOffset); | |
if (Hdr.Pe32->Signature != EFI_IMAGE_NT_SIGNATURE) { | |
Status = EFI_UNSUPPORTED; | |
goto Finish; | |
} | |
// | |
// PE/COFF Image Measurement | |
// | |
// NOTE: The following codes/steps are based upon the authenticode image hashing in | |
// PE/COFF Specification 8.0 Appendix A. | |
// | |
// | |
// 1. Load the image header into memory. | |
// 2. Initialize a SHA hash context. | |
Status = HashStart (&HashHandle); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
// | |
// Measuring PE/COFF Image Header; | |
// But CheckSum field and SECURITY data directory (certificate) are excluded | |
// | |
// | |
// 3. Calculate the distance from the base of the image header to the image checksum address. | |
// 4. Hash the image header from its base to beginning of the image checksum. | |
// | |
HashBase = (UINT8 *)(UINTN)ImageAddress; | |
if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { | |
// | |
// Use PE32 offset | |
// | |
NumberOfRvaAndSizes = Hdr.Pe32->OptionalHeader.NumberOfRvaAndSizes; | |
HashSize = (UINTN)(&Hdr.Pe32->OptionalHeader.CheckSum) - (UINTN)HashBase; | |
} else { | |
// | |
// Use PE32+ offset | |
// | |
NumberOfRvaAndSizes = Hdr.Pe32Plus->OptionalHeader.NumberOfRvaAndSizes; | |
HashSize = (UINTN)(&Hdr.Pe32Plus->OptionalHeader.CheckSum) - (UINTN)HashBase; | |
} | |
Status = HashUpdate (HashHandle, HashBase, HashSize); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
// | |
// 5. Skip over the image checksum (it occupies a single ULONG). | |
// | |
if (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_SECURITY) { | |
// | |
// 6. Since there is no Cert Directory in optional header, hash everything | |
// from the end of the checksum to the end of image header. | |
// | |
if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { | |
// | |
// Use PE32 offset. | |
// | |
HashBase = (UINT8 *)&Hdr.Pe32->OptionalHeader.CheckSum + sizeof (UINT32); | |
HashSize = Hdr.Pe32->OptionalHeader.SizeOfHeaders - (UINTN)(HashBase - ImageAddress); | |
} else { | |
// | |
// Use PE32+ offset. | |
// | |
HashBase = (UINT8 *)&Hdr.Pe32Plus->OptionalHeader.CheckSum + sizeof (UINT32); | |
HashSize = Hdr.Pe32Plus->OptionalHeader.SizeOfHeaders - (UINTN)(HashBase - ImageAddress); | |
} | |
if (HashSize != 0) { | |
Status = HashUpdate (HashHandle, HashBase, HashSize); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
} | |
} else { | |
// | |
// 7. Hash everything from the end of the checksum to the start of the Cert Directory. | |
// | |
if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { | |
// | |
// Use PE32 offset | |
// | |
HashBase = (UINT8 *)&Hdr.Pe32->OptionalHeader.CheckSum + sizeof (UINT32); | |
HashSize = (UINTN)(&Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]) - (UINTN)HashBase; | |
} else { | |
// | |
// Use PE32+ offset | |
// | |
HashBase = (UINT8 *)&Hdr.Pe32Plus->OptionalHeader.CheckSum + sizeof (UINT32); | |
HashSize = (UINTN)(&Hdr.Pe32Plus->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]) - (UINTN)HashBase; | |
} | |
if (HashSize != 0) { | |
Status = HashUpdate (HashHandle, HashBase, HashSize); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
} | |
// | |
// 8. Skip over the Cert Directory. (It is sizeof(IMAGE_DATA_DIRECTORY) bytes.) | |
// 9. Hash everything from the end of the Cert Directory to the end of image header. | |
// | |
if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { | |
// | |
// Use PE32 offset | |
// | |
HashBase = (UINT8 *)&Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY + 1]; | |
HashSize = Hdr.Pe32->OptionalHeader.SizeOfHeaders - (UINTN)(HashBase - ImageAddress); | |
} else { | |
// | |
// Use PE32+ offset | |
// | |
HashBase = (UINT8 *)&Hdr.Pe32Plus->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY + 1]; | |
HashSize = Hdr.Pe32Plus->OptionalHeader.SizeOfHeaders - (UINTN)(HashBase - ImageAddress); | |
} | |
if (HashSize != 0) { | |
Status = HashUpdate (HashHandle, HashBase, HashSize); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
} | |
} | |
// | |
// 10. Set the SUM_OF_BYTES_HASHED to the size of the header | |
// | |
if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { | |
// | |
// Use PE32 offset | |
// | |
SumOfBytesHashed = Hdr.Pe32->OptionalHeader.SizeOfHeaders; | |
} else { | |
// | |
// Use PE32+ offset | |
// | |
SumOfBytesHashed = Hdr.Pe32Plus->OptionalHeader.SizeOfHeaders; | |
} | |
// | |
// 11. Build a temporary table of pointers to all the IMAGE_SECTION_HEADER | |
// structures in the image. The 'NumberOfSections' field of the image | |
// header indicates how big the table should be. Do not include any | |
// IMAGE_SECTION_HEADERs in the table whose 'SizeOfRawData' field is zero. | |
// | |
SectionHeader = (EFI_IMAGE_SECTION_HEADER *)AllocateZeroPool (sizeof (EFI_IMAGE_SECTION_HEADER) * Hdr.Pe32->FileHeader.NumberOfSections); | |
if (SectionHeader == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto Finish; | |
} | |
// | |
// 12. Using the 'PointerToRawData' in the referenced section headers as | |
// a key, arrange the elements in the table in ascending order. In other | |
// words, sort the section headers according to the disk-file offset of | |
// the section. | |
// | |
Section = (EFI_IMAGE_SECTION_HEADER *)( | |
(UINT8 *)(UINTN)ImageAddress + | |
PeCoffHeaderOffset + | |
sizeof (UINT32) + | |
sizeof (EFI_IMAGE_FILE_HEADER) + | |
Hdr.Pe32->FileHeader.SizeOfOptionalHeader | |
); | |
for (Index = 0; Index < Hdr.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; | |
} | |
// | |
// 13. Walk through the sorted table, bring the corresponding section | |
// into memory, and hash the entire section (using the 'SizeOfRawData' | |
// field in the section header to determine the amount of data to hash). | |
// 14. Add the section's 'SizeOfRawData' to SUM_OF_BYTES_HASHED . | |
// 15. Repeat steps 13 and 14 for all the sections in the sorted table. | |
// | |
for (Index = 0; Index < Hdr.Pe32->FileHeader.NumberOfSections; Index++) { | |
Section = (EFI_IMAGE_SECTION_HEADER *)&SectionHeader[Index]; | |
if (Section->SizeOfRawData == 0) { | |
continue; | |
} | |
HashBase = (UINT8 *)(UINTN)ImageAddress + Section->PointerToRawData; | |
HashSize = (UINTN)Section->SizeOfRawData; | |
Status = HashUpdate (HashHandle, HashBase, HashSize); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
SumOfBytesHashed += HashSize; | |
} | |
// | |
// 16. If the file size is greater than SUM_OF_BYTES_HASHED, there is extra | |
// data in the file that needs to be added to the hash. This data begins | |
// at file offset SUM_OF_BYTES_HASHED and its length is: | |
// FileSize - (CertDirectory->Size) | |
// | |
if (ImageSize > SumOfBytesHashed) { | |
HashBase = (UINT8 *)(UINTN)ImageAddress + SumOfBytesHashed; | |
if (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_SECURITY) { | |
CertSize = 0; | |
} else { | |
if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { | |
// | |
// Use PE32 offset. | |
// | |
CertSize = Hdr.Pe32->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size; | |
} else { | |
// | |
// Use PE32+ offset. | |
// | |
CertSize = Hdr.Pe32Plus->OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size; | |
} | |
} | |
if (ImageSize > CertSize + SumOfBytesHashed) { | |
HashSize = (UINTN)(ImageSize - CertSize - SumOfBytesHashed); | |
Status = HashUpdate (HashHandle, HashBase, HashSize); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
} else if (ImageSize < CertSize + SumOfBytesHashed) { | |
Status = EFI_UNSUPPORTED; | |
goto Finish; | |
} | |
} | |
// | |
// 17. Finalize the SHA hash. | |
// | |
Status = HashCompleteAndExtend (HashHandle, PCRIndex, NULL, 0, DigestList); | |
if (EFI_ERROR (Status)) { | |
goto Finish; | |
} | |
Finish: | |
if (SectionHeader != NULL) { | |
FreePool (SectionHeader); | |
} | |
return Status; | |
} |