From nobody Mon Feb 9 01:35:05 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) client-ip=66.175.222.108; envelope-from=bounce+27952+113757+1787277+3901457@groups.io; helo=mail02.groups.io; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+113757+1787277+3901457@groups.io ARC-Seal: i=1; a=rsa-sha256; t=1705086962; cv=none; d=zohomail.com; s=zohoarc; b=fJz1qzVYxJNwnqMMLAz/4dy0g5WTIxBckTa4cHtIO9jZvWYFHwR2hkWsvI4DJlp3x+2UOD5aA1lLNGpXjkZ4jZ6bd2z7+ty8RqHpqRqJuAvMBdP1Fcxjf+A02D/DggPJtjYjiXyidauhapBFHeMOByO/mVb/rO1bHj2+xb/UuM8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1705086962; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Id:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:Reply-To:References:Sender:Subject:Subject:To:To:Message-Id; bh=VPHSHOptWowlzOCnLq+NEXps2vkTupAV+//nCv94vOQ=; b=L9aZDiZKngFrwU4fFo9GT0/RRh6hyL94xso94yKztZow2NLR5Gc/47N9XRsj5IjL5foJJor2vHOUj8uYiC9oLdSDGs2MDjusmMRxcqeNaWKEGNk0JR6bPKZEDzZl+HjpKt2TnH7SEC853xbO/q16kzMK+q20/2hxtfIif9keLLg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce+27952+113757+1787277+3901457@groups.io Received: from mail02.groups.io (mail02.groups.io [66.175.222.108]) by mx.zohomail.com with SMTPS id 1705086962803490.4868596906259; Fri, 12 Jan 2024 11:16:02 -0800 (PST) Return-Path: DKIM-Signature: a=rsa-sha256; bh=6ShZkVz9cBf0nsc9gUNtxPIPwDpvh9Ew7LHXQ0KFgLE=; c=relaxed/simple; d=groups.io; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References:MIME-Version:Precedence:List-Subscribe:List-Help:Sender:List-Id:Mailing-List:Delivered-To:Reply-To:List-Unsubscribe-Post:List-Unsubscribe:Content-Transfer-Encoding; s=20140610; t=1705086962; v=1; b=SJbSpT3m+3lrAnKWv1dQXIArY3NhMM5l9XAGjr73/CAz+1+3lwngWbdmJ58e8+POHU+Ar51I L5vl5spiDBDNiX/hvB481k5H3vVTfipq6OD7blCGDJjk1M7mMTMu/MghcmNCV3yOIWvlQgbUfwI zO7JfSjQ2ZQH4oDSylcEgshw= X-Received: by 127.0.0.2 with SMTP id rAcYYY1788612x0Uf31Uqk0G; Fri, 12 Jan 2024 11:16:02 -0800 X-Received: from mail-pl1-f178.google.com (mail-pl1-f178.google.com [209.85.214.178]) by mx.groups.io with SMTP id smtpd.web11.18610.1704997014047649083 for ; Thu, 11 Jan 2024 10:16:54 -0800 X-Received: by mail-pl1-f178.google.com with SMTP id d9443c01a7336-1d3e8a51e6bso44965025ad.3 for ; Thu, 11 Jan 2024 10:16:54 -0800 (PST) X-Gm-Message-State: T8qnPqoyM8A3dM7eMNZMAGm3x1787277AA= X-Google-Smtp-Source: AGHT+IHzj/eKUDrdzsMSrCcGUnCxNcaGLdhT74jS77svIlZkEqUXTci1nYB13emAB2QPQ6YjTX6BiA== X-Received: by 2002:a17:902:e743:b0:1d3:fe01:17ec with SMTP id p3-20020a170902e74300b001d3fe0117ecmr174314plf.18.1704997012062; Thu, 11 Jan 2024 10:16:52 -0800 (PST) X-Received: from localhost.localdomain ([131.107.1.208]) by smtp.gmail.com with ESMTPSA id kd13-20020a17090313cd00b001d4752f5403sm1453414plb.206.2024.01.11.10.16.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 11 Jan 2024 10:16:51 -0800 (PST) From: "Doug Flick via groups.io" To: devel@edk2.groups.io Cc: "Douglas Flick [MSFT]" , Jiewen Yao Subject: [edk2-devel] [PATCH 1/6] SecurityPkg: DxeTpm2MeasureBootLib: SECURITY PATCH 4117 - CVE 2022-36763 Date: Thu, 11 Jan 2024 10:16:01 -0800 Message-ID: <991051b5e7572f9bd6abacef48275a3511e5a0c5.1704996627.git.doug.edk2@gmail.com> In-Reply-To: References: MIME-Version: 1.0 Precedence: Bulk List-Subscribe: List-Help: Sender: devel@edk2.groups.io List-Id: Mailing-List: list devel@edk2.groups.io; contact devel+owner@edk2.groups.io Reply-To: devel@edk2.groups.io,dougflick@microsoft.com List-Unsubscribe-Post: List-Unsubscribe=One-Click List-Unsubscribe: Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @groups.io) X-ZM-MESSAGEID: 1705086964274100006 Content-Type: text/plain; charset="utf-8" This commit contains the patch files and tests for DxeTpm2MeasureBootLib CVE 2022-36763. Cc: Jiewen Yao Signed-off-by: Doug Flick [MSFT] --- SecurityPkg/Test/SecurityPkgHostTest.dsc | 1 + .../DxeTpm2MeasureBootLib.inf | 4 +- ...Tpm2MeasureBootLibSanitizationTestHost.inf | 28 ++ .../DxeTpm2MeasureBootLibSanitization.h | 113 +++++++ .../DxeTpm2MeasureBootLib.c | 75 +++-- .../DxeTpm2MeasureBootLibSanitization.c | 275 ++++++++++++++++ .../DxeTpm2MeasureBootLibSanitizationTest.c | 303 ++++++++++++++++++ SecurityPkg/SecurityPkg.ci.yaml | 1 + 8 files changed, 767 insertions(+), 33 deletions(-) create mode 100644 SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitT= est/DxeTpm2MeasureBootLibSanitizationTestHost.inf create mode 100644 SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2Measur= eBootLibSanitization.h create mode 100644 SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2Measur= eBootLibSanitization.c create mode 100644 SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitT= est/DxeTpm2MeasureBootLibSanitizationTest.c diff --git a/SecurityPkg/Test/SecurityPkgHostTest.dsc b/SecurityPkg/Test/Se= curityPkgHostTest.dsc index ad5b4fc350ea..788c1ab6fec6 100644 --- a/SecurityPkg/Test/SecurityPkgHostTest.dsc +++ b/SecurityPkg/Test/SecurityPkgHostTest.dsc @@ -26,6 +26,7 @@ [Components] SecurityPkg/Library/SecureBootVariableLib/UnitTest/MockPlatformPKProtect= ionLib.inf SecurityPkg/Library/SecureBootVariableLib/UnitTest/MockUefiLib.inf SecurityPkg/Test/Mock/Library/GoogleTest/MockPlatformPKProtectionLib/Moc= kPlatformPKProtectionLib.inf + SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitTest/DxeTpm2Measur= eBootLibSanitizationTestHost.inf =20 # # Build SecurityPkg HOST_APPLICATION Tests diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLi= b.inf b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf index 6dca79a20c93..28995f438de6 100644 --- a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf +++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.inf @@ -37,6 +37,8 @@ [Defines] =20 [Sources] DxeTpm2MeasureBootLib.c + DxeTpm2MeasureBootLibSanitization.c + DxeTpm2MeasureBootLibSanitization.h =20 [Packages] MdePkg/MdePkg.dec @@ -46,6 +48,7 @@ [Packages] =20 [LibraryClasses] BaseMemoryLib + SafeIntLib DebugLib MemoryAllocationLib DevicePathLib @@ -65,4 +68,3 @@ [Protocols] gEfiFirmwareVolumeBlockProtocolGuid ## SOMETIMES_CONSUMES gEfiBlockIoProtocolGuid ## SOMETIMES_CONSUMES gEfiDiskIoProtocolGuid ## SOMETIMES_CONSUMES - diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitTest/Dxe= Tpm2MeasureBootLibSanitizationTestHost.inf b/SecurityPkg/Library/DxeTpm2Mea= sureBootLib/InternalUnitTest/DxeTpm2MeasureBootLibSanitizationTestHost.inf new file mode 100644 index 000000000000..2999aa2a44e0 --- /dev/null +++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitTest/DxeTpm2Mea= sureBootLibSanitizationTestHost.inf @@ -0,0 +1,28 @@ +## @file +# This file builds the unit tests for DxeTpm2MeasureBootLib +# +# Copyright (C) Microsoft Corporation.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION =3D 0x00010006 + BASE_NAME =3D DxeTpm2MeasuredBootLibTest + FILE_GUID =3D 144d757f-d423-484e-9309-a23695fad5bd + MODULE_TYPE =3D HOST_APPLICATION + VERSION_STRING =3D 1.0 + ENTRY_POINT =3D main + +[Sources] + DxeTpm2MeasureBootLibSanitizationTest.c + ../DxeTpm2MeasureBootLibSanitization.c + +[Packages] + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + UnitTestLib + PrintLib + SafeIntLib diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLi= bSanitization.h b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureB= ootLibSanitization.h new file mode 100644 index 000000000000..048b73898744 --- /dev/null +++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLibSaniti= zation.h @@ -0,0 +1,113 @@ +/** @file + This file includes the function prototypes for the sanitization function= s. + + These are those functions: + + DxeTpm2MeasureBootLibImageRead() function will make sure the PE/COFF ima= ge content + read is within the image buffer. + + Tcg2MeasureGptTable() function will receive untrusted GPT partition tabl= e, and parse + partition data carefully. + + Copyright (c) Microsoft Corporation.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef DXE_TPM2_MEASURE_BOOT_LIB_SANITATION_ +#define DXE_TPM2_MEASURE_BOOT_LIB_SANITATION_ + +#include +#include +#include +#include +#include + +/** + This function will validate the EFI_PARTITION_TABLE_HEADER structure is = safe to parse + However this function will not attempt to verify the validity of the GPT= partition + It will check the following: + - Signature + - Revision + - AlternateLBA + - FirstUsableLBA + - LastUsableLBA + - PartitionEntryLBA + - NumberOfPartitionEntries + - SizeOfPartitionEntry + - BlockIo + + @param[in] PrimaryHeader + Pointer to the EFI_PARTITION_TABLE_HEADER structure. + + @param[in] BlockIo + Pointer to the EFI_BLOCK_IO_PROTOCOL structure. + + @retval EFI_SUCCESS + The EFI_PARTITION_TABLE_HEADER structure is valid. + + @retval EFI_INVALID_PARAMETER + The EFI_PARTITION_TABLE_HEADER structure is invalid. +**/ +EFI_STATUS +EFIAPI +SanitizeEfiPartitionTableHeader ( + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader, + IN CONST EFI_BLOCK_IO_PROTOCOL *BlockIo + ); + +/** + This function will validate that the allocation size from the primary he= ader is sane + It will check the following: + - AllocationSize does not overflow + + @param[in] PrimaryHeader + Pointer to the EFI_PARTITION_TABLE_HEADER structure. + + @param[out] AllocationSize + Pointer to the allocation size. + + @retval EFI_SUCCESS + The allocation size is valid. + + @retval EFI_OUT_OF_RESOURCES + The allocation size is invalid. +**/ +EFI_STATUS +EFIAPI +SanitizePrimaryHeaderAllocationSize ( + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader, + OUT UINT32 *AllocationSize + ); + +/** + This function will validate that the Gpt Event Size calculated from the = primary header is sane + It will check the following: + - EventSize does not overflow + + Important: This function includes the entire length of the allocated spa= ce, including + (sizeof (EFI_TCG2_EVENT) - sizeof (Tcg2Event->Event)) . When hashing the= buffer allocated with this + size, the caller must subtract the size of the (sizeof (EFI_TCG2_EVENT) = - sizeof (Tcg2Event->Event)) + from the size of the buffer before hashing. + + @param[in] PrimaryHeader - Pointer to the EFI_PARTITION_TABLE_HEADER str= ucture. + @param[in] NumberOfPartition - Number of partitions. + @param[out] EventSize - Pointer to the event size. + + @retval EFI_SUCCESS + The event size is valid. + + @retval EFI_OUT_OF_RESOURCES + Overflow would have occurred. + + @retval EFI_INVALID_PARAMETER + One of the passed parameters was invalid. +**/ +EFI_STATUS +SanitizePrimaryHeaderGptEventSize ( + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader, + IN UINTN NumberOfPartition, + OUT UINT32 *EventSize + ); + +#endif // DXE_TPM2_MEASURE_BOOT_LIB_SANITATION_ diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLi= b.c b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c index 36a256a7af50..0475103d6ef8 100644 --- a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c +++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c @@ -20,6 +20,8 @@ Copyright (c) 2013 - 2018, Intel Corporation. All rights = reserved.
(C) Copyright 2015 Hewlett Packard Enterprise Development LP
SPDX-License-Identifier: BSD-2-Clause-Patent =20 +Copyright (c) Microsoft Corporation.
+SPDX-License-Identifier: BSD-2-Clause-Patent **/ =20 #include @@ -44,6 +46,8 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include #include =20 +#include "DxeTpm2MeasureBootLibSanitization.h" + typedef struct { EFI_TCG2_PROTOCOL *Tcg2Protocol; EFI_CC_MEASUREMENT_PROTOCOL *CcProtocol; @@ -144,10 +148,11 @@ Tcg2MeasureGptTable ( EFI_TCG2_EVENT *Tcg2Event; EFI_CC_EVENT *CcEvent; EFI_GPT_DATA *GptData; - UINT32 EventSize; + UINT32 TcgEventSize; EFI_TCG2_PROTOCOL *Tcg2Protocol; EFI_CC_MEASUREMENT_PROTOCOL *CcProtocol; EFI_CC_MR_INDEX MrIndex; + UINT32 AllocSize; =20 if (mTcg2MeasureGptCount > 0) { return EFI_SUCCESS; @@ -195,25 +200,22 @@ Tcg2MeasureGptTable ( BlockIo->Media->BlockSize, (UINT8 *)PrimaryHeader ); - if (EFI_ERROR (Status)) { - DEBUG ((DEBUG_ERROR, "Failed to Read Partition Table Header!\n")); + if (EFI_ERROR (Status) || EFI_ERROR (SanitizeEfiPartitionTableHeader (Pr= imaryHeader, BlockIo))) { + DEBUG ((DEBUG_ERROR, "Failed to read Partition Table Header or invalid= Partition Table Header!\n")); FreePool (PrimaryHeader); return EFI_DEVICE_ERROR; } =20 - // - // PrimaryHeader->SizeOfPartitionEntry should not be zero - // - if (PrimaryHeader->SizeOfPartitionEntry =3D=3D 0) { - DEBUG ((DEBUG_ERROR, "SizeOfPartitionEntry should not be zero!\n")); - FreePool (PrimaryHeader); - return EFI_BAD_BUFFER_SIZE; - } - // // Read the partition entry. // - EntryPtr =3D (UINT8 *)AllocatePool (PrimaryHeader->NumberOfPartitionEntr= ies * PrimaryHeader->SizeOfPartitionEntry); + Status =3D SanitizePrimaryHeaderAllocationSize (PrimaryHeader, &AllocSiz= e); + if (EFI_ERROR (Status)) { + FreePool (PrimaryHeader); + return EFI_BAD_BUFFER_SIZE; + } + + EntryPtr =3D (UINT8 *)AllocatePool (AllocSize); if (EntryPtr =3D=3D NULL) { FreePool (PrimaryHeader); return EFI_OUT_OF_RESOURCES; @@ -223,7 +225,7 @@ Tcg2MeasureGptTable ( DiskIo, BlockIo->Media->MediaId, MultU64x32 (PrimaryHeader->PartitionEntryLBA, BlockIo= ->Media->BlockSize), - PrimaryHeader->NumberOfPartitionEntries * PrimaryHead= er->SizeOfPartitionEntry, + AllocSize, EntryPtr ); if (EFI_ERROR (Status)) { @@ -248,16 +250,21 @@ Tcg2MeasureGptTable ( // // Prepare Data for Measurement (CcProtocol and Tcg2Protocol) // - EventSize =3D (UINT32)(sizeof (EFI_GPT_DATA) - sizeof (GptData->Partitio= ns) - + NumberOfPartition * PrimaryHeader->SizeOfPartitio= nEntry); - EventPtr =3D (UINT8 *)AllocateZeroPool (EventSize + sizeof (EFI_TCG2_EVE= NT) - sizeof (Tcg2Event->Event)); + Status =3D SanitizePrimaryHeaderGptEventSize (PrimaryHeader, NumberOfPar= tition, &TcgEventSize); + if (EFI_ERROR (Status)) { + FreePool (PrimaryHeader); + FreePool (EntryPtr); + return EFI_DEVICE_ERROR; + } + + EventPtr =3D (UINT8 *)AllocateZeroPool (TcgEventSize); if (EventPtr =3D=3D NULL) { Status =3D EFI_OUT_OF_RESOURCES; goto Exit; } =20 Tcg2Event =3D (EFI_TCG2_EVENT *)EventPtr; - Tcg2Event->Size =3D EventSize + sizeof (EFI_TCG2_EVENT) = - sizeof (Tcg2Event->Event); + Tcg2Event->Size =3D TcgEventSize; Tcg2Event->Header.HeaderSize =3D sizeof (EFI_TCG2_EVENT_HEADER); Tcg2Event->Header.HeaderVersion =3D EFI_TCG2_EVENT_HEADER_VERSION; Tcg2Event->Header.PCRIndex =3D 5; @@ -310,7 +317,7 @@ Tcg2MeasureGptTable ( CcProtocol, 0, (EFI_PHYSICAL_ADDRESS)(UINTN)(= VOID *)GptData, - (UINT64)EventSize, + (UINT64)TcgEventSize - OFFSET_= OF (EFI_TCG2_EVENT, Event), CcEvent ); if (!EFI_ERROR (Status)) { @@ -326,7 +333,7 @@ Tcg2MeasureGptTable ( Tcg2Protocol, 0, (EFI_PHYSICAL_ADDRESS)(UINTN)(VOID *)GptData, - (UINT64)EventSize, + (UINT64)TcgEventSize - OFFSET_OF (EFI_TCG2_E= VENT, Event), Tcg2Event ); if (!EFI_ERROR (Status)) { @@ -443,11 +450,13 @@ Tcg2MeasurePeImage ( Tcg2Event->Header.PCRIndex =3D 2; break; default: - DEBUG (( - DEBUG_ERROR, - "Tcg2MeasurePeImage: Unknown subsystem type %d", - ImageType - )); + DEBUG ( + ( + DEBUG_ERROR, + "Tcg2MeasurePeImage: Unknown subsystem type %d", + ImageType + ) + ); goto Finish; } =20 @@ -515,7 +524,7 @@ Finish: =20 @param MeasureBootProtocols Pointer to the located measure boot protoc= ol instances. =20 - @retval EFI_SUCCESS Sucessfully locate the measure boot protoc= ol instances (at least one instance). + @retval EFI_SUCCESS Successfully locate the measure boot proto= col instances (at least one instance). @retval EFI_UNSUPPORTED Measure boot is not supported. **/ EFI_STATUS @@ -646,12 +655,14 @@ DxeTpm2MeasureBootHandler ( return EFI_SUCCESS; } =20 - DEBUG (( - DEBUG_INFO, - "Tcg2Protocol =3D %p, CcMeasurementProtocol =3D %p\n", - MeasureBootProtocols.Tcg2Protocol, - MeasureBootProtocols.CcProtocol - )); + DEBUG ( + ( + DEBUG_INFO, + "Tcg2Protocol =3D %p, CcMeasurementProtocol =3D %p\n", + MeasureBootProtocols.Tcg2Protocol, + MeasureBootProtocols.CcProtocol + ) + ); =20 // // Copy File Device Path diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLi= bSanitization.c b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureB= ootLibSanitization.c new file mode 100644 index 000000000000..e2309655d384 --- /dev/null +++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLibSaniti= zation.c @@ -0,0 +1,275 @@ +/** @file + The library instance provides security service of TPM2 measure boot and + Confidential Computing (CC) measure boot. + + Caution: This file requires additional review when modified. + This library will have external input - PE/COFF image and GPT partition. + This external input must be validated carefully to avoid security issue = like + buffer overflow, integer overflow. + + This file will pull out the validation logic from the following function= s, in an + attempt to validate the untrusted input in the form of unit tests + + These are those functions: + + DxeTpm2MeasureBootLibImageRead() function will make sure the PE/COFF ima= ge content + read is within the image buffer. + + Tcg2MeasureGptTable() function will receive untrusted GPT partition tabl= e, and parse + partition data carefully. + + Copyright (c) Microsoft Corporation.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DxeTpm2MeasureBootLibSanitization.h" + +#define GPT_HEADER_REVISION_V1 0x00010000 + +/** + This function will validate the EFI_PARTITION_TABLE_HEADER structure is = safe to parse + However this function will not attempt to verify the validity of the GPT= partition + It will check the following: + - Signature + - Revision + - AlternateLBA + - FirstUsableLBA + - LastUsableLBA + - PartitionEntryLBA + - NumberOfPartitionEntries + - SizeOfPartitionEntry + - BlockIo + + @param[in] PrimaryHeader + Pointer to the EFI_PARTITION_TABLE_HEADER structure. + + @param[in] BlockIo + Pointer to the EFI_BLOCK_IO_PROTOCOL structure. + + @retval EFI_SUCCESS + The EFI_PARTITION_TABLE_HEADER structure is valid. + + @retval EFI_INVALID_PARAMETER + The EFI_PARTITION_TABLE_HEADER structure is invalid. +**/ +EFI_STATUS +EFIAPI +SanitizeEfiPartitionTableHeader ( + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader, + IN CONST EFI_BLOCK_IO_PROTOCOL *BlockIo + ) +{ + // + // Verify that the input parameters are safe to use + // + if (PrimaryHeader =3D=3D NULL) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header!\n")); + return EFI_INVALID_PARAMETER; + } + + if ((BlockIo =3D=3D NULL) || (BlockIo->Media =3D=3D NULL)) { + DEBUG ((DEBUG_ERROR, "Invalid BlockIo!\n")); + return EFI_INVALID_PARAMETER; + } + + // + // The signature must be EFI_PTAB_HEADER_ID ("EFI PART" in ASCII) + // + if (PrimaryHeader->Header.Signature !=3D EFI_PTAB_HEADER_ID) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header!\n")); + return EFI_DEVICE_ERROR; + } + + // + // The version must be GPT_HEADER_REVISION_V1 (0x00010000) + // + if (PrimaryHeader->Header.Revision !=3D GPT_HEADER_REVISION_V1) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header Revision!\n")); + return EFI_DEVICE_ERROR; + } + + // + // The HeaderSize must be greater than or equal to 92 and must be less t= han or equal to the logical block size + // + if ((PrimaryHeader->Header.HeaderSize < sizeof (EFI_PARTITION_TABLE_HEAD= ER)) || (PrimaryHeader->Header.HeaderSize > BlockIo->Media->BlockSize)) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header HeaderSize!\n")); + return EFI_DEVICE_ERROR; + } + + // + // The partition entries should all be before the first usable block + // + if (PrimaryHeader->FirstUsableLBA <=3D PrimaryHeader->PartitionEntryLBA)= { + DEBUG ((DEBUG_ERROR, "GPT PartitionEntryLBA is not less than FirstUsab= leLBA!\n")); + return EFI_DEVICE_ERROR; + } + + // + // Check that the PartitionEntryLBA greater than the Max LBA + // This will be used later for multiplication + // + if (PrimaryHeader->PartitionEntryLBA > DivU64x32 (MAX_UINT64, BlockIo->M= edia->BlockSize)) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header PartitionEntryLBA= !\n")); + return EFI_DEVICE_ERROR; + } + + // + // Check that the number of partition entries is greater than zero + // + if (PrimaryHeader->NumberOfPartitionEntries =3D=3D 0) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header NumberOfPartition= Entries!\n")); + return EFI_DEVICE_ERROR; + } + + // + // SizeOfPartitionEntry must be 128, 256, 512... improper size may lead = to accessing uninitialized memory + // + if ((PrimaryHeader->SizeOfPartitionEntry < 128) || ((PrimaryHeader->Size= OfPartitionEntry & (PrimaryHeader->SizeOfPartitionEntry - 1)) !=3D 0)) { + DEBUG ((DEBUG_ERROR, "SizeOfPartitionEntry shall be set to a value of = 128 x 2^n where n is an integer greater than or equal to zero (e.g., 128, 2= 56, 512, etc.)!\n")); + return EFI_DEVICE_ERROR; + } + + // + // This check is to prevent overflow when calculating the allocation siz= e for the partition entries + // This check will be used later for multiplication + // + if (PrimaryHeader->NumberOfPartitionEntries > DivU64x32 (MAX_UINT64, Pri= maryHeader->SizeOfPartitionEntry)) { + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header NumberOfPartition= Entries!\n")); + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} + +/** + This function will validate that the allocation size from the primary he= ader is sane + It will check the following: + - AllocationSize does not overflow + + @param[in] PrimaryHeader + Pointer to the EFI_PARTITION_TABLE_HEADER structure. + + @param[out] AllocationSize + Pointer to the allocation size. + + @retval EFI_SUCCESS + The allocation size is valid. + + @retval EFI_OUT_OF_RESOURCES + The allocation size is invalid. +**/ +EFI_STATUS +EFIAPI +SanitizePrimaryHeaderAllocationSize ( + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader, + OUT UINT32 *AllocationSize + ) +{ + EFI_STATUS Status; + + if (PrimaryHeader =3D=3D NULL) { + return EFI_INVALID_PARAMETER; + } + + if (AllocationSize =3D=3D NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // Replacing logic: + // PrimaryHeader->NumberOfPartitionEntries * PrimaryHeader->SizeOfPartit= ionEntry; + // + Status =3D SafeUint32Mult (PrimaryHeader->NumberOfPartitionEntries, Prim= aryHeader->SizeOfPartitionEntry, AllocationSize); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Allocation Size would have overflowed!\n")); + return EFI_BAD_BUFFER_SIZE; + } + + return EFI_SUCCESS; +} + +/** + This function will validate that the Gpt Event Size calculated from the = primary header is sane + It will check the following: + - EventSize does not overflow + + Important: This function includes the entire length of the allocated spa= ce, including + (sizeof (EFI_TCG2_EVENT) - sizeof (Tcg2Event->Event)) . When hashing the= buffer allocated with this + size, the caller must subtract the size of the (sizeof (EFI_TCG2_EVENT) = - sizeof (Tcg2Event->Event)) + from the size of the buffer before hashing. + + @param[in] PrimaryHeader - Pointer to the EFI_PARTITION_TABLE_HEADER str= ucture. + @param[in] NumberOfPartition - Number of partitions. + @param[out] EventSize - Pointer to the event size. + + @retval EFI_SUCCESS + The event size is valid. + + @retval EFI_OUT_OF_RESOURCES + Overflow would have occurred. + + @retval EFI_INVALID_PARAMETER + One of the passed parameters was invalid. +**/ +EFI_STATUS +SanitizePrimaryHeaderGptEventSize ( + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader, + IN UINTN NumberOfPartition, + OUT UINT32 *EventSize + ) +{ + EFI_STATUS Status; + UINT32 SafeNumberOfPartitions; + + if (PrimaryHeader =3D=3D NULL) { + return EFI_INVALID_PARAMETER; + } + + if (EventSize =3D=3D NULL) { + return EFI_INVALID_PARAMETER; + } + + // + // We shouldn't even attempt to perform the multiplication if the number= of partitions is greater than the maximum value of UINT32 + // + Status =3D SafeUintnToUint32 (NumberOfPartition, &SafeNumberOfPartitions= ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "NumberOfPartition would have overflowed!\n")); + return EFI_INVALID_PARAMETER; + } + + // + // Replacing logic: + // (UINT32)(sizeof (EFI_GPT_DATA) - sizeof (GptData->Partitions) + Numbe= rOfPartition * PrimaryHeader.SizeOfPartitionEntry); + // + Status =3D SafeUint32Mult (SafeNumberOfPartitions, PrimaryHeader->SizeOf= PartitionEntry, EventSize); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Event Size would have overflowed!\n")); + return EFI_BAD_BUFFER_SIZE; + } + + // + // Replacing logic: + // *EventSize + sizeof (EFI_TCG2_EVENT) - sizeof (Tcg2Event->Event); + // + Status =3D SafeUint32Add ( + OFFSET_OF (EFI_TCG2_EVENT, Event) + OFFSET_OF (EFI_GPT_DATA, = Partitions), + *EventSize, + EventSize + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Event Size would have overflowed because of GPTD= ata!\n")); + return EFI_BAD_BUFFER_SIZE; + } + + return EFI_SUCCESS; +} diff --git a/SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitTest/Dxe= Tpm2MeasureBootLibSanitizationTest.c b/SecurityPkg/Library/DxeTpm2MeasureBo= otLib/InternalUnitTest/DxeTpm2MeasureBootLibSanitizationTest.c new file mode 100644 index 000000000000..3eb9763e3c91 --- /dev/null +++ b/SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitTest/DxeTpm2Mea= sureBootLibSanitizationTest.c @@ -0,0 +1,303 @@ +/** @file + This file includes the unit test cases for the DxeTpm2MeasureBootLibSani= tizationTest.c. + + Copyright (c) Microsoft Corporation.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../DxeTpm2MeasureBootLibSanitization.h" + +#define UNIT_TEST_NAME "DxeTpm2MeasureBootLibSanitizationTest" +#define UNIT_TEST_VERSION "1.0" + +#define DEFAULT_PRIMARY_TABLE_HEADER_REVISION 0x000100= 00 +#define DEFAULT_PRIMARY_TABLE_HEADER_NUMBER_OF_PARTITION_ENTRIES 1 +#define DEFAULT_PRIMARY_TABLE_HEADER_SIZE_OF_PARTITION_ENTRY 128 + +/** + This function tests the SanitizeEfiPartitionTableHeader function. + It's intent is to test that a malicious EFI_PARTITION_TABLE_HEADER + structure will not cause undefined or unexpected behavior. + + In general the TPM should still be able to measure the data, but + be the header should be sanitized to prevent any unexpected behavior. + + @param[in] Context The unit test context. + + @retval UNIT_TEST_PASSED The test passed. + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestSanitizeEfiPartitionTableHeader ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_PARTITION_TABLE_HEADER PrimaryHeader; + EFI_BLOCK_IO_PROTOCOL BlockIo; + EFI_BLOCK_IO_MEDIA BlockMedia; + + // Generate EFI_BLOCK_IO_MEDIA test data + BlockMedia.MediaId =3D 1; + BlockMedia.RemovableMedia =3D FALSE; + BlockMedia.MediaPresent =3D TRUE; + BlockMedia.LogicalPartition =3D FALSE; + BlockMedia.ReadOnly =3D FALSE; + BlockMedia.WriteCaching =3D FALSE; + BlockMedia.BlockSize =3D 512; + BlockMedia.IoAlign =3D 1; + BlockMedia.LastBlock =3D 0; + + // Generate EFI_BLOCK_IO_PROTOCOL test data + BlockIo.Revision =3D 1; + BlockIo.Media =3D &BlockMedia; + BlockIo.Reset =3D NULL; + BlockIo.ReadBlocks =3D NULL; + BlockIo.WriteBlocks =3D NULL; + BlockIo.FlushBlocks =3D NULL; + + // Geneate EFI_PARTITION_TABLE_HEADER test data + PrimaryHeader.Header.Signature =3D EFI_PTAB_HEADER_ID; + PrimaryHeader.Header.Revision =3D DEFAULT_PRIMARY_TABLE_HEADER_= REVISION; + PrimaryHeader.Header.HeaderSize =3D sizeof (EFI_PARTITION_TABLE_H= EADER); + PrimaryHeader.MyLBA =3D 1; + PrimaryHeader.AlternateLBA =3D 2; + PrimaryHeader.FirstUsableLBA =3D 3; + PrimaryHeader.LastUsableLBA =3D 4; + PrimaryHeader.PartitionEntryLBA =3D 5; + PrimaryHeader.NumberOfPartitionEntries =3D DEFAULT_PRIMARY_TABLE_HEADER_= NUMBER_OF_PARTITION_ENTRIES; + PrimaryHeader.SizeOfPartitionEntry =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY; + PrimaryHeader.PartitionEntryArrayCRC32 =3D 0; // Purposely invalid + + // Calculate the CRC32 of the PrimaryHeader + PrimaryHeader.Header.CRC32 =3D CalculateCrc32 ((UINT8 *)&PrimaryHeader, = PrimaryHeader.Header.HeaderSize); + + // Test that a normal PrimaryHeader passes validation + Status =3D SanitizeEfiPartitionTableHeader (&PrimaryHeader, &BlockIo); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Test that when number of partition entries is 0, the function returns= EFI_DEVICE_ERROR + // Should print "Invalid Partition Table Header NumberOfPartitionEntries= !"" + PrimaryHeader.NumberOfPartitionEntries =3D 0; + Status =3D SanitizeEfiPartitionTableHead= er (&PrimaryHeader, &BlockIo); + UT_ASSERT_EQUAL (Status, EFI_DEVICE_ERROR); + PrimaryHeader.NumberOfPartitionEntries =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY; + + // Test that when the header size is too small, the function returns EFI= _DEVICE_ERROR + // Should print "Invalid Partition Table Header Size!" + PrimaryHeader.Header.HeaderSize =3D 0; + Status =3D SanitizeEfiPartitionTableHeader (&Pr= imaryHeader, &BlockIo); + UT_ASSERT_EQUAL (Status, EFI_DEVICE_ERROR); + PrimaryHeader.Header.HeaderSize =3D sizeof (EFI_PARTITION_TABLE_HEADER); + + // Test that when the SizeOfPartitionEntry is too small, the function re= turns EFI_DEVICE_ERROR + // should print: "SizeOfPartitionEntry shall be set to a value of 128 x = 2^n where n is an integer greater than or equal to zero (e.g., 128, 256, 51= 2, etc.)!" + PrimaryHeader.SizeOfPartitionEntry =3D 1; + Status =3D SanitizeEfiPartitionTableHeader (= &PrimaryHeader, &BlockIo); + UT_ASSERT_EQUAL (Status, EFI_DEVICE_ERROR); + + DEBUG ((DEBUG_INFO, "%a: Test passed\n", __func__)); + + return UNIT_TEST_PASSED; +} + +/** + This function tests the SanitizePrimaryHeaderAllocationSize function. + It's intent is to test that the untrusted input from a EFI_PARTITION_TAB= LE_HEADER + structure will not cause an overflow when calculating the allocation siz= e. + + @param[in] Context The unit test context. + + @retval UNIT_TEST_PASSED The test passed. + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestSanitizePrimaryHeaderAllocationSize ( + IN UNIT_TEST_CONTEXT Context + ) +{ + UINT32 AllocationSize; + + EFI_STATUS Status; + EFI_PARTITION_TABLE_HEADER PrimaryHeader; + + // Test that a normal PrimaryHeader passes validation + PrimaryHeader.NumberOfPartitionEntries =3D 5; + PrimaryHeader.SizeOfPartitionEntry =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY; + + Status =3D SanitizePrimaryHeaderAllocationSize (&PrimaryHeader, &Allocat= ionSize); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Test that the allocation size is correct compared to the existing log= ic + UT_ASSERT_EQUAL (AllocationSize, PrimaryHeader.NumberOfPartitionEntries = * PrimaryHeader.SizeOfPartitionEntry); + + // Test that an overflow is detected + PrimaryHeader.NumberOfPartitionEntries =3D MAX_UINT32; + PrimaryHeader.SizeOfPartitionEntry =3D 5; + Status =3D SanitizePrimaryHeaderAllocati= onSize (&PrimaryHeader, &AllocationSize); + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE); + + // Test the inverse + PrimaryHeader.NumberOfPartitionEntries =3D 5; + PrimaryHeader.SizeOfPartitionEntry =3D MAX_UINT32; + Status =3D SanitizePrimaryHeaderAllocati= onSize (&PrimaryHeader, &AllocationSize); + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE); + + // Test the worst case scenario + PrimaryHeader.NumberOfPartitionEntries =3D MAX_UINT32; + PrimaryHeader.SizeOfPartitionEntry =3D MAX_UINT32; + Status =3D SanitizePrimaryHeaderAllocati= onSize (&PrimaryHeader, &AllocationSize); + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE); + + DEBUG ((DEBUG_INFO, "%a: Test passed\n", __func__)); + + return UNIT_TEST_PASSED; +} + +/** + This function tests the SanitizePrimaryHeaderGptEventSize function. + It's intent is to test that the untrusted input from a EFI_GPT_DATA stru= cture + will not cause an overflow when calculating the event size. + + @param[in] Context The unit test context. + + @retval UNIT_TEST_PASSED The test passed. + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed. +**/ +UNIT_TEST_STATUS +EFIAPI +TestSanitizePrimaryHeaderGptEventSize ( + IN UNIT_TEST_CONTEXT Context + ) +{ + UINT32 EventSize; + UINT32 ExistingLogicEventSize; + EFI_STATUS Status; + EFI_PARTITION_TABLE_HEADER PrimaryHeader; + UINTN NumberOfPartition; + EFI_GPT_DATA *GptData; + EFI_TCG2_EVENT *Tcg2Event; + + Tcg2Event =3D NULL; + GptData =3D NULL; + + // Test that a normal PrimaryHeader passes validation + PrimaryHeader.NumberOfPartitionEntries =3D 5; + PrimaryHeader.SizeOfPartitionEntry =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY; + + // set the number of partitions + NumberOfPartition =3D 13; + + // that the primary event size is correct + Status =3D SanitizePrimaryHeaderGptEventSize (&PrimaryHeader, NumberOfPa= rtition, &EventSize); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Calculate the existing logic event size + ExistingLogicEventSize =3D (UINT32)(OFFSET_OF (EFI_TCG2_EVENT, Event) + = OFFSET_OF (EFI_GPT_DATA, Partitions) + + NumberOfPartition * PrimaryHeader.Si= zeOfPartitionEntry); + + // Check that the event size is correct + UT_ASSERT_EQUAL (EventSize, ExistingLogicEventSize); + + // Tests that the primary event size may not overflow + Status =3D SanitizePrimaryHeaderGptEventSize (&PrimaryHeader, MAX_UINT32= , &EventSize); + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE); + + // Test that the size of partition entries may not overflow + PrimaryHeader.SizeOfPartitionEntry =3D MAX_UINT32; + Status =3D SanitizePrimaryHeaderGptEventSize= (&PrimaryHeader, NumberOfPartition, &EventSize); + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE); + + DEBUG ((DEBUG_INFO, "%a: Test passed\n", __func__)); + + return UNIT_TEST_PASSED; +} + +// *--------------------------------------------------------------------* +// * Unit Test Code Main Function +// *--------------------------------------------------------------------* + +/** + This function acts as the entry point for the unit tests. + + @retval UNIT_TEST_PASSED The test passed. + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed. + @retval others The test failed. +**/ +EFI_STATUS +EFIAPI +UefiTestMain ( + VOID + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE Tcg2MeasureBootLibValidationTestSuite; + + Framework =3D NULL; + + DEBUG ((DEBUG_INFO, "%a: TestMain() - Start\n", UNIT_TEST_NAME)); + + Status =3D InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCaller= BaseName, UNIT_TEST_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%a: Failed in InitUnitTestFramework. Status =3D = %r\n", UNIT_TEST_NAME, Status)); + goto EXIT; + } + + Status =3D CreateUnitTestSuite (&Tcg2MeasureBootLibValidationTestSuite, = Framework, "Tcg2MeasureBootLibValidationTestSuite", "Common.Tcg2MeasureBoot= LibValidation", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "%s: Failed in CreateUnitTestSuite for Tcg2Measur= eBootLibValidationTestSuite\n", UNIT_TEST_NAME)); + Status =3D EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + // -----------Suite---------------------------------Description---------= -------------------Class----------------------------------Test Function----= --------------------Pre---Clean-Context + AddTestCase (Tcg2MeasureBootLibValidationTestSuite, "Tests Validating EF= I Partition Table", "Common.Tcg2MeasureBootLibValidation", TestSanitizeEfiP= artitionTableHeader, NULL, NULL, NULL); + AddTestCase (Tcg2MeasureBootLibValidationTestSuite, "Tests Primary heade= r gpt event checks for overflow", "Common.Tcg2MeasureBootLibValidation", Te= stSanitizePrimaryHeaderAllocationSize, NULL, NULL, NULL); + AddTestCase (Tcg2MeasureBootLibValidationTestSuite, "Tests Primary heade= r allocation size checks for overflow", "Common.Tcg2MeasureBootLibValidatio= n", TestSanitizePrimaryHeaderGptEventSize, NULL, NULL, NULL); + + Status =3D RunAllTestSuites (Framework); + +EXIT: + if (Framework !=3D NULL) { + FreeUnitTestFramework (Framework); + } + + DEBUG ((DEBUG_INFO, "%a: TestMain() - End\n", UNIT_TEST_NAME)); + return Status; +} + +/// +/// Avoid ECC error for function name that starts with lower case letter +/// +#define DxeTpm2MeasureBootLibUnitTestMain main + +/** + Standard POSIX C entry point for host based unit test execution. + + @param[in] Argc Number of arguments + @param[in] Argv Array of pointers to arguments + + @retval 0 Success + @retval other Error +**/ +INT32 +DxeTpm2MeasureBootLibUnitTestMain ( + IN INT32 Argc, + IN CHAR8 *Argv[] + ) +{ + return (INT32)UefiTestMain (); +} diff --git a/SecurityPkg/SecurityPkg.ci.yaml b/SecurityPkg/SecurityPkg.ci.y= aml index 3f03762bd6f9..24389531afaa 100644 --- a/SecurityPkg/SecurityPkg.ci.yaml +++ b/SecurityPkg/SecurityPkg.ci.yaml @@ -16,6 +16,7 @@ ## ] "ExceptionList": [ "8005", "gRT", + "8001", "DxeTpm2MeasureBootLibUnitTestMain", ], ## Both file path and directory path are accepted. "IgnoreFiles": [ --=20 2.43.0 -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#113757): https://edk2.groups.io/g/devel/message/113757 Mute This Topic: https://groups.io/mt/103689720/1787277 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [importer@patchew.org] -=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-