| /** @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/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] RtmrIndex Rtmr 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 RtmrIndex, | |
| 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, RtmrIndex, NULL, 0, DigestList); | |
| if (EFI_ERROR (Status)) { | |
| goto Finish; | |
| } | |
| Finish: | |
| if (SectionHeader != NULL) { | |
| FreePool (SectionHeader); | |
| } | |
| return Status; | |
| } |