From nobody Sat Nov 2 16:24:15 2024 Delivered-To: importer@patchew.org Received-SPF: none (zoho.com: 198.145.21.10 is neither permitted nor denied by domain of lists.01.org) client-ip=198.145.21.10; envelope-from=edk2-devel-bounces@lists.01.org; helo=ml01.01.org; Authentication-Results: mx.zoho.com; spf=none (zoho.com: 198.145.21.10 is neither permitted nor denied by domain of lists.01.org) smtp.mailfrom=edk2-devel-bounces@lists.01.org; Return-Path: Received: from ml01.01.org (ml01.01.org [198.145.21.10]) by mx.zohomail.com with SMTPS id 1488133854615421.8016684781983; Sun, 26 Feb 2017 10:30:54 -0800 (PST) Received: from [127.0.0.1] (localhost [IPv6:::1]) by ml01.01.org (Postfix) with ESMTP id 7E3FE82144; Sun, 26 Feb 2017 10:30:53 -0800 (PST) Received: from mail-wm0-x233.google.com (mail-wm0-x233.google.com [IPv6:2a00:1450:400c:c09::233]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ml01.01.org (Postfix) with ESMTPS id 6251382101 for ; Sun, 26 Feb 2017 10:30:51 -0800 (PST) Received: by mail-wm0-x233.google.com with SMTP id v77so47559412wmv.0 for ; Sun, 26 Feb 2017 10:30:51 -0800 (PST) Received: from localhost.localdomain ([105.149.201.216]) by smtp.gmail.com with ESMTPSA id s103sm4499474wrc.6.2017.02.26.10.30.46 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 26 Feb 2017 10:30:48 -0800 (PST) X-Original-To: edk2-devel@lists.01.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=a79GOy+joRMlSezljz6Ft3qZZmVq4lf0nRsdC2EY9pw=; b=giaNgZOT+wl571Wc31yhgfQpb8mMtssrsHfZqZiITUAshaQtA72/V/2YldAXJoUAlH 6gViLSu9y+TkwLgAOkNFz45NJExxugGVtEetZJ81sf5CiV0FB4R5HVMEHT0pidCyt0WW 3/BokHTY09c6wHaWbLARFXZWVYMhnq2qDX2sI= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=a79GOy+joRMlSezljz6Ft3qZZmVq4lf0nRsdC2EY9pw=; b=gT395NdDaEvhxGuk8xQ4NqXFix/ju9jHfPxPwRfyzqjUcA6unPKYUNBYA778iKmLUO vlYt4GOH+oTC3w7lf0axbTgkonP7xJJMMo29Kjda1lMB/CNnkZaYba1YKG+n3+/EfgMO 695BQqk4U7xnO2jvtYoK7QhwvOETYyzTDGTgBGcvMYgfHMDNLQT8FhJY16bzE2khgi6h Vb1+TanJOzQovjb2c62DLu/KaspCJR+cq+3rlEBxPXZ2TttbcRiPLZYn6FpjKh6p8ec/ aqJ1P2iEZ2SVtIGhVa/Vfg69gHKZkoIPGYwBID5KcNdQlaP1ebojPDIjgGzRYmm7TZvg jW9g== X-Gm-Message-State: AMke39l/yKQD6OkuAV8BZi7T53uijU7znXF0kRymrEnblOVblkk/dCPnpzGH7L8VR60sBoTZ X-Received: by 10.28.28.74 with SMTP id c71mr6016879wmc.9.1488133849748; Sun, 26 Feb 2017 10:30:49 -0800 (PST) From: Ard Biesheuvel To: edk2-devel@lists.01.org, jiewen.yao@intel.com, leif.lindholm@linaro.org Date: Sun, 26 Feb 2017 18:30:05 +0000 Message-Id: <1488133805-4773-7-git-send-email-ard.biesheuvel@linaro.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1488133805-4773-1-git-send-email-ard.biesheuvel@linaro.org> References: <1488133805-4773-1-git-send-email-ard.biesheuvel@linaro.org> Subject: [edk2] [PATCH v3 6/6] MdeModulePkg/DxeCore: implement memory protection policy X-BeenThere: edk2-devel@lists.01.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: EDK II Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: feng.tian@intel.com, Ard Biesheuvel , afish@apple.com, liming.gao@Intel.com, michael.d.kinney@intel.com, lersek@redhat.com, star.zeng@intel.com MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Errors-To: edk2-devel-bounces@lists.01.org Sender: "edk2-devel" X-ZohoMail: RSF_4 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" This implements a DXE memory protection policy that ensure that regions that don't require executable permissions are mapped with the non-exec attribute set. First of all, it iterates over all entries in the UEFI memory map, and removes executable permissions according to the configured DXE memory protection policy, as recorded in PcdDxeMemoryProtectionPolicy. Secondly, it sets or clears the non-executable attribute when allocating or freeing pages, both for page based or pool based allocations. Note that this complements the image protection facility, which applies strict permissions to BootServicesCode/RuntimeServicesCode regions when the section alignment allows it. The memory protection configured by this patch operates on non-code regions only. Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Ard Biesheuvel --- MdeModulePkg/Core/Dxe/DxeMain.h | 24 ++ MdeModulePkg/Core/Dxe/DxeMain.inf | 1 + MdeModulePkg/Core/Dxe/Mem/Page.c | 4 + MdeModulePkg/Core/Dxe/Mem/Pool.c | 7 + MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c | 306 +++++++++++++++++++- 5 files changed, 341 insertions(+), 1 deletion(-) diff --git a/MdeModulePkg/Core/Dxe/DxeMain.h b/MdeModulePkg/Core/Dxe/DxeMai= n.h index b14be9a74d8e..5668c1f2d648 100644 --- a/MdeModulePkg/Core/Dxe/DxeMain.h +++ b/MdeModulePkg/Core/Dxe/DxeMain.h @@ -2949,4 +2949,28 @@ MemoryProtectionExitBootServicesCallback ( VOID ); =20 +/** + Manage memory permission attributes on a memory range, according to the + configured DXE memory protection policy. + + @param OldType The old memory type of the range + @param NewType The new memory type of the range + @param Memory The base address of the range + @param Length The size of the range (in bytes) + + @return EFI_SUCCESS If the the CPU arch protocol is not installed = yet + @return EFI_SUCCESS If no DXE memory protection policy has been co= nfigured + @return EFI_SUCCESS If OldType and NewType use the same permission= attributes + @return other Return value of gCpu->SetMemoryAttributes() + +**/ +EFI_STATUS +EFIAPI +ApplyMemoryProtectionPolicy ( + IN EFI_MEMORY_TYPE OldType, + IN EFI_MEMORY_TYPE NewType, + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINT64 Length + ); + #endif diff --git a/MdeModulePkg/Core/Dxe/DxeMain.inf b/MdeModulePkg/Core/Dxe/DxeM= ain.inf index 371e91cb0d7e..30d5984f7c1f 100644 --- a/MdeModulePkg/Core/Dxe/DxeMain.inf +++ b/MdeModulePkg/Core/Dxe/DxeMain.inf @@ -191,6 +191,7 @@ [Pcd] gEfiMdeModulePkgTokenSpaceGuid.PcdMemoryProfileDriverPath = ## CONSUMES gEfiMdeModulePkgTokenSpaceGuid.PcdPropertiesTableEnable = ## CONSUMES gEfiMdeModulePkgTokenSpaceGuid.PcdImageProtectionPolicy = ## CONSUMES + gEfiMdeModulePkgTokenSpaceGuid.PcdDxeNxMemoryProtectionPolicy = ## CONSUMES =20 # [Hob] # RESOURCE_DESCRIPTOR ## CONSUMES diff --git a/MdeModulePkg/Core/Dxe/Mem/Page.c b/MdeModulePkg/Core/Dxe/Mem/P= age.c index bda4f6397e91..86874906de58 100644 --- a/MdeModulePkg/Core/Dxe/Mem/Page.c +++ b/MdeModulePkg/Core/Dxe/Mem/Page.c @@ -1344,6 +1344,8 @@ CoreAllocatePages ( NULL ); InstallMemoryAttributesTableOnMemoryAllocation (MemoryType); + ApplyMemoryProtectionPolicy (EfiConventionalMemory, MemoryType, *Memor= y, + EFI_PAGES_TO_SIZE (NumberOfPages)); } return Status; } @@ -1460,6 +1462,8 @@ CoreFreePages ( NULL ); InstallMemoryAttributesTableOnMemoryAllocation (MemoryType); + ApplyMemoryProtectionPolicy (MemoryType, EfiConventionalMemory, Memory, + EFI_PAGES_TO_SIZE (NumberOfPages)); } return Status; } diff --git a/MdeModulePkg/Core/Dxe/Mem/Pool.c b/MdeModulePkg/Core/Dxe/Mem/P= ool.c index 410615e0dee9..63b9983b88b5 100644 --- a/MdeModulePkg/Core/Dxe/Mem/Pool.c +++ b/MdeModulePkg/Core/Dxe/Mem/Pool.c @@ -305,6 +305,10 @@ CoreAllocatePoolPagesI ( Buffer =3D CoreAllocatePoolPages (PoolType, NoPages, Granularity); CoreReleaseMemoryLock (); =20 + if (Buffer !=3D NULL) { + ApplyMemoryProtectionPolicy (EfiConventionalMemory, PoolType, + (EFI_PHYSICAL_ADDRESS)(UINTN)Buffer, EFI_PAGES_TO_SIZE (NoPages)); + } return Buffer; } =20 @@ -555,6 +559,9 @@ CoreFreePoolPagesI ( CoreAcquireMemoryLock (); CoreFreePoolPages (Memory, NoPages); CoreReleaseMemoryLock (); + + ApplyMemoryProtectionPolicy (PoolType, EfiConventionalMemory, + (EFI_PHYSICAL_ADDRESS)(UINTN)Memory, EFI_PAGES_TO_SIZE (NoPages)); } =20 /** diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/C= ore/Dxe/Misc/MemoryProtection.c index 46d88463d417..f2a69a3d0df9 100644 --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c @@ -64,6 +64,12 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHE= R EXPRESS OR IMPLIED. #define DO_NOT_PROTECT 0x00000000 #define PROTECT_IF_ALIGNED_ELSE_ALLOW 0x00000001 =20 +#define MEMORY_TYPE_OS_RESERVED_MIN 0x80000000 +#define MEMORY_TYPE_OEM_RESERVED_MIN 0x70000000 + +#define PREVIOUS_MEMORY_DESCRIPTOR(MemoryDescriptor, Size) \ + ((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(MemoryDescriptor) - (Size))) + UINT32 mImageProtectionPolicy; =20 /** @@ -647,6 +653,210 @@ UnprotectUefiImage ( } =20 /** + Return the EFI memory permission attribute associated with memory + type 'Type' under the configured DXE memory protection policy. +**/ +STATIC +UINT64 +GetPermissionAttributeForMemoryType ( + IN EFI_MEMORY_TYPE MemoryType + ) +{ + UINT64 TestBit; + + if ((UINT32) MemoryType >=3D MEMORY_TYPE_OS_RESERVED_MIN) { + TestBit =3D BIT63; + } else if ((UINT32) MemoryType >=3D MEMORY_TYPE_OEM_RESERVED_MIN) { + TestBit =3D BIT62; + } else { + TestBit =3D LShiftU64 (1, MemoryType); + } + + if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) !=3D 0) { + return EFI_MEMORY_XP; + } else { + return 0; + } +} + +/** + Sort memory map entries based upon PhysicalStart, from low to high. + + @param MemoryMap A pointer to the buffer in which firmware= places + the current memory map. + @param MemoryMapSize Size, in bytes, of the MemoryMap buffer. + @param DescriptorSize Size, in bytes, of an individual EFI_MEMO= RY_DESCRIPTOR. +**/ +STATIC +VOID +SortMemoryMap ( + IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, + IN UINTN MemoryMapSize, + IN UINTN DescriptorSize + ) +{ + EFI_MEMORY_DESCRIPTOR *MemoryMapEntry; + EFI_MEMORY_DESCRIPTOR *NextMemoryMapEntry; + EFI_MEMORY_DESCRIPTOR *MemoryMapEnd; + EFI_MEMORY_DESCRIPTOR TempMemoryMap; + + MemoryMapEntry =3D MemoryMap; + NextMemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, Descripto= rSize); + MemoryMapEnd =3D (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + Memory= MapSize); + while (MemoryMapEntry < MemoryMapEnd) { + while (NextMemoryMapEntry < MemoryMapEnd) { + if (MemoryMapEntry->PhysicalStart > NextMemoryMapEntry->PhysicalStar= t) { + CopyMem (&TempMemoryMap, MemoryMapEntry, sizeof(EFI_MEMORY_DESCRIP= TOR)); + CopyMem (MemoryMapEntry, NextMemoryMapEntry, sizeof(EFI_MEMORY_DES= CRIPTOR)); + CopyMem (NextMemoryMapEntry, &TempMemoryMap, sizeof(EFI_MEMORY_DES= CRIPTOR)); + } + + NextMemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (NextMemoryMapEntry, D= escriptorSize); + } + + MemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, Descri= ptorSize); + NextMemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, Descri= ptorSize); + } +} + +/** + Merge adjacent memory map entries if they use the same memory protection= policy + + @param[in, out] MemoryMap A pointer to the buffer in which= firmware places + the current memory map. + @param[in, out] MemoryMapSize A pointer to the size, in bytes,= of the + MemoryMap buffer. On input, this= is the size of + the current memory map. On outp= ut, + it is the size of new memory map= after merge. + @param[in] DescriptorSize Size, in bytes, of an individual= EFI_MEMORY_DESCRIPTOR. +**/ +STATIC +VOID +MergeMemoryMapForProtectionPolicy ( + IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap, + IN OUT UINTN *MemoryMapSize, + IN UINTN DescriptorSize + ) +{ + EFI_MEMORY_DESCRIPTOR *MemoryMapEntry; + EFI_MEMORY_DESCRIPTOR *MemoryMapEnd; + UINT64 MemoryBlockLength; + EFI_MEMORY_DESCRIPTOR *NewMemoryMapEntry; + EFI_MEMORY_DESCRIPTOR *NextMemoryMapEntry; + UINT64 Attributes; + + SortMemoryMap (MemoryMap, *MemoryMapSize, DescriptorSize); + + MemoryMapEntry =3D MemoryMap; + NewMemoryMapEntry =3D MemoryMap; + MemoryMapEnd =3D (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + *Memor= yMapSize); + while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) { + CopyMem (NewMemoryMapEntry, MemoryMapEntry, sizeof(EFI_MEMORY_DESCRIPT= OR)); + NextMemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, Descrip= torSize); + + do { + MemoryBlockLength =3D (UINT64) (EFI_PAGES_TO_SIZE((UINTN)MemoryMapEn= try->NumberOfPages)); + Attributes =3D GetPermissionAttributeForMemoryType (MemoryMapEntry->= Type); + + if (((UINTN)NextMemoryMapEntry < (UINTN)MemoryMapEnd) && + Attributes =3D=3D GetPermissionAttributeForMemoryType (NextMemor= yMapEntry->Type) && + ((MemoryMapEntry->PhysicalStart + MemoryBlockLength) =3D=3D Next= MemoryMapEntry->PhysicalStart)) { + MemoryMapEntry->NumberOfPages +=3D NextMemoryMapEntry->NumberOfPag= es; + if (NewMemoryMapEntry !=3D MemoryMapEntry) { + NewMemoryMapEntry->NumberOfPages +=3D NextMemoryMapEntry->Number= OfPages; + } + + NextMemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (NextMemoryMapEntry,= DescriptorSize); + continue; + } else { + MemoryMapEntry =3D PREVIOUS_MEMORY_DESCRIPTOR (NextMemoryMapEntry,= DescriptorSize); + break; + } + } while (TRUE); + + MemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorS= ize); + NewMemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (NewMemoryMapEntry, Descr= iptorSize); + } + + *MemoryMapSize =3D (UINTN)NewMemoryMapEntry - (UINTN)MemoryMap; + + return ; +} + + +/** + Remove exec permissions from all regions whose type is identified by + PcdDxeNxMemoryProtectionPolicy +**/ +STATIC +VOID +InitializeDxeNxMemoryProtectionPolicy ( + VOID + ) +{ + UINTN MemoryMapSize; + UINTN MapKey; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + EFI_MEMORY_DESCRIPTOR *MemoryMap; + EFI_MEMORY_DESCRIPTOR *MemoryMapEntry; + EFI_MEMORY_DESCRIPTOR *MemoryMapEnd; + EFI_STATUS Status; + UINT64 Attributes; + + // + // Get the EFI memory map. + // + MemoryMapSize =3D 0; + MemoryMap =3D NULL; + + Status =3D gBS->GetMemoryMap ( + &MemoryMapSize, + MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion + ); + ASSERT (Status =3D=3D EFI_BUFFER_TOO_SMALL); + do { + MemoryMap =3D (EFI_MEMORY_DESCRIPTOR *) AllocatePool (MemoryMapSize); + ASSERT (MemoryMap !=3D NULL); + Status =3D gBS->GetMemoryMap ( + &MemoryMapSize, + MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion + ); + if (EFI_ERROR (Status)) { + FreePool (MemoryMap); + } + } while (Status =3D=3D EFI_BUFFER_TOO_SMALL); + ASSERT_EFI_ERROR (Status); + + DEBUG((DEBUG_ERROR, "%a: removing exec permissions from memory regions\n= ", + __FUNCTION__)); + + MergeMemoryMapForProtectionPolicy (MemoryMap, &MemoryMapSize, Descriptor= Size); + + MemoryMapEntry =3D MemoryMap; + MemoryMapEnd =3D (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + Memory= MapSize); + while ((UINTN) MemoryMapEntry < (UINTN) MemoryMapEnd) { + + Attributes =3D GetPermissionAttributeForMemoryType (MemoryMapEntry->Ty= pe); + if (Attributes !=3D 0) { + SetUefiImageMemoryAttributes ( + MemoryMapEntry->PhysicalStart, + EFI_PAGES_TO_SIZE (MemoryMapEntry->NumberOfPages), + Attributes); + } + MemoryMapEntry =3D NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorS= ize); + } + FreePool (MemoryMap); +} + + +/** A notification for CPU_ARCH protocol. =20 @param[in] Event Event whose notification function is b= eing invoked. @@ -674,6 +884,17 @@ MemoryProtectionCpuArchProtocolNotify ( return; } =20 + // + // Apply the memory protection policy on non-BScode/RTcode regions. + // + if (PcdGet64 (PcdDxeNxMemoryProtectionPolicy) !=3D 0) { + InitializeDxeNxMemoryProtectionPolicy (); + } + + if (mImageProtectionPolicy =3D=3D 0) { + return; + } + Status =3D gBS->LocateHandleBuffer ( ByProtocol, &gEfiLoadedImageProtocolGuid, @@ -753,7 +974,7 @@ CoreInitializeMemoryProtection ( =20 mImageProtectionPolicy =3D PcdGet32(PcdImageProtectionPolicy); =20 - if (mImageProtectionPolicy !=3D 0) { + if (mImageProtectionPolicy !=3D 0 || PcdGet64 (PcdDxeNxMemoryProtectionP= olicy) !=3D 0) { Status =3D CoreCreateEvent ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, @@ -775,3 +996,86 @@ CoreInitializeMemoryProtection ( } return ; } + +STATIC +BOOLEAN +IsInSmm ( + VOID + ) +{ + BOOLEAN InSmm; + + InSmm =3D FALSE; + if (gSmmBase2 !=3D NULL) { + gSmmBase2->InSmm (gSmmBase2, &InSmm); + } + return InSmm; +} + +/** + Manage memory permission attributes on a memory range, according to the + configured DXE memory protection policy. + + @param OldType The old memory type of the range + @param NewType The new memory type of the range + @param Memory The base address of the range + @param Length The size of the range (in bytes) + + @return EFI_SUCCESS If we are executing in SMM mode. No permission= attributes + are updated in this case + @return EFI_SUCCESS If the the CPU arch protocol is not installed = yet + @return EFI_SUCCESS If no DXE memory protection policy has been co= nfigured + @return EFI_SUCCESS If OldType and NewType use the same permission= attributes + @return other Return value of gCpu->SetMemoryAttributes() + +**/ +EFI_STATUS +EFIAPI +ApplyMemoryProtectionPolicy ( + IN EFI_MEMORY_TYPE OldType, + IN EFI_MEMORY_TYPE NewType, + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINT64 Length + ) +{ + UINT64 OldAttributes; + UINT64 NewAttributes; + + // + // The policy configured in PcdDxeNxMemoryProtectionPolicy + // does not apply to allocations performed in SMM mode. + // + if (IsInSmm ()) { + return EFI_SUCCESS; + } + + // + // If the CPU arch protocol is not installed yet, we cannot manage memory + // permission attributes, and it is the job of the driver that installs = this + // protocol to set the permissions on existing allocations. + // + if (gCpu =3D=3D NULL) { + return EFI_SUCCESS; + } + + // + // Check if a DXE memory protection policy has been configured + // + if (PcdGet64 (PcdDxeNxMemoryProtectionPolicy) =3D=3D 0) { + return EFI_SUCCESS; + } + + // + // Update the executable permissions according to the DXE memory + // protection policy, but only if the policy is different between + // the old and the new type. + // + OldAttributes =3D GetPermissionAttributeForMemoryType (OldType); + NewAttributes =3D GetPermissionAttributeForMemoryType (NewType); + + if (OldAttributes =3D=3D NewAttributes) { + return EFI_SUCCESS; + } + + return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes); +} --=20 2.7.4 _______________________________________________ edk2-devel mailing list edk2-devel@lists.01.org https://lists.01.org/mailman/listinfo/edk2-devel