From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mail02.groups.io (mail02.groups.io [66.175.222.108]) by spool.mail.gandi.net (Postfix) with ESMTPS id 94B12AC18A9 for ; Fri, 12 Jan 2024 19:16:05 +0000 (UTC) DKIM-Signature: a=rsa-sha256; bh=SbFrnvfN5bthVM75HsNvMLNHmd8k+9eTTUAgLHYvXok=; 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=1705086964; v=1; b=Kuw30GsOWnHBUz9FuQkCN0Q7zVsdTXriGMvXnRGB0VNT2XGD+Bb1qqYMrIy2yfMnJptNVG/7 SrhIQv5zZzFbt7kkirv39cgKmn8DDV2Fgp3dYW5YqssBP/9g76pHegqbZMgPW6jD55F2XgfuK0X 1NwPrKANz/O3T8bDUm+uUhBw= X-Received: by 127.0.0.2 with SMTP id XqapYY7687511xwY5TZ98zMC; Fri, 12 Jan 2024 11:16:04 -0800 X-Received: from mail-pl1-f171.google.com (mail-pl1-f171.google.com [209.85.214.171]) by mx.groups.io with SMTP id smtpd.web10.18728.1704997015938433705 for ; Thu, 11 Jan 2024 10:16:56 -0800 X-Received: by mail-pl1-f171.google.com with SMTP id d9443c01a7336-1d4ab4e65aeso42713925ad.0 for ; Thu, 11 Jan 2024 10:16:55 -0800 (PST) X-Gm-Message-State: kGTlmMBGuBMIw4xDrq3pluCQx7686176AA= X-Google-Smtp-Source: AGHT+IEE2h8mjgi9TcGmJG+IGWnZsr3PxpUlGobNFQk4x799IwoC+EY0sxqG4hIvlVyvQUpMLygQJw== X-Received: by 2002:a17:902:ff01:b0:1d4:40f3:7ab9 with SMTP id f1-20020a170902ff0100b001d440f37ab9mr1644549plj.40.1704997014275; Thu, 11 Jan 2024 10:16:54 -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.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 11 Jan 2024 10:16:53 -0800 (PST) From: "Doug Flick via groups.io" To: devel@edk2.groups.io Cc: "Douglas Flick [MSFT]" , Jiewen Yao Subject: [edk2-devel] [PATCH 2/6] SecurityPkg: DxeTpmMeasureBootLib: SECURITY PATCH 4117 - CVE 2022-36763 Date: Thu, 11 Jan 2024 10:16:02 -0800 Message-ID: <3e347894f931240d0a7d5a74b0cc381ea0b29b15.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-GND-Status: LEGIT Authentication-Results: spool.mail.gandi.net; dkim=pass header.d=groups.io header.s=20140610 header.b=Kuw30GsO; dmarc=none; spf=pass (spool.mail.gandi.net: domain of bounce@groups.io designates 66.175.222.108 as permitted sender) smtp.mailfrom=bounce@groups.io This commit contains the patch files and tests for DxeTpmMeasureBootLib CVE 2022-36763. Cc: Jiewen Yao Signed-off-by: Doug Flick [MSFT] --- SecurityPkg/Test/SecurityPkgHostTest.dsc | 1 + .../DxeTpmMeasureBootLib.inf | 4 +- ...eTpmMeasureBootLibSanitizationTestHost.inf | 28 ++ .../DxeTpmMeasureBootLibSanitization.h | 114 +++++++ .../DxeTpmMeasureBootLib.c | 40 ++- .../DxeTpmMeasureBootLibSanitization.c | 241 ++++++++++++++ .../DxeTpmMeasureBootLibSanitizationTest.c | 301 ++++++++++++++++++ SecurityPkg/SecurityPkg.ci.yaml | 1 + 8 files changed, 716 insertions(+), 14 deletions(-) create mode 100644 SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTe= st/DxeTpmMeasureBootLibSanitizationTestHost.inf create mode 100644 SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureB= ootLibSanitization.h create mode 100644 SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureB= ootLibSanitization.c create mode 100644 SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTe= st/DxeTpmMeasureBootLibSanitizationTest.c diff --git a/SecurityPkg/Test/SecurityPkgHostTest.dsc b/SecurityPkg/Test/Se= curityPkgHostTest.dsc index 788c1ab6fec6..1655e573eae4 100644 --- a/SecurityPkg/Test/SecurityPkgHostTest.dsc +++ b/SecurityPkg/Test/SecurityPkgHostTest.dsc @@ -27,6 +27,7 @@ [Components] SecurityPkg/Library/SecureBootVariableLib/UnitTest/MockUefiLib.inf=0D SecurityPkg/Test/Mock/Library/GoogleTest/MockPlatformPKProtectionLib/Moc= kPlatformPKProtectionLib.inf=0D SecurityPkg/Library/DxeTpm2MeasureBootLib/InternalUnitTest/DxeTpm2Measur= eBootLibSanitizationTestHost.inf=0D + SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTest/DxeTpmMeasureB= ootLibSanitizationTestHost.inf=0D =0D #=0D # Build SecurityPkg HOST_APPLICATION Tests=0D diff --git a/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.= inf b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.inf index ebab6f7c1e1e..414c654d156a 100644 --- a/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.inf +++ b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.inf @@ -32,6 +32,8 @@ [Defines] =0D [Sources]=0D DxeTpmMeasureBootLib.c=0D + DxeTpmMeasureBootLibSanitization.c=0D + DxeTpmMeasureBootLibSanitization.h=0D =0D [Packages]=0D MdePkg/MdePkg.dec=0D @@ -41,6 +43,7 @@ [Packages] =0D [LibraryClasses]=0D BaseMemoryLib=0D + SafeIntLib=0D DebugLib=0D MemoryAllocationLib=0D DevicePathLib=0D @@ -59,4 +62,3 @@ [Protocols] gEfiFirmwareVolumeBlockProtocolGuid ## SOMETIMES_CONSUMES=0D gEfiBlockIoProtocolGuid ## SOMETIMES_CONSUMES=0D gEfiDiskIoProtocolGuid ## SOMETIMES_CONSUMES=0D -=0D diff --git a/SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTest/DxeT= pmMeasureBootLibSanitizationTestHost.inf b/SecurityPkg/Library/DxeTpmMeasur= eBootLib/InternalUnitTest/DxeTpmMeasureBootLibSanitizationTestHost.inf new file mode 100644 index 000000000000..47b0811b00bc --- /dev/null +++ b/SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTest/DxeTpmMeasu= reBootLibSanitizationTestHost.inf @@ -0,0 +1,28 @@ +## @file=0D +# This file builds the unit tests for DxeTpmMeasureBootLib=0D +#=0D +# Copyright (C) Microsoft Corporation.
=0D +# SPDX-License-Identifier: BSD-2-Clause-Patent=0D +##=0D +=0D +[Defines]=0D + INF_VERSION =3D 0x00010006=0D + BASE_NAME =3D DxeTpmMeasuredBootLibTest=0D + FILE_GUID =3D eb01bc38-309c-4d3e-967e-9f078c90772f= =0D + MODULE_TYPE =3D HOST_APPLICATION=0D + VERSION_STRING =3D 1.0=0D + ENTRY_POINT =3D main=0D +=0D +[Sources]=0D + DxeTpmMeasureBootLibSanitizationTest.c=0D + ../DxeTpmMeasureBootLibSanitization.c=0D +=0D +[Packages]=0D + MdePkg/MdePkg.dec=0D +=0D +[LibraryClasses]=0D + BaseLib=0D + DebugLib=0D + UnitTestLib=0D + PrintLib=0D + SafeIntLib=0D diff --git a/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLibS= anitization.h b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootL= ibSanitization.h new file mode 100644 index 000000000000..0d9d00c281f6 --- /dev/null +++ b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLibSanitiza= tion.h @@ -0,0 +1,114 @@ +/** @file=0D + This file includes the function prototypes for the sanitization function= s.=0D +=0D + These are those functions:=0D +=0D + DxeTpmMeasureBootLibImageRead() function will make sure the PE/COFF imag= e content=0D + read is within the image buffer.=0D +=0D + TcgMeasurePeImage() function will accept untrusted PE/COFF image and val= idate its=0D + data structure within this image buffer before use.=0D +=0D + TcgMeasureGptTable() function will receive untrusted GPT partition table= , and parse=0D + partition data carefully.=0D +=0D + Copyright (c) Microsoft Corporation.
=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D +=0D +**/=0D +=0D +#ifndef DXE_TPM_MEASURE_BOOT_LIB_VALIDATION_=0D +#define DXE_TPM_MEASURE_BOOT_LIB_VALIDATION_=0D +=0D +#include =0D +#include =0D +#include =0D +#include =0D +=0D +/**=0D + This function will validate the EFI_PARTITION_TABLE_HEADER structure is = safe to parse=0D + However this function will not attempt to verify the validity of the GPT= partition=0D + It will check the following:=0D + - Signature=0D + - Revision=0D + - AlternateLBA=0D + - FirstUsableLBA=0D + - LastUsableLBA=0D + - PartitionEntryLBA=0D + - NumberOfPartitionEntries=0D + - SizeOfPartitionEntry=0D + - BlockIo=0D +=0D + @param[in] PrimaryHeader=0D + Pointer to the EFI_PARTITION_TABLE_HEADER structure.=0D +=0D + @param[in] BlockIo=0D + Pointer to the EFI_BLOCK_IO_PROTOCOL structure.=0D +=0D + @retval EFI_SUCCESS=0D + The EFI_PARTITION_TABLE_HEADER structure is valid.=0D +=0D + @retval EFI_INVALID_PARAMETER=0D + The EFI_PARTITION_TABLE_HEADER structure is invalid.=0D +**/=0D +EFI_STATUS=0D +EFIAPI=0D +SanitizeEfiPartitionTableHeader (=0D + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader,=0D + IN CONST EFI_BLOCK_IO_PROTOCOL *BlockIo=0D + );=0D +=0D +/**=0D + This function will validate that the allocation size from the primary he= ader is sane=0D + It will check the following:=0D + - AllocationSize does not overflow=0D +=0D + @param[in] PrimaryHeader=0D + Pointer to the EFI_PARTITION_TABLE_HEADER structure.=0D +=0D + @param[out] AllocationSize=0D + Pointer to the allocation size.=0D +=0D + @retval EFI_SUCCESS=0D + The allocation size is valid.=0D +=0D + @retval EFI_OUT_OF_RESOURCES=0D + The allocation size is invalid.=0D +**/=0D +EFI_STATUS=0D +EFIAPI=0D +SanitizePrimaryHeaderAllocationSize (=0D + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader,=0D + OUT UINT32 *AllocationSize=0D + );=0D +=0D +/**=0D + This function will validate that the Gpt Event Size calculated from the = primary header is sane=0D + It will check the following:=0D + - EventSize does not overflow=0D +=0D + Important: This function includes the entire length of the allocated spa= ce, including the=0D + TCG_PCR_EVENT_HDR. When hashing the buffer allocated with this size, the= caller must subtract=0D + the size of the TCG_PCR_EVENT_HDR from the size of the buffer before has= hing.=0D +=0D + @param[in] PrimaryHeader - Pointer to the EFI_PARTITION_TABLE_HEADER str= ucture.=0D + @param[in] NumberOfPartition - Number of partitions.=0D + @param[out] EventSize - Pointer to the event size.=0D +=0D + @retval EFI_SUCCESS=0D + The event size is valid.=0D +=0D + @retval EFI_OUT_OF_RESOURCES=0D + Overflow would have occurred.=0D +=0D + @retval EFI_INVALID_PARAMETER=0D + One of the passed parameters was invalid.=0D +**/=0D +EFI_STATUS=0D +SanitizePrimaryHeaderGptEventSize (=0D + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader,=0D + IN UINTN NumberOfPartition,=0D + OUT UINT32 *EventSize=0D + );=0D +=0D +#endif // DXE_TPM_MEASURE_BOOT_LIB_VALIDATION_=0D diff --git a/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.= c b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.c index 220393dd2beb..669ab1913440 100644 --- a/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.c +++ b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLib.c @@ -18,6 +18,8 @@ Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.
=0D SPDX-License-Identifier: BSD-2-Clause-Patent=0D =0D +Copyright (c) Microsoft Corporation.
=0D +SPDX-License-Identifier: BSD-2-Clause-Patent=0D **/=0D =0D #include =0D @@ -40,6 +42,8 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include =0D #include =0D =0D +#include "DxeTpmMeasureBootLibSanitization.h"=0D +=0D //=0D // Flag to check GPT partition. It only need be measured once.=0D //=0D @@ -136,6 +140,9 @@ TcgMeasureGptTable ( UINT32 EventSize;=0D UINT32 EventNumber;=0D EFI_PHYSICAL_ADDRESS EventLogLastEntry;=0D + UINT32 AllocSize;=0D +=0D + GptData =3D NULL;=0D =0D if (mMeasureGptCount > 0) {=0D return EFI_SUCCESS;=0D @@ -166,8 +173,8 @@ TcgMeasureGptTable ( BlockIo->Media->BlockSize,=0D (UINT8 *)PrimaryHeader=0D );=0D - if (EFI_ERROR (Status)) {=0D - DEBUG ((DEBUG_ERROR, "Failed to Read Partition Table Header!\n"));=0D + if (EFI_ERROR (Status) || EFI_ERROR (SanitizeEfiPartitionTableHeader (Pr= imaryHeader, BlockIo))) {=0D + DEBUG ((DEBUG_ERROR, "Failed to read Partition Table Header or invalid= Partition Table Header!\n"));=0D FreePool (PrimaryHeader);=0D return EFI_DEVICE_ERROR;=0D }=0D @@ -175,7 +182,13 @@ TcgMeasureGptTable ( //=0D // Read the partition entry.=0D //=0D - EntryPtr =3D (UINT8 *)AllocatePool (PrimaryHeader->NumberOfPartitionEntr= ies * PrimaryHeader->SizeOfPartitionEntry);=0D + Status =3D SanitizePrimaryHeaderAllocationSize (PrimaryHeader, &AllocSiz= e);=0D + if (EFI_ERROR (Status)) {=0D + FreePool (PrimaryHeader);=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + EntryPtr =3D (UINT8 *)AllocatePool (AllocSize);=0D if (EntryPtr =3D=3D NULL) {=0D FreePool (PrimaryHeader);=0D return EFI_OUT_OF_RESOURCES;=0D @@ -185,7 +198,7 @@ TcgMeasureGptTable ( DiskIo,=0D BlockIo->Media->MediaId,=0D MultU64x32 (PrimaryHeader->PartitionEntryLBA, BlockIo= ->Media->BlockSize),=0D - PrimaryHeader->NumberOfPartitionEntries * PrimaryHead= er->SizeOfPartitionEntry,=0D + AllocSize,=0D EntryPtr=0D );=0D if (EFI_ERROR (Status)) {=0D @@ -210,9 +223,8 @@ TcgMeasureGptTable ( //=0D // Prepare Data for Measurement=0D //=0D - EventSize =3D (UINT32)(sizeof (EFI_GPT_DATA) - sizeof (GptData->Partitio= ns)=0D - + NumberOfPartition * PrimaryHeader->SizeOfPartitio= nEntry);=0D - TcgEvent =3D (TCG_PCR_EVENT *)AllocateZeroPool (EventSize + sizeof (TCG_= PCR_EVENT_HDR));=0D + Status =3D SanitizePrimaryHeaderGptEventSize (PrimaryHeader, NumberOfP= artition, &EventSize);=0D + TcgEvent =3D (TCG_PCR_EVENT *)AllocateZeroPool (EventSize);=0D if (TcgEvent =3D=3D NULL) {=0D FreePool (PrimaryHeader);=0D FreePool (EntryPtr);=0D @@ -221,7 +233,7 @@ TcgMeasureGptTable ( =0D TcgEvent->PCRIndex =3D 5;=0D TcgEvent->EventType =3D EV_EFI_GPT_EVENT;=0D - TcgEvent->EventSize =3D EventSize;=0D + TcgEvent->EventSize =3D EventSize - sizeof (TCG_PCR_EVENT_HDR);=0D GptData =3D (EFI_GPT_DATA *)TcgEvent->Event;=0D =0D //=0D @@ -361,11 +373,13 @@ TcgMeasurePeImage ( TcgEvent->PCRIndex =3D 2;=0D break;=0D default:=0D - DEBUG ((=0D - DEBUG_ERROR,=0D - "TcgMeasurePeImage: Unknown subsystem type %d",=0D - ImageType=0D - ));=0D + DEBUG (=0D + (=0D + DEBUG_ERROR,=0D + "TcgMeasurePeImage: Unknown subsystem type %d",=0D + ImageType=0D + )=0D + );=0D goto Finish;=0D }=0D =0D diff --git a/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLibS= anitization.c b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootL= ibSanitization.c new file mode 100644 index 000000000000..a3fa46f5e632 --- /dev/null +++ b/SecurityPkg/Library/DxeTpmMeasureBootLib/DxeTpmMeasureBootLibSanitiza= tion.c @@ -0,0 +1,241 @@ +/** @file=0D + The library instance provides security service of TPM2 measure boot and= =0D + Confidential Computing (CC) measure boot.=0D +=0D + Caution: This file requires additional review when modified.=0D + This library will have external input - PE/COFF image and GPT partition.= =0D + This external input must be validated carefully to avoid security issue = like=0D + buffer overflow, integer overflow.=0D +=0D + This file will pull out the validation logic from the following function= s, in an=0D + attempt to validate the untrusted input in the form of unit tests=0D +=0D + These are those functions:=0D +=0D + DxeTpmMeasureBootLibImageRead() function will make sure the PE/COFF imag= e content=0D + read is within the image buffer.=0D +=0D + Tcg2MeasureGptTable() function will receive untrusted GPT partition tabl= e, and parse=0D + partition data carefully.=0D +=0D + Copyright (c) Microsoft Corporation.
=0D + SPDX-License-Identifier: BSD-2-Clause-Patent=0D +**/=0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +=0D +#include "DxeTpmMeasureBootLibSanitization.h"=0D +=0D +#define GPT_HEADER_REVISION_V1 0x00010000=0D +=0D +/**=0D + This function will validate the EFI_PARTITION_TABLE_HEADER structure is = safe to parse=0D + However this function will not attempt to verify the validity of the GPT= partition=0D + It will check the following:=0D + - Signature=0D + - Revision=0D + - AlternateLBA=0D + - FirstUsableLBA=0D + - LastUsableLBA=0D + - PartitionEntryLBA=0D + - NumberOfPartitionEntries=0D + - SizeOfPartitionEntry=0D + - BlockIo=0D +=0D + @param[in] PrimaryHeader=0D + Pointer to the EFI_PARTITION_TABLE_HEADER structure.=0D +=0D + @param[in] BlockIo=0D + Pointer to the EFI_BLOCK_IO_PROTOCOL structure.=0D +=0D + @retval EFI_SUCCESS=0D + The EFI_PARTITION_TABLE_HEADER structure is valid.=0D +=0D + @retval EFI_INVALID_PARAMETER=0D + The EFI_PARTITION_TABLE_HEADER structure is invalid.=0D +**/=0D +EFI_STATUS=0D +EFIAPI=0D +SanitizeEfiPartitionTableHeader (=0D + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader,=0D + IN CONST EFI_BLOCK_IO_PROTOCOL *BlockIo=0D + )=0D +{=0D + // Verify that the input parameters are safe to use=0D + if (PrimaryHeader =3D=3D NULL) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header!\n"));=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + if ((BlockIo =3D=3D NULL) || (BlockIo->Media =3D=3D NULL)) {=0D + DEBUG ((DEBUG_ERROR, "Invalid BlockIo!\n"));=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + // The signature must be EFI_PTAB_HEADER_ID ("EFI PART" in ASCII)=0D + if (PrimaryHeader->Header.Signature !=3D EFI_PTAB_HEADER_ID) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header!\n"));=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + // The version must be GPT_HEADER_REVISION_V1 (0x00010000)=0D + if (PrimaryHeader->Header.Revision !=3D GPT_HEADER_REVISION_V1) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header Revision!\n"));=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + // The HeaderSize must be greater than or equal to 92 and must be less t= han or equal to the logical block size=0D + if ((PrimaryHeader->Header.HeaderSize < sizeof (EFI_PARTITION_TABLE_HEAD= ER)) || (PrimaryHeader->Header.HeaderSize > BlockIo->Media->BlockSize)) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header HeaderSize!\n"));= =0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + // check that the PartitionEntryLBA greater than the Max LBA=0D + // This will be used later for multiplication=0D + if (PrimaryHeader->PartitionEntryLBA > DivU64x32 (MAX_UINT64, BlockIo->M= edia->BlockSize)) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header PartitionEntryLBA= !\n"));=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + // Check that the number of partition entries is greater than zero=0D + if (PrimaryHeader->NumberOfPartitionEntries =3D=3D 0) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header NumberOfPartition= Entries!\n"));=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + // SizeOfPartitionEntry must be 128, 256, 512... improper size may lead = to accessing uninitialized memory=0D + if ((PrimaryHeader->SizeOfPartitionEntry < 128) || ((PrimaryHeader->Size= OfPartitionEntry & (PrimaryHeader->SizeOfPartitionEntry - 1)) !=3D 0)) {=0D + 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"));=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + // This check is to prevent overflow when calculating the allocation siz= e for the partition entries=0D + // This check will be used later for multiplication=0D + if (PrimaryHeader->NumberOfPartitionEntries > DivU64x32 (MAX_UINT64, Pri= maryHeader->SizeOfPartitionEntry)) {=0D + DEBUG ((DEBUG_ERROR, "Invalid Partition Table Header NumberOfPartition= Entries!\n"));=0D + return EFI_DEVICE_ERROR;=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + This function will validate that the allocation size from the primary he= ader is sane=0D + It will check the following:=0D + - AllocationSize does not overflow=0D +=0D + @param[in] PrimaryHeader=0D + Pointer to the EFI_PARTITION_TABLE_HEADER structure.=0D +=0D + @param[out] AllocationSize=0D + Pointer to the allocation size.=0D +=0D + @retval EFI_SUCCESS=0D + The allocation size is valid.=0D +=0D + @retval EFI_OUT_OF_RESOURCES=0D + The allocation size is invalid.=0D +**/=0D +EFI_STATUS=0D +EFIAPI=0D +SanitizePrimaryHeaderAllocationSize (=0D + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader,=0D + OUT UINT32 *AllocationSize=0D + )=0D +{=0D + EFI_STATUS Status;=0D +=0D + if (PrimaryHeader =3D=3D NULL) {=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + if (AllocationSize =3D=3D NULL) {=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + // Replacing logic:=0D + // PrimaryHeader->NumberOfPartitionEntries * PrimaryHeader->SizeOfPartit= ionEntry;=0D + Status =3D SafeUint32Mult (PrimaryHeader->NumberOfPartitionEntries, Prim= aryHeader->SizeOfPartitionEntry, AllocationSize);=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((DEBUG_ERROR, "Allocation Size would have overflowed!\n"));=0D + return EFI_BAD_BUFFER_SIZE;=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D +=0D +/**=0D + This function will validate that the Gpt Event Size calculated from the = primary header is sane=0D + It will check the following:=0D + - EventSize does not overflow=0D +=0D + Important: This function includes the entire length of the allocated spa= ce, including the=0D + TCG_PCR_EVENT_HDR. When hashing the buffer allocated with this size, the= caller must subtract=0D + the size of the TCG_PCR_EVENT_HDR from the size of the buffer before has= hing.=0D +=0D + @param[in] PrimaryHeader - Pointer to the EFI_PARTITION_TABLE_HEADER str= ucture.=0D + @param[in] NumberOfPartition - Number of partitions.=0D + @param[out] EventSize - Pointer to the event size.=0D +=0D + @retval EFI_SUCCESS=0D + The event size is valid.=0D +=0D + @retval EFI_OUT_OF_RESOURCES=0D + Overflow would have occurred.=0D +=0D + @retval EFI_INVALID_PARAMETER=0D + One of the passed parameters was invalid.=0D +**/=0D +EFI_STATUS=0D +SanitizePrimaryHeaderGptEventSize (=0D + IN CONST EFI_PARTITION_TABLE_HEADER *PrimaryHeader,=0D + IN UINTN NumberOfPartition,=0D + OUT UINT32 *EventSize=0D + )=0D +{=0D + EFI_STATUS Status;=0D + UINT32 SafeNumberOfPartitions;=0D +=0D + if (PrimaryHeader =3D=3D NULL) {=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + if (EventSize =3D=3D NULL) {=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + // We shouldn't even attempt to perform the multiplication if the number= of partitions is greater than the maximum value of UINT32=0D + Status =3D SafeUintnToUint32 (NumberOfPartition, &SafeNumberOfPartitions= );=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((DEBUG_ERROR, "NumberOfPartition would have overflowed!\n"));=0D + return EFI_INVALID_PARAMETER;=0D + }=0D +=0D + // Replacing logic:=0D + // (UINT32)(sizeof (EFI_GPT_DATA) - sizeof (GptData->Partitions) + Numbe= rOfPartition * PrimaryHeader.SizeOfPartitionEntry + sizeof (TCG_PCR_EVENT_H= DR));=0D + Status =3D SafeUint32Mult (SafeNumberOfPartitions, PrimaryHeader->SizeOf= PartitionEntry, EventSize);=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((DEBUG_ERROR, "Event Size would have overflowed!\n"));=0D + return EFI_BAD_BUFFER_SIZE;=0D + }=0D +=0D + Status =3D SafeUint32Add (=0D + sizeof (TCG_PCR_EVENT_HDR) +=0D + OFFSET_OF (EFI_GPT_DATA, Partitions),=0D + *EventSize,=0D + EventSize=0D + );=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((DEBUG_ERROR, "Event Size would have overflowed because of GPTD= ata!\n"));=0D + return EFI_BAD_BUFFER_SIZE;=0D + }=0D +=0D + return EFI_SUCCESS;=0D +}=0D diff --git a/SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTest/DxeT= pmMeasureBootLibSanitizationTest.c b/SecurityPkg/Library/DxeTpmMeasureBootL= ib/InternalUnitTest/DxeTpmMeasureBootLibSanitizationTest.c new file mode 100644 index 000000000000..eeb928cdb0aa --- /dev/null +++ b/SecurityPkg/Library/DxeTpmMeasureBootLib/InternalUnitTest/DxeTpmMeasu= reBootLibSanitizationTest.c @@ -0,0 +1,301 @@ +/** @file=0D +This file includes the unit test cases for the DxeTpmMeasureBootLibSanitiz= ationTest.c.=0D +=0D +Copyright (c) Microsoft Corporation.
=0D +SPDX-License-Identifier: BSD-2-Clause-Patent=0D +**/=0D +=0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +#include =0D +=0D +#include "../DxeTpmMeasureBootLibSanitization.h"=0D +=0D +#define UNIT_TEST_NAME "DxeTpmMeasureBootLibSanitizationTest"=0D +#define UNIT_TEST_VERSION "1.0"=0D +=0D +#define DEFAULT_PRIMARY_TABLE_HEADER_REVISION 0x000100= 00=0D +#define DEFAULT_PRIMARY_TABLE_HEADER_NUMBER_OF_PARTITION_ENTRIES 1=0D +#define DEFAULT_PRIMARY_TABLE_HEADER_SIZE_OF_PARTITION_ENTRY 128=0D +=0D +/**=0D + This function tests the SanitizeEfiPartitionTableHeader function.=0D + It's intent is to test that a malicious EFI_PARTITION_TABLE_HEADER=0D + structure will not cause undefined or unexpected behavior.=0D +=0D + In general the TPM should still be able to measure the data, but=0D + be the header should be sanitized to prevent any unexpected behavior.=0D +=0D + @param[in] Context The unit test context.=0D +=0D + @retval UNIT_TEST_PASSED The test passed.=0D + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed.=0D +**/=0D +UNIT_TEST_STATUS=0D +EFIAPI=0D +TestSanitizeEfiPartitionTableHeader (=0D + IN UNIT_TEST_CONTEXT Context=0D + )=0D +{=0D + EFI_STATUS Status;=0D + EFI_PARTITION_TABLE_HEADER PrimaryHeader;=0D + EFI_BLOCK_IO_PROTOCOL BlockIo;=0D + EFI_BLOCK_IO_MEDIA BlockMedia;=0D +=0D + // Generate EFI_BLOCK_IO_MEDIA test data=0D + BlockMedia.MediaId =3D 1;=0D + BlockMedia.RemovableMedia =3D FALSE;=0D + BlockMedia.MediaPresent =3D TRUE;=0D + BlockMedia.LogicalPartition =3D FALSE;=0D + BlockMedia.ReadOnly =3D FALSE;=0D + BlockMedia.WriteCaching =3D FALSE;=0D + BlockMedia.BlockSize =3D 512;=0D + BlockMedia.IoAlign =3D 1;=0D + BlockMedia.LastBlock =3D 0;=0D +=0D + // Generate EFI_BLOCK_IO_PROTOCOL test data=0D + BlockIo.Revision =3D 1;=0D + BlockIo.Media =3D &BlockMedia;=0D + BlockIo.Reset =3D NULL;=0D + BlockIo.ReadBlocks =3D NULL;=0D + BlockIo.WriteBlocks =3D NULL;=0D + BlockIo.FlushBlocks =3D NULL;=0D +=0D + // Geneate EFI_PARTITION_TABLE_HEADER test data=0D + PrimaryHeader.Header.Signature =3D EFI_PTAB_HEADER_ID;=0D + PrimaryHeader.Header.Revision =3D DEFAULT_PRIMARY_TABLE_HEADER_= REVISION;=0D + PrimaryHeader.Header.HeaderSize =3D sizeof (EFI_PARTITION_TABLE_H= EADER);=0D + PrimaryHeader.MyLBA =3D 1;=0D + PrimaryHeader.AlternateLBA =3D 2;=0D + PrimaryHeader.FirstUsableLBA =3D 3;=0D + PrimaryHeader.LastUsableLBA =3D 4;=0D + PrimaryHeader.PartitionEntryLBA =3D 5;=0D + PrimaryHeader.NumberOfPartitionEntries =3D DEFAULT_PRIMARY_TABLE_HEADER_= NUMBER_OF_PARTITION_ENTRIES;=0D + PrimaryHeader.SizeOfPartitionEntry =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY;=0D + PrimaryHeader.PartitionEntryArrayCRC32 =3D 0; // Purposely invalid=0D +=0D + // Calculate the CRC32 of the PrimaryHeader=0D + PrimaryHeader.Header.CRC32 =3D CalculateCrc32 ((UINT8 *)&PrimaryHeader, = PrimaryHeader.Header.HeaderSize);=0D +=0D + // Test that a normal PrimaryHeader passes validation=0D + Status =3D SanitizeEfiPartitionTableHeader (&PrimaryHeader, &BlockIo);=0D + UT_ASSERT_NOT_EFI_ERROR (Status);=0D +=0D + // Test that when number of partition entries is 0, the function returns= EFI_DEVICE_ERROR=0D + // Should print "Invalid Partition Table Header NumberOfPartitionEntries= !""=0D + PrimaryHeader.NumberOfPartitionEntries =3D 0;=0D + Status =3D SanitizeEfiPartitionTableHead= er (&PrimaryHeader, &BlockIo);=0D + UT_ASSERT_EQUAL (Status, EFI_DEVICE_ERROR);=0D + PrimaryHeader.NumberOfPartitionEntries =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY;=0D +=0D + // Test that when the header size is too small, the function returns EFI= _DEVICE_ERROR=0D + // Should print "Invalid Partition Table Header Size!"=0D + PrimaryHeader.Header.HeaderSize =3D 0;=0D + Status =3D SanitizeEfiPartitionTableHeader (&Pr= imaryHeader, &BlockIo);=0D + UT_ASSERT_EQUAL (Status, EFI_DEVICE_ERROR);=0D + PrimaryHeader.Header.HeaderSize =3D sizeof (EFI_PARTITION_TABLE_HEADER);= =0D +=0D + // Test that when the SizeOfPartitionEntry is too small, the function re= turns EFI_DEVICE_ERROR=0D + // 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.)!"=0D + PrimaryHeader.SizeOfPartitionEntry =3D 1;=0D + Status =3D SanitizeEfiPartitionTableHeader (= &PrimaryHeader, &BlockIo);=0D + UT_ASSERT_EQUAL (Status, EFI_DEVICE_ERROR);=0D +=0D + DEBUG ((DEBUG_INFO, "%a: Test passed\n", __func__));=0D +=0D + return UNIT_TEST_PASSED;=0D +}=0D +=0D +/**=0D + This function tests the SanitizePrimaryHeaderAllocationSize function.=0D + It's intent is to test that the untrusted input from a EFI_PARTITION_TAB= LE_HEADER=0D + structure will not cause an overflow when calculating the allocation siz= e.=0D +=0D + @param[in] Context The unit test context.=0D +=0D + @retval UNIT_TEST_PASSED The test passed.=0D + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed.=0D +**/=0D +UNIT_TEST_STATUS=0D +EFIAPI=0D +TestSanitizePrimaryHeaderAllocationSize (=0D + IN UNIT_TEST_CONTEXT Context=0D + )=0D +{=0D + UINT32 AllocationSize;=0D +=0D + EFI_STATUS Status;=0D + EFI_PARTITION_TABLE_HEADER PrimaryHeader;=0D +=0D + // Test that a normal PrimaryHeader passes validation=0D + PrimaryHeader.NumberOfPartitionEntries =3D 5;=0D + PrimaryHeader.SizeOfPartitionEntry =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY;=0D +=0D + Status =3D SanitizePrimaryHeaderAllocationSize (&PrimaryHeader, &Allocat= ionSize);=0D + UT_ASSERT_NOT_EFI_ERROR (Status);=0D +=0D + // Test that the allocation size is correct compared to the existing log= ic=0D + UT_ASSERT_EQUAL (AllocationSize, PrimaryHeader.NumberOfPartitionEntries = * PrimaryHeader.SizeOfPartitionEntry);=0D +=0D + // Test that an overflow is detected=0D + PrimaryHeader.NumberOfPartitionEntries =3D MAX_UINT32;=0D + PrimaryHeader.SizeOfPartitionEntry =3D 5;=0D + Status =3D SanitizePrimaryHeaderAllocati= onSize (&PrimaryHeader, &AllocationSize);=0D + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE);=0D +=0D + // Test the inverse=0D + PrimaryHeader.NumberOfPartitionEntries =3D 5;=0D + PrimaryHeader.SizeOfPartitionEntry =3D MAX_UINT32;=0D + Status =3D SanitizePrimaryHeaderAllocati= onSize (&PrimaryHeader, &AllocationSize);=0D + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE);=0D +=0D + // Test the worst case scenario=0D + PrimaryHeader.NumberOfPartitionEntries =3D MAX_UINT32;=0D + PrimaryHeader.SizeOfPartitionEntry =3D MAX_UINT32;=0D + Status =3D SanitizePrimaryHeaderAllocati= onSize (&PrimaryHeader, &AllocationSize);=0D + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE);=0D +=0D + DEBUG ((DEBUG_INFO, "%a: Test passed\n", __func__));=0D +=0D + return UNIT_TEST_PASSED;=0D +}=0D +=0D +/**=0D + This function tests the SanitizePrimaryHeaderGptEventSize function.=0D + It's intent is to test that the untrusted input from a EFI_GPT_DATA stru= cture=0D + will not cause an overflow when calculating the event size.=0D +=0D + @param[in] Context The unit test context.=0D +=0D + @retval UNIT_TEST_PASSED The test passed.=0D + @retval UNIT_TEST_ERROR_TEST_FAILED The test failed.=0D +**/=0D +UNIT_TEST_STATUS=0D +EFIAPI=0D +TestSanitizePrimaryHeaderGptEventSize (=0D + IN UNIT_TEST_CONTEXT Context=0D + )=0D +{=0D + UINT32 EventSize;=0D + UINT32 ExistingLogicEventSize;=0D + EFI_STATUS Status;=0D + EFI_PARTITION_TABLE_HEADER PrimaryHeader;=0D + UINTN NumberOfPartition;=0D + EFI_GPT_DATA *GptData;=0D +=0D + GptData =3D NULL;=0D +=0D + // Test that a normal PrimaryHeader passes validation=0D + PrimaryHeader.NumberOfPartitionEntries =3D 5;=0D + PrimaryHeader.SizeOfPartitionEntry =3D DEFAULT_PRIMARY_TABLE_HEADER_= SIZE_OF_PARTITION_ENTRY;=0D +=0D + // set the number of partitions=0D + NumberOfPartition =3D 13;=0D +=0D + // that the primary event size is correct=0D + Status =3D SanitizePrimaryHeaderGptEventSize (&PrimaryHeader, NumberOfPa= rtition, &EventSize);=0D + UT_ASSERT_NOT_EFI_ERROR (Status);=0D +=0D + // Calculate the existing logic event size=0D + ExistingLogicEventSize =3D (UINT32)(sizeof (TCG_PCR_EVENT_HDR) + OFFSET_= OF (EFI_GPT_DATA, Partitions)=0D + + NumberOfPartition * PrimaryHeader.Si= zeOfPartitionEntry);=0D +=0D + // Check that the event size is correct=0D + UT_ASSERT_EQUAL (EventSize, ExistingLogicEventSize);=0D +=0D + // Tests that the primary event size may not overflow=0D + Status =3D SanitizePrimaryHeaderGptEventSize (&PrimaryHeader, MAX_UINT32= , &EventSize);=0D + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE);=0D +=0D + // Test that the size of partition entries may not overflow=0D + PrimaryHeader.SizeOfPartitionEntry =3D MAX_UINT32;=0D + Status =3D SanitizePrimaryHeaderGptEventSize= (&PrimaryHeader, NumberOfPartition, &EventSize);=0D + UT_ASSERT_EQUAL (Status, EFI_BAD_BUFFER_SIZE);=0D +=0D + DEBUG ((DEBUG_INFO, "%a: Test passed\n", __func__));=0D +=0D + return UNIT_TEST_PASSED;=0D +}=0D +=0D +// *--------------------------------------------------------------------*= =0D +// * Unit Test Code Main Function=0D +// *--------------------------------------------------------------------*= =0D +=0D +/**=0D + This function acts as the entry point for the unit tests.=0D +=0D + @param argc - The number of command line arguments=0D + @param argv - The command line arguments=0D +=0D + @return int - The status of the test=0D +**/=0D +EFI_STATUS=0D +EFIAPI=0D +UefiTestMain (=0D + VOID=0D + )=0D +{=0D + EFI_STATUS Status;=0D + UNIT_TEST_FRAMEWORK_HANDLE Framework;=0D + UNIT_TEST_SUITE_HANDLE TcgMeasureBootLibValidationTestSuite;=0D +=0D + Framework =3D NULL;=0D +=0D + DEBUG ((DEBUG_INFO, "%a: TestMain() - Start\n", UNIT_TEST_NAME));=0D +=0D + Status =3D InitUnitTestFramework (&Framework, UNIT_TEST_NAME, gEfiCaller= BaseName, UNIT_TEST_VERSION);=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((DEBUG_ERROR, "%a: Failed in InitUnitTestFramework. Status =3D = %r\n", UNIT_TEST_NAME, Status));=0D + goto EXIT;=0D + }=0D +=0D + Status =3D CreateUnitTestSuite (&TcgMeasureBootLibValidationTestSuite, F= ramework, "TcgMeasureBootLibValidationTestSuite", "Common.TcgMeasureBootLib= Validation", NULL, NULL);=0D + if (EFI_ERROR (Status)) {=0D + DEBUG ((DEBUG_ERROR, "%s: Failed in CreateUnitTestSuite for TcgMeasure= BootLibValidationTestSuite\n", UNIT_TEST_NAME));=0D + Status =3D EFI_OUT_OF_RESOURCES;=0D + goto EXIT;=0D + }=0D +=0D + // -----------Suite---------------------------------Description---------= -------------------Class----------------------------------Test Function----= --------------------Pre---Clean-Context=0D + AddTestCase (TcgMeasureBootLibValidationTestSuite, "Tests Validating EFI= Partition Table", "Common.TcgMeasureBootLibValidation", TestSanitizeEfiPar= titionTableHeader, NULL, NULL, NULL);=0D + AddTestCase (TcgMeasureBootLibValidationTestSuite, "Tests Primary header= gpt event checks for overflow", "Common.TcgMeasureBootLibValidation", Test= SanitizePrimaryHeaderAllocationSize, NULL, NULL, NULL);=0D + AddTestCase (TcgMeasureBootLibValidationTestSuite, "Tests Primary header= allocation size checks for overflow", "Common.TcgMeasureBootLibValidation"= , TestSanitizePrimaryHeaderGptEventSize, NULL, NULL, NULL);=0D +=0D + Status =3D RunAllTestSuites (Framework);=0D +=0D +EXIT:=0D + if (Framework !=3D NULL) {=0D + FreeUnitTestFramework (Framework);=0D + }=0D +=0D + DEBUG ((DEBUG_INFO, "%a: TestMain() - End\n", UNIT_TEST_NAME));=0D + return Status;=0D +}=0D +=0D +///=0D +/// Avoid ECC error for function name that starts with lower case letter=0D +///=0D +#define DxeTpmMeasureBootLibUnitTestMain main=0D +=0D +/**=0D + Standard POSIX C entry point for host based unit test execution.=0D +=0D + @param[in] Argc Number of arguments=0D + @param[in] Argv Array of pointers to arguments=0D +=0D + @retval 0 Success=0D + @retval other Error=0D +**/=0D +INT32=0D +DxeTpmMeasureBootLibUnitTestMain (=0D + IN INT32 Argc,=0D + IN CHAR8 *Argv[]=0D + )=0D +{=0D + return (INT32)UefiTestMain ();=0D +}=0D diff --git a/SecurityPkg/SecurityPkg.ci.yaml b/SecurityPkg/SecurityPkg.ci.y= aml index 24389531afaa..53e5b1fd8e69 100644 --- a/SecurityPkg/SecurityPkg.ci.yaml +++ b/SecurityPkg/SecurityPkg.ci.yaml @@ -17,6 +17,7 @@ "ExceptionList": [=0D "8005", "gRT",=0D "8001", "DxeTpm2MeasureBootLibUnitTestMain",=0D + "8001", "DxeTpmMeasureBootLibUnitTestMain"=0D ],=0D ## Both file path and directory path are accepted.=0D "IgnoreFiles": [=0D --=20 2.43.0 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#113758): https://edk2.groups.io/g/devel/message/113758 Mute This Topic: https://groups.io/mt/103689721/7686176 Group Owner: devel+owner@edk2.groups.io Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io] -=-=-=-=-=-=-=-=-=-=-=-