public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
* [PATCH 0/3] Apply NX protections more strictly
@ 2023-02-08 17:58 Ard Biesheuvel
  2023-02-08 17:58 ` [PATCH 1/3] ArmPkg/ArmMmuLib: Avoid splitting block entries if possible Ard Biesheuvel
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 17:58 UTC (permalink / raw)
  To: devel
  Cc: Ard Biesheuvel, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar, Taylor Beebe, Marvin Häuser

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1975 bytes --]

This fixes an issue reported by Marvin, where NX memory protections are
applied in a rather unreliable manner, resulting in the possibility that
memory mappings may exist that are using different attributes than
intended.

The reason for this approach was that applying memory protections
eagerly (i.e., after every alloc/free even if the memory attributes are
not expected to change as a result) may result in unbounded recursion in
the page table code, due to the fact that the page tables it allocates
need to be remapped with the correct attributes as well.

This has not been reported as being an issue on x86, but on ARM, this
needs a couple of fixes so that converting between EfiConventionalMemory
and EfiBootServicesData will never trigger a block entry split. With
that fixed, we can just remove the shortcut from DXE core and always
call SetMemoryAttributes.

Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=3316

Cc: Michael Kinney <michael.d.kinney@intel.com>
Cc: Liming Gao <gaoliming@byosoft.com.cn>
Cc: Jiewen Yao <jiewen.yao@intel.com>
Cc: Michael Kubacki <michael.kubacki@microsoft.com>
Cc: Sean Brogan <sean.brogan@microsoft.com>
Cc: Rebecca Cran <quic_rcran@quicinc.com>
Cc: Leif Lindholm <quic_llindhol@quicinc.com>
Cc: Sami Mujawar <sami.mujawar@arm.com>
Cc: Taylor Beebe <t@taylorbeebe.com>
Cc: Marvin Häuser <mhaeuser@posteo.de>

Ard Biesheuvel (3):
  ArmPkg/ArmMmuLib: Avoid splitting block entries if possible
  ArmPkg/CpuDxe: Perform preliminary NX remap of free memory
  MdeModulePkg/DxeCore: Unconditionally set memory protections

 ArmPkg/Drivers/CpuDxe/CpuDxe.c                   | 77 ++++++++++++++++++++
 ArmPkg/Drivers/CpuDxe/CpuDxe.inf                 |  2 +
 ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c |  9 +++
 MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c    | 29 --------
 4 files changed, 88 insertions(+), 29 deletions(-)

-- 
2.39.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/3] ArmPkg/ArmMmuLib: Avoid splitting block entries if possible
  2023-02-08 17:58 [PATCH 0/3] Apply NX protections more strictly Ard Biesheuvel
@ 2023-02-08 17:58 ` Ard Biesheuvel
  2023-02-08 17:58 ` [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory Ard Biesheuvel
  2023-02-08 17:58 ` [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections Ard Biesheuvel
  2 siblings, 0 replies; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 17:58 UTC (permalink / raw)
  To: devel
  Cc: Ard Biesheuvel, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar, Taylor Beebe, Marvin Häuser

Currently, the AArch64 MMU page table logic will break down any block
entry that overlaps with the region being mapped, even if the block
entry in question is using the same attributes as the new region.

This means that creating a non-executable mapping inside a region that
is already mapped non-executable at a coarser granularity may trigger a
call to AllocatePages (), which may recurse back into the page table
code to update the attributes on the newly allocated page tables.

Let's avoid this, by preserving the block entry if it already covers the
region being mapped with the correct attributes.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c b/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c
index 1cf8dc090012..28191938aeb1 100644
--- a/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c
+++ b/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c
@@ -251,6 +251,15 @@ UpdateRegionMappingRecursive (
       ASSERT (Level < 3);
 
       if (!IsTableEntry (*Entry, Level)) {
+        //
+        // If the region we are trying to map is already covered by a block
+        // entry with the right attributes, don't bother splitting it up.
+        //
+        if (IsBlockEntry (*Entry, Level) &&
+            ((*Entry & TT_ATTRIBUTES_MASK & ~AttributeClearMask) == AttributeSetMask)) {
+          continue;
+        }
+
         //
         // No table entry exists yet, so we need to allocate a page table
         // for the next level.
-- 
2.39.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory
  2023-02-08 17:58 [PATCH 0/3] Apply NX protections more strictly Ard Biesheuvel
  2023-02-08 17:58 ` [PATCH 1/3] ArmPkg/ArmMmuLib: Avoid splitting block entries if possible Ard Biesheuvel
@ 2023-02-08 17:58 ` Ard Biesheuvel
  2023-02-08 18:32   ` Marvin Häuser
  2023-02-08 17:58 ` [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections Ard Biesheuvel
  2 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 17:58 UTC (permalink / raw)
  To: devel
  Cc: Ard Biesheuvel, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar, Taylor Beebe, Marvin Häuser

The DXE core implementation of PcdDxeNxMemoryProtectionPolicy already
contains an assertion that EfiConventionalMemory and EfiBootServicesData
are subjected to the same policy when it comes to the use of NX
permissions. The reason for this is that we may otherwise end up with
unbounded recursion in the page table code, given that allocating a page
table would then involve a permission attribute change, and this could
result in the need for a block entry to be split, which would trigger
the allocation of a page table recursively.

For the same reason, a shortcut exists in ApplyMemoryProtectionPolicy()
where, instead of setting the memory attributes unconditionally, we
compare the NX policies and avoid touching the page tables if they are
the same for the old and the new memory types. Without this shortcut, we
may end up in a situation where, as the CPU arch protocol DXE driver is
ramping up, the same unbounded recursion is triggered, due to the fact
that the NX policy for EfiConventionalMemory has not been applied yet.

To break this cycle, let's remap all EfiConventionalMemory regions
according to the NX policy for EfiBootServicesData before exposing the
CPU arch protocol to the DXE core and other drivers. This ensures that
creating EfiBootServicesData allocations does not result in memory
attribute changes, and therefore no recursion.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 ArmPkg/Drivers/CpuDxe/CpuDxe.c   | 77 ++++++++++++++++++++
 ArmPkg/Drivers/CpuDxe/CpuDxe.inf |  2 +
 2 files changed, 79 insertions(+)

diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.c b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
index d04958e79e52..83fd6fd4e476 100644
--- a/ArmPkg/Drivers/CpuDxe/CpuDxe.c
+++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
@@ -11,6 +11,8 @@
 
 #include <Guid/IdleLoopEvent.h>
 
+#include <Library/MemoryAllocationLib.h>
+
 BOOLEAN  mIsFlushingGCD;
 
 /**
@@ -227,6 +229,69 @@ InitializeDma (
   CpuArchProtocol->DmaBufferAlignment = ArmCacheWritebackGranule ();
 }
 
+STATIC
+VOID
+RemapUnusedMemoryNx (
+  VOID
+  )
+{
+  UINT64                     TestBit;
+  UINTN                      MemoryMapSize;
+  UINTN                      MapKey;
+  UINTN                      DescriptorSize;
+  UINT32                     DescriptorVersion;
+  EFI_MEMORY_DESCRIPTOR      *MemoryMap;
+  EFI_MEMORY_DESCRIPTOR      *MemoryMapEntry;
+  EFI_MEMORY_DESCRIPTOR      *MemoryMapEnd;
+  EFI_STATUS                 Status;
+
+  TestBit = LShiftU64 (1, EfiBootServicesData);
+  if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) == 0) {
+    return;
+  }
+
+  MemoryMapSize = 0;
+  MemoryMap     = NULL;
+
+  Status = gBS->GetMemoryMap (
+                  &MemoryMapSize,
+                  MemoryMap,
+                  &MapKey,
+                  &DescriptorSize,
+                  &DescriptorVersion
+                  );
+  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
+  do {
+    MemoryMap = (EFI_MEMORY_DESCRIPTOR *)AllocatePool (MemoryMapSize);
+    ASSERT (MemoryMap != NULL);
+    Status = gBS->GetMemoryMap (
+                    &MemoryMapSize,
+                    MemoryMap,
+                    &MapKey,
+                    &DescriptorSize,
+                    &DescriptorVersion
+                    );
+    if (EFI_ERROR (Status)) {
+      FreePool (MemoryMap);
+    }
+  } while (Status == EFI_BUFFER_TOO_SMALL);
+
+  ASSERT_EFI_ERROR (Status);
+
+  MemoryMapEntry = MemoryMap;
+  MemoryMapEnd   = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + MemoryMapSize);
+  while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) {
+    if (MemoryMapEntry->Type == EfiConventionalMemory) {
+      ArmSetMemoryRegionNoExec (
+        MemoryMapEntry->PhysicalStart,
+        EFI_PAGES_TO_SIZE (MemoryMapEntry->NumberOfPages)
+        );
+    }
+
+    MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
+  }
+}
+
 EFI_STATUS
 CpuDxeInitialize (
   IN EFI_HANDLE        ImageHandle,
@@ -240,6 +305,18 @@ CpuDxeInitialize (
 
   InitializeDma (&mCpu);
 
+  //
+  // Once we install the CPU arch protocol, the DXE core's memory
+  // protection routines will invoke them to manage the permissions of page
+  // allocations as they are created. Given that this includes pages
+  // allocated for page tables by this driver, we must ensure that unused
+  // memory is mapped with the same permissions as boot services data
+  // regions. Otherwise, we may end up with unbounded recursion, due to the
+  // fact that updating permissions on a newly allocated page table may trigger
+  // a block entry split, which triggers a page table allocation, etc etc
+  //
+  RemapUnusedMemoryNx ();
+
   Status = gBS->InstallMultipleProtocolInterfaces (
                   &mCpuHandle,
                   &gEfiCpuArchProtocolGuid,
diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
index e732e21cb94a..8fd0f4133088 100644
--- a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
+++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
@@ -48,6 +48,7 @@ [LibraryClasses]
   DefaultExceptionHandlerLib
   DxeServicesTableLib
   HobLib
+  MemoryAllocationLib
   PeCoffGetEntryPointLib
   UefiDriverEntryPoint
   UefiLib
@@ -64,6 +65,7 @@ [Guids]
 
 [Pcd.common]
   gArmTokenSpaceGuid.PcdVFPEnabled
+  gEfiMdeModulePkgTokenSpaceGuid.PcdDxeNxMemoryProtectionPolicy
 
 [FeaturePcd.common]
   gArmTokenSpaceGuid.PcdDebuggerExceptionSupport
-- 
2.39.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections
  2023-02-08 17:58 [PATCH 0/3] Apply NX protections more strictly Ard Biesheuvel
  2023-02-08 17:58 ` [PATCH 1/3] ArmPkg/ArmMmuLib: Avoid splitting block entries if possible Ard Biesheuvel
  2023-02-08 17:58 ` [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory Ard Biesheuvel
@ 2023-02-08 17:58 ` Ard Biesheuvel
  2023-02-08 18:25   ` Ard Biesheuvel
  2 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 17:58 UTC (permalink / raw)
  To: devel
  Cc: Ard Biesheuvel, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar, Taylor Beebe, Marvin Häuser

Instead of relying on a questionable heuristic that avoids calling into
the SetMemoryAttributes () DXE service when the old memory type and the
new one are subjected to the same NX memory protection policy, make this
call unconditionally. This avoids corner cases where memory region
attributes are out of sync with the policy, either due to the fact that
we are in the middle of ramping up the protections, or due to explicit
invocations of SetMemoryAttributes() by drivers.

This requires the architecture page table code to be able to deal with
this, in particular, it needs to be robust against potential recursion
due to NX policies being applied to newly allocated page tables.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
 MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c | 29 --------------------
 1 file changed, 29 deletions(-)

diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
index 36987843f142..503feb72b5d0 100644
--- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
+++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
@@ -1263,9 +1263,7 @@ ApplyMemoryProtectionPolicy (
   IN  UINT64                Length
   )
 {
-  UINT64      OldAttributes;
   UINT64      NewAttributes;
-  EFI_STATUS  Status;
 
   //
   // The policy configured in PcdDxeNxMemoryProtectionPolicy
@@ -1320,32 +1318,5 @@ ApplyMemoryProtectionPolicy (
   //
   NewAttributes = GetPermissionAttributeForMemoryType (NewType);
 
-  if (OldType != EfiMaxMemoryType) {
-    OldAttributes = GetPermissionAttributeForMemoryType (OldType);
-    if (!mAfterDxeNxMemoryProtectionInit &&
-        (OldAttributes == NewAttributes)) {
-      return EFI_SUCCESS;
-    }
-
-    //
-    // If available, use the EFI memory attribute protocol to obtain
-    // the current attributes of the region. If the entire region is
-    // covered and the attributes match, we don't have to do anything.
-    //
-    if (mMemoryAttribute != NULL) {
-      Status = mMemoryAttribute->GetMemoryAttributes (mMemoryAttribute,
-                                                      Memory,
-                                                      Length,
-                                                      &OldAttributes
-                                                      );
-      if (!EFI_ERROR (Status) && (OldAttributes == NewAttributes)) {
-        return EFI_SUCCESS;
-      }
-    }
-  } else if (NewAttributes == 0) {
-    // newly added region of a type that does not require protection
-    return EFI_SUCCESS;
-  }
-
   return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes);
 }
-- 
2.39.1


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections
  2023-02-08 17:58 ` [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections Ard Biesheuvel
@ 2023-02-08 18:25   ` Ard Biesheuvel
  2023-02-08 18:55     ` Marvin Häuser
  2023-02-08 19:12     ` Taylor Beebe
  0 siblings, 2 replies; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 18:25 UTC (permalink / raw)
  To: devel
  Cc: Michael Kinney, Liming Gao, Jiewen Yao, Michael Kubacki,
	Sean Brogan, Rebecca Cran, Leif Lindholm, Sami Mujawar,
	Taylor Beebe, Marvin Häuser

On Wed, 8 Feb 2023 at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
>
> Instead of relying on a questionable heuristic that avoids calling into
> the SetMemoryAttributes () DXE service when the old memory type and the
> new one are subjected to the same NX memory protection policy, make this
> call unconditionally. This avoids corner cases where memory region
> attributes are out of sync with the policy, either due to the fact that
> we are in the middle of ramping up the protections, or due to explicit
> invocations of SetMemoryAttributes() by drivers.
>
> This requires the architecture page table code to be able to deal with
> this, in particular, it needs to be robust against potential recursion
> due to NX policies being applied to newly allocated page tables.
>
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
>  MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c | 29 --------------------
>  1 file changed, 29 deletions(-)
>
> diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> index 36987843f142..503feb72b5d0 100644
> --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> @@ -1263,9 +1263,7 @@ ApplyMemoryProtectionPolicy (
>    IN  UINT64                Length
>    )
>  {
> -  UINT64      OldAttributes;
>    UINT64      NewAttributes;
> -  EFI_STATUS  Status;
>
>    //
>    // The policy configured in PcdDxeNxMemoryProtectionPolicy
> @@ -1320,32 +1318,5 @@ ApplyMemoryProtectionPolicy (
>    //
>    NewAttributes = GetPermissionAttributeForMemoryType (NewType);
>
> -  if (OldType != EfiMaxMemoryType) {
> -    OldAttributes = GetPermissionAttributeForMemoryType (OldType);
> -    if (!mAfterDxeNxMemoryProtectionInit &&
> -        (OldAttributes == NewAttributes)) {
> -      return EFI_SUCCESS;
> -    }
> -

This removes some code that does not actually exist - apologies.

It comes down to just removing the conditional checks here, though,
and perform the tail call below unconditionally.

> -    //
> -    // If available, use the EFI memory attribute protocol to obtain
> -    // the current attributes of the region. If the entire region is
> -    // covered and the attributes match, we don't have to do anything.
> -    //
> -    if (mMemoryAttribute != NULL) {
> -      Status = mMemoryAttribute->GetMemoryAttributes (mMemoryAttribute,
> -                                                      Memory,
> -                                                      Length,
> -                                                      &OldAttributes
> -                                                      );
> -      if (!EFI_ERROR (Status) && (OldAttributes == NewAttributes)) {
> -        return EFI_SUCCESS;
> -      }
> -    }
> -  } else if (NewAttributes == 0) {
> -    // newly added region of a type that does not require protection
> -    return EFI_SUCCESS;
> -  }
> -
>    return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes);
>  }
> --
> 2.39.1
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory
  2023-02-08 17:58 ` [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory Ard Biesheuvel
@ 2023-02-08 18:32   ` Marvin Häuser
  2023-02-08 18:49     ` [edk2-devel] " Ard Biesheuvel
  0 siblings, 1 reply; 13+ messages in thread
From: Marvin Häuser @ 2023-02-08 18:32 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: edk2-devel-groups-io, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar, Taylor Beebe

Thanks!! :) Comments inline.

> On 8. Feb 2023, at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
> 
> The DXE core implementation of PcdDxeNxMemoryProtectionPolicy already
> contains an assertion that EfiConventionalMemory and EfiBootServicesData
> are subjected to the same policy when it comes to the use of NX
> permissions. The reason for this is that we may otherwise end up with
> unbounded recursion in the page table code, given that allocating a page
> table would then involve a permission attribute change, and this could
> result in the need for a block entry to be split, which would trigger
> the allocation of a page table recursively.
> 
> For the same reason, a shortcut exists in ApplyMemoryProtectionPolicy()
> where, instead of setting the memory attributes unconditionally, we
> compare the NX policies and avoid touching the page tables if they are
> the same for the old and the new memory types. Without this shortcut, we
> may end up in a situation where, as the CPU arch protocol DXE driver is
> ramping up, the same unbounded recursion is triggered, due to the fact
> that the NX policy for EfiConventionalMemory has not been applied yet.
> 
> To break this cycle, let's remap all EfiConventionalMemory regions
> according to the NX policy for EfiBootServicesData before exposing the
> CPU arch protocol to the DXE core and other drivers. This ensures that
> creating EfiBootServicesData allocations does not result in memory
> attribute changes, and therefore no recursion.
> 
> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> ---
> ArmPkg/Drivers/CpuDxe/CpuDxe.c   | 77 ++++++++++++++++++++
> ArmPkg/Drivers/CpuDxe/CpuDxe.inf |  2 +
> 2 files changed, 79 insertions(+)
> 
> diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.c b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> index d04958e79e52..83fd6fd4e476 100644
> --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> @@ -11,6 +11,8 @@
> 
> #include <Guid/IdleLoopEvent.h>
> 
> +#include <Library/MemoryAllocationLib.h>
> +
> BOOLEAN  mIsFlushingGCD;
> 
> /**
> @@ -227,6 +229,69 @@ InitializeDma (
>   CpuArchProtocol->DmaBufferAlignment = ArmCacheWritebackGranule ();
> }
> 
> +STATIC
> +VOID
> +RemapUnusedMemoryNx (
> +  VOID
> +  )
> +{
> +  UINT64                     TestBit;
> +  UINTN                      MemoryMapSize;
> +  UINTN                      MapKey;
> +  UINTN                      DescriptorSize;
> +  UINT32                     DescriptorVersion;
> +  EFI_MEMORY_DESCRIPTOR      *MemoryMap;
> +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEntry;
> +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEnd;
> +  EFI_STATUS                 Status;
> +
> +  TestBit = LShiftU64 (1, EfiBootServicesData);
> +  if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) == 0) {
> +    return;
> +  }
> +
> +  MemoryMapSize = 0;
> +  MemoryMap     = NULL;
> +
> +  Status = gBS->GetMemoryMap (
> +                  &MemoryMapSize,
> +                  MemoryMap,
> +                  &MapKey,
> +                  &DescriptorSize,
> +                  &DescriptorVersion
> +                  );
> +  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
> +  do {
> +    MemoryMap = (EFI_MEMORY_DESCRIPTOR *)AllocatePool (MemoryMapSize);

nitpick: *If* there is a V2, maybe drop the cast?

> +    ASSERT (MemoryMap != NULL);

I know ASSERTing for NULL is a common pattern for some reason, but I'd rather not have more code with this added.

> +    Status = gBS->GetMemoryMap (
> +                    &MemoryMapSize,
> +                    MemoryMap,
> +                    &MapKey,
> +                    &DescriptorSize,
> +                    &DescriptorVersion
> +                    );

Another nitpick, isn't it common practice to call the Core* functions directly within *Core? I know there is code that doesn't, but the former should be more efficient.

> +    if (EFI_ERROR (Status)) {
> +      FreePool (MemoryMap);
> +    }
> +  } while (Status == EFI_BUFFER_TOO_SMALL);

Is this guaranteed to terminate? I mean, I get the idea - try again with the larger size and when allocating the bigger buffer, its potential new entry will already be accounted for. However, I saw downstream code that tried something like this (they actually added a constant overhead ahead of time) bounded by something like 5 iterations. Might just have been defensive programming - probably was -, but it's not trivial to verify, I think, is it?

Regarding the added constant, the spec actually says the following, which obviously is just to shortcut a second round of GetMemoryMap(), but still:
"The actual size of the buffer allocated for the consequent call to GetMemoryMap() should be bigger then the value returned in MemoryMapSize"

It appears the spec did not define a canonical way to call GetMemoryMap(), did it? :(

> +
> +  ASSERT_EFI_ERROR (Status);
> +
> +  MemoryMapEntry = MemoryMap;
> +  MemoryMapEnd   = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + MemoryMapSize);
> +  while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) {
> +    if (MemoryMapEntry->Type == EfiConventionalMemory) {
> +      ArmSetMemoryRegionNoExec (
> +        MemoryMapEntry->PhysicalStart,
> +        EFI_PAGES_TO_SIZE (MemoryMapEntry->NumberOfPages)
> +        );
> +    }
> +
> +    MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
> +  }
> +}
> +
> EFI_STATUS
> CpuDxeInitialize (
>   IN EFI_HANDLE        ImageHandle,
> @@ -240,6 +305,18 @@ CpuDxeInitialize (
> 
>   InitializeDma (&mCpu);
> 
> +  //
> +  // Once we install the CPU arch protocol, the DXE core's memory
> +  // protection routines will invoke them to manage the permissions of page
> +  // allocations as they are created. Given that this includes pages
> +  // allocated for page tables by this driver, we must ensure that unused
> +  // memory is mapped with the same permissions as boot services data
> +  // regions. Otherwise, we may end up with unbounded recursion, due to the
> +  // fact that updating permissions on a newly allocated page table may trigger
> +  // a block entry split, which triggers a page table allocation, etc etc
> +  //
> +  RemapUnusedMemoryNx ();

Hmm. I might be too tired, but why is this not already done by InitializeDxeNxMemoryProtectionPolicy(), assuming BS_Data and ConvMem have the same XP configuration?

This reassures me that the CPU Arch protocol producers should be linked into DxeCore rather than loaded at some arbitrary point in time... Unrelated to the patch, of course.

Best regards,
Marvin

> +
>   Status = gBS->InstallMultipleProtocolInterfaces (
>                   &mCpuHandle,
>                   &gEfiCpuArchProtocolGuid,
> diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
> index e732e21cb94a..8fd0f4133088 100644
> --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
> +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
> @@ -48,6 +48,7 @@ [LibraryClasses]
>   DefaultExceptionHandlerLib
>   DxeServicesTableLib
>   HobLib
> +  MemoryAllocationLib
>   PeCoffGetEntryPointLib
>   UefiDriverEntryPoint
>   UefiLib
> @@ -64,6 +65,7 @@ [Guids]
> 
> [Pcd.common]
>   gArmTokenSpaceGuid.PcdVFPEnabled
> +  gEfiMdeModulePkgTokenSpaceGuid.PcdDxeNxMemoryProtectionPolicy
> 
> [FeaturePcd.common]
>   gArmTokenSpaceGuid.PcdDebuggerExceptionSupport
> -- 
> 2.39.1
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [edk2-devel] [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory
  2023-02-08 18:32   ` Marvin Häuser
@ 2023-02-08 18:49     ` Ard Biesheuvel
  2023-02-08 18:57       ` Taylor Beebe
  0 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 18:49 UTC (permalink / raw)
  To: devel, mhaeuser
  Cc: Michael Kinney, Liming Gao, Jiewen Yao, Michael Kubacki,
	Sean Brogan, Rebecca Cran, Leif Lindholm, Sami Mujawar,
	Taylor Beebe

On Wed, 8 Feb 2023 at 19:32, Marvin Häuser <mhaeuser@posteo.de> wrote:
>
> Thanks!! :) Comments inline.
>
> > On 8. Feb 2023, at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
> >
> > The DXE core implementation of PcdDxeNxMemoryProtectionPolicy already
> > contains an assertion that EfiConventionalMemory and EfiBootServicesData
> > are subjected to the same policy when it comes to the use of NX
> > permissions. The reason for this is that we may otherwise end up with
> > unbounded recursion in the page table code, given that allocating a page
> > table would then involve a permission attribute change, and this could
> > result in the need for a block entry to be split, which would trigger
> > the allocation of a page table recursively.
> >
> > For the same reason, a shortcut exists in ApplyMemoryProtectionPolicy()
> > where, instead of setting the memory attributes unconditionally, we
> > compare the NX policies and avoid touching the page tables if they are
> > the same for the old and the new memory types. Without this shortcut, we
> > may end up in a situation where, as the CPU arch protocol DXE driver is
> > ramping up, the same unbounded recursion is triggered, due to the fact
> > that the NX policy for EfiConventionalMemory has not been applied yet.
> >
> > To break this cycle, let's remap all EfiConventionalMemory regions
> > according to the NX policy for EfiBootServicesData before exposing the
> > CPU arch protocol to the DXE core and other drivers. This ensures that
> > creating EfiBootServicesData allocations does not result in memory
> > attribute changes, and therefore no recursion.
> >
> > Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> > ---
> > ArmPkg/Drivers/CpuDxe/CpuDxe.c   | 77 ++++++++++++++++++++
> > ArmPkg/Drivers/CpuDxe/CpuDxe.inf |  2 +
> > 2 files changed, 79 insertions(+)
> >
> > diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.c b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> > index d04958e79e52..83fd6fd4e476 100644
> > --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> > +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> > @@ -11,6 +11,8 @@
> >
> > #include <Guid/IdleLoopEvent.h>
> >
> > +#include <Library/MemoryAllocationLib.h>
> > +
> > BOOLEAN  mIsFlushingGCD;
> >
> > /**
> > @@ -227,6 +229,69 @@ InitializeDma (
> >   CpuArchProtocol->DmaBufferAlignment = ArmCacheWritebackGranule ();
> > }
> >
> > +STATIC
> > +VOID
> > +RemapUnusedMemoryNx (
> > +  VOID
> > +  )
> > +{
> > +  UINT64                     TestBit;
> > +  UINTN                      MemoryMapSize;
> > +  UINTN                      MapKey;
> > +  UINTN                      DescriptorSize;
> > +  UINT32                     DescriptorVersion;
> > +  EFI_MEMORY_DESCRIPTOR      *MemoryMap;
> > +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEntry;
> > +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEnd;
> > +  EFI_STATUS                 Status;
> > +
> > +  TestBit = LShiftU64 (1, EfiBootServicesData);
> > +  if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) == 0) {
> > +    return;
> > +  }
> > +
> > +  MemoryMapSize = 0;
> > +  MemoryMap     = NULL;
> > +
> > +  Status = gBS->GetMemoryMap (
> > +                  &MemoryMapSize,
> > +                  MemoryMap,
> > +                  &MapKey,
> > +                  &DescriptorSize,
> > +                  &DescriptorVersion
> > +                  );
> > +  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
> > +  do {
> > +    MemoryMap = (EFI_MEMORY_DESCRIPTOR *)AllocatePool (MemoryMapSize);
>
> nitpick: *If* there is a V2, maybe drop the cast?
>
> > +    ASSERT (MemoryMap != NULL);
>
> I know ASSERTing for NULL is a common pattern for some reason, but I'd rather not have more code with this added.
>
> > +    Status = gBS->GetMemoryMap (
> > +                    &MemoryMapSize,
> > +                    MemoryMap,
> > +                    &MapKey,
> > +                    &DescriptorSize,
> > +                    &DescriptorVersion
> > +                    );
>
> Another nitpick, isn't it common practice to call the Core* functions directly within *Core? I know there is code that doesn't, but the former should be more efficient.
>
> > +    if (EFI_ERROR (Status)) {
> > +      FreePool (MemoryMap);
> > +    }
> > +  } while (Status == EFI_BUFFER_TOO_SMALL);
>
> Is this guaranteed to terminate? I mean, I get the idea - try again with the larger size and when allocating the bigger buffer, its potential new entry will already be accounted for. However, I saw downstream code that tried something like this (they actually added a constant overhead ahead of time) bounded by something like 5 iterations. Might just have been defensive programming - probably was -, but it's not trivial to verify, I think, is it?
>
> Regarding the added constant, the spec actually says the following, which obviously is just to shortcut a second round of GetMemoryMap(), but still:
> "The actual size of the buffer allocated for the consequent call to GetMemoryMap() should be bigger then the value returned in MemoryMapSize"
>
> It appears the spec did not define a canonical way to call GetMemoryMap(), did it? :(
>

This is all copy-pasted from MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c

> > +
> > +  ASSERT_EFI_ERROR (Status);
> > +
> > +  MemoryMapEntry = MemoryMap;
> > +  MemoryMapEnd   = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + MemoryMapSize);
> > +  while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) {
> > +    if (MemoryMapEntry->Type == EfiConventionalMemory) {
> > +      ArmSetMemoryRegionNoExec (
> > +        MemoryMapEntry->PhysicalStart,
> > +        EFI_PAGES_TO_SIZE (MemoryMapEntry->NumberOfPages)
> > +        );
> > +    }
> > +
> > +    MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
> > +  }
> > +}
> > +
> > EFI_STATUS
> > CpuDxeInitialize (
> >   IN EFI_HANDLE        ImageHandle,
> > @@ -240,6 +305,18 @@ CpuDxeInitialize (
> >
> >   InitializeDma (&mCpu);
> >
> > +  //
> > +  // Once we install the CPU arch protocol, the DXE core's memory
> > +  // protection routines will invoke them to manage the permissions of page
> > +  // allocations as they are created. Given that this includes pages
> > +  // allocated for page tables by this driver, we must ensure that unused
> > +  // memory is mapped with the same permissions as boot services data
> > +  // regions. Otherwise, we may end up with unbounded recursion, due to the
> > +  // fact that updating permissions on a newly allocated page table may trigger
> > +  // a block entry split, which triggers a page table allocation, etc etc
> > +  //
> > +  RemapUnusedMemoryNx ();
>
> Hmm. I might be too tired, but why is this not already done by InitializeDxeNxMemoryProtectionPolicy(), assuming BS_Data and ConvMem have the same XP configuration?
>
> This reassures me that the CPU Arch protocol producers should be linked into DxeCore rather than loaded at some arbitrary point in time... Unrelated to the patch, of course.
>

The ordering here is a bit tricky. As soon as the CPU arch protocol is
exposed, every allocation will be remapped individually, resulting in
page table splits and therefore recursion.


>
> > +
> >   Status = gBS->InstallMultipleProtocolInterfaces (
> >                   &mCpuHandle,
> >                   &gEfiCpuArchProtocolGuid,
> > diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
> > index e732e21cb94a..8fd0f4133088 100644
> > --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
> > +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
> > @@ -48,6 +48,7 @@ [LibraryClasses]
> >   DefaultExceptionHandlerLib
> >   DxeServicesTableLib
> >   HobLib
> > +  MemoryAllocationLib
> >   PeCoffGetEntryPointLib
> >   UefiDriverEntryPoint
> >   UefiLib
> > @@ -64,6 +65,7 @@ [Guids]
> >
> > [Pcd.common]
> >   gArmTokenSpaceGuid.PcdVFPEnabled
> > +  gEfiMdeModulePkgTokenSpaceGuid.PcdDxeNxMemoryProtectionPolicy
> >
> > [FeaturePcd.common]
> >   gArmTokenSpaceGuid.PcdDebuggerExceptionSupport
> > --
> > 2.39.1
> >
>
>
>
> 
>
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections
  2023-02-08 18:25   ` Ard Biesheuvel
@ 2023-02-08 18:55     ` Marvin Häuser
  2023-02-08 19:12     ` Taylor Beebe
  1 sibling, 0 replies; 13+ messages in thread
From: Marvin Häuser @ 2023-02-08 18:55 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: edk2-devel-groups-io, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar, Taylor Beebe


> On 8. Feb 2023, at 19:49, Ard Biesheuvel <ardb@kernel.org> wrote:
> 
> This is all copy-pasted from MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c

:(

> 
> The ordering here is a bit tricky. As soon as the CPU arch protocol is
> exposed, every allocation will be remapped individually, resulting in
> page table splits and therefore recursion.

So the issue is the order of event handlers or allocations within the event dispatcher? :( Oh lord...

Can we maybe clarify the comment with something like "While DxeCore/InitializeDxeNxMemoryProtectionPolicy() would in theory perform this task, allocations between the protocol installation and the invocation of its event handler may trigger the issue."?

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [edk2-devel] [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory
  2023-02-08 18:49     ` [edk2-devel] " Ard Biesheuvel
@ 2023-02-08 18:57       ` Taylor Beebe
  2023-02-08 22:52         ` Ard Biesheuvel
  0 siblings, 1 reply; 13+ messages in thread
From: Taylor Beebe @ 2023-02-08 18:57 UTC (permalink / raw)
  To: Ard Biesheuvel, devel, mhaeuser
  Cc: Michael Kinney, Liming Gao, Jiewen Yao, Michael Kubacki,
	Sean Brogan, Rebecca Cran, Leif Lindholm, Sami Mujawar



On 2/8/2023 10:49 AM, Ard Biesheuvel wrote:
> On Wed, 8 Feb 2023 at 19:32, Marvin Häuser <mhaeuser@posteo.de> wrote:
>>
>> Thanks!! :) Comments inline.
>>
>>> On 8. Feb 2023, at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
>>>
>>> The DXE core implementation of PcdDxeNxMemoryProtectionPolicy already
>>> contains an assertion that EfiConventionalMemory and EfiBootServicesData
>>> are subjected to the same policy when it comes to the use of NX
>>> permissions. The reason for this is that we may otherwise end up with
>>> unbounded recursion in the page table code, given that allocating a page
>>> table would then involve a permission attribute change, and this could
>>> result in the need for a block entry to be split, which would trigger
>>> the allocation of a page table recursively.
>>>
>>> For the same reason, a shortcut exists in ApplyMemoryProtectionPolicy()
>>> where, instead of setting the memory attributes unconditionally, we
>>> compare the NX policies and avoid touching the page tables if they are
>>> the same for the old and the new memory types. Without this shortcut, we
>>> may end up in a situation where, as the CPU arch protocol DXE driver is
>>> ramping up, the same unbounded recursion is triggered, due to the fact
>>> that the NX policy for EfiConventionalMemory has not been applied yet.
>>>
>>> To break this cycle, let's remap all EfiConventionalMemory regions
>>> according to the NX policy for EfiBootServicesData before exposing the
>>> CPU arch protocol to the DXE core and other drivers. This ensures that
>>> creating EfiBootServicesData allocations does not result in memory
>>> attribute changes, and therefore no recursion.
>>>
>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>> ---
>>> ArmPkg/Drivers/CpuDxe/CpuDxe.c   | 77 ++++++++++++++++++++
>>> ArmPkg/Drivers/CpuDxe/CpuDxe.inf |  2 +
>>> 2 files changed, 79 insertions(+)
>>>
>>> diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.c b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
>>> index d04958e79e52..83fd6fd4e476 100644
>>> --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.c
>>> +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
>>> @@ -11,6 +11,8 @@
>>>
>>> #include <Guid/IdleLoopEvent.h>
>>>
>>> +#include <Library/MemoryAllocationLib.h>
>>> +
>>> BOOLEAN  mIsFlushingGCD;
>>>
>>> /**
>>> @@ -227,6 +229,69 @@ InitializeDma (
>>>    CpuArchProtocol->DmaBufferAlignment = ArmCacheWritebackGranule ();
>>> }
>>>
>>> +STATIC
>>> +VOID
>>> +RemapUnusedMemoryNx (
>>> +  VOID
>>> +  )
>>> +{

This feels somewhat hacky but it's strictly better than what we have 
right now and a value add so I'm all for this change.

>>> +  UINT64                     TestBit;
>>> +  UINTN                      MemoryMapSize;
>>> +  UINTN                      MapKey;
>>> +  UINTN                      DescriptorSize;
>>> +  UINT32                     DescriptorVersion;
>>> +  EFI_MEMORY_DESCRIPTOR      *MemoryMap;
>>> +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEntry;
>>> +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEnd;
>>> +  EFI_STATUS                 Status;
>>> +
>>> +  TestBit = LShiftU64 (1, EfiBootServicesData);
>>> +  if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) == 0) {
>>> +    return;
>>> +  }
>>> +
>>> +  MemoryMapSize = 0;
>>> +  MemoryMap     = NULL;
>>> +
>>> +  Status = gBS->GetMemoryMap (
>>> +                  &MemoryMapSize,
>>> +                  MemoryMap,
>>> +                  &MapKey,
>>> +                  &DescriptorSize,
>>> +                  &DescriptorVersion
>>> +                  );
>>> +  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
>>> +  do {
>>> +    MemoryMap = (EFI_MEMORY_DESCRIPTOR *)AllocatePool (MemoryMapSize);
>>
>> nitpick: *If* there is a V2, maybe drop the cast?
>>
>>> +    ASSERT (MemoryMap != NULL);
>>
>> I know ASSERTing for NULL is a common pattern for some reason, but I'd rather not have more code with this added.
>>
>>> +    Status = gBS->GetMemoryMap (
>>> +                    &MemoryMapSize,
>>> +                    MemoryMap,
>>> +                    &MapKey,
>>> +                    &DescriptorSize,
>>> +                    &DescriptorVersion
>>> +                    );
>>
>> Another nitpick, isn't it common practice to call the Core* functions directly within *Core? I know there is code that doesn't, but the former should be more efficient.
>>
>>> +    if (EFI_ERROR (Status)) {
>>> +      FreePool (MemoryMap);
>>> +    }
>>> +  } while (Status == EFI_BUFFER_TOO_SMALL);
>>
>> Is this guaranteed to terminate? I mean, I get the idea - try again with the larger size and when allocating the bigger buffer, its potential new entry will already be accounted for. However, I saw downstream code that tried something like this (they actually added a constant overhead ahead of time) bounded by something like 5 iterations. Might just have been defensive programming - probably was -, but it's not trivial to verify, I think, is it?
>>
>> Regarding the added constant, the spec actually says the following, which obviously is just to shortcut a second round of GetMemoryMap(), but still:
>> "The actual size of the buffer allocated for the consequent call to GetMemoryMap() should be bigger then the value returned in MemoryMapSize"
>>
>> It appears the spec did not define a canonical way to call GetMemoryMap(), did it? :(
>>
> 
> This is all copy-pasted from MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> 
>>> +
>>> +  ASSERT_EFI_ERROR (Status);
>>> +
>>> +  MemoryMapEntry = MemoryMap;
>>> +  MemoryMapEnd   = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + MemoryMapSize);
>>> +  while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) {
>>> +    if (MemoryMapEntry->Type == EfiConventionalMemory) {
>>> +      ArmSetMemoryRegionNoExec (
>>> +        MemoryMapEntry->PhysicalStart,
>>> +        EFI_PAGES_TO_SIZE (MemoryMapEntry->NumberOfPages)
>>> +        );
>>> +    }
>>> +
>>> +    MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
>>> +  }
>>> +}
>>> +
>>> EFI_STATUS
>>> CpuDxeInitialize (
>>>    IN EFI_HANDLE        ImageHandle,
>>> @@ -240,6 +305,18 @@ CpuDxeInitialize (
>>>
>>>    InitializeDma (&mCpu);
>>>
>>> +  //
>>> +  // Once we install the CPU arch protocol, the DXE core's memory
>>> +  // protection routines will invoke them to manage the permissions of page
>>> +  // allocations as they are created. Given that this includes pages
>>> +  // allocated for page tables by this driver, we must ensure that unused
>>> +  // memory is mapped with the same permissions as boot services data
>>> +  // regions. Otherwise, we may end up with unbounded recursion, due to the
>>> +  // fact that updating permissions on a newly allocated page table may trigger
>>> +  // a block entry split, which triggers a page table allocation, etc etc
>>> +  //
>>> +  RemapUnusedMemoryNx ();
>>
>> Hmm. I might be too tired, but why is this not already done by InitializeDxeNxMemoryProtectionPolicy(), assuming BS_Data and ConvMem have the same XP configuration?
>>
>> This reassures me that the CPU Arch protocol producers should be linked into DxeCore rather than loaded at some arbitrary point in time... Unrelated to the patch, of course.
>>
> 
> The ordering here is a bit tricky. As soon as the CPU arch protocol is
> exposed, every allocation will be remapped individually, resulting in
> page table splits and therefore recursion.
> 
> 

As Ard says, this is done in InitializeDxeNxMemoryProtectionPolicy(), 
but at that point the calls to ApplyMemoryProtectionPolicy() will cause 
the infinite recursion issue when you no longer skip applying attributes 
if the to-from types have the same NX policy.

Yes, it is arbitrary that protection is linked to the CPU Arch Protocol. 
But, it would also be bad practice to apply the memory protection policy 
before the interface for manipulating attributes has been published for 
use by modules outside of the core. Page table and translation table 
manipulation is architecturally defined, so I think a good long term 
goal should be to allow the manipulation of attributes via a library 
rather than a protocol in DXE as ARM platforms currently do.

>>
>>> +
>>>    Status = gBS->InstallMultipleProtocolInterfaces (
>>>                    &mCpuHandle,
>>>                    &gEfiCpuArchProtocolGuid,
>>> diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
>>> index e732e21cb94a..8fd0f4133088 100644
>>> --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
>>> +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.inf
>>> @@ -48,6 +48,7 @@ [LibraryClasses]
>>>    DefaultExceptionHandlerLib
>>>    DxeServicesTableLib
>>>    HobLib
>>> +  MemoryAllocationLib
>>>    PeCoffGetEntryPointLib
>>>    UefiDriverEntryPoint
>>>    UefiLib
>>> @@ -64,6 +65,7 @@ [Guids]
>>>
>>> [Pcd.common]
>>>    gArmTokenSpaceGuid.PcdVFPEnabled
>>> +  gEfiMdeModulePkgTokenSpaceGuid.PcdDxeNxMemoryProtectionPolicy
>>>
>>> [FeaturePcd.common]
>>>    gArmTokenSpaceGuid.PcdDebuggerExceptionSupport
>>> --
>>> 2.39.1
>>>
>>
>>
>>
>> 
>>
>>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections
  2023-02-08 18:25   ` Ard Biesheuvel
  2023-02-08 18:55     ` Marvin Häuser
@ 2023-02-08 19:12     ` Taylor Beebe
  2023-02-08 22:08       ` Ard Biesheuvel
  1 sibling, 1 reply; 13+ messages in thread
From: Taylor Beebe @ 2023-02-08 19:12 UTC (permalink / raw)
  To: Ard Biesheuvel, devel
  Cc: Michael Kinney, Liming Gao, Jiewen Yao, Michael Kubacki,
	Sean Brogan, Rebecca Cran, Leif Lindholm, Sami Mujawar,
	Marvin Häuser

I ran some tests and did some quick napkin math. Based on the time it 
takes to perform the SetMemoryAttributes() routine on QEMU, as long as 
<79% of the calls to ApplyMemoryProtectionPolicy() actually require a 
subsequent call to SetMemoryAttributes(), it is more efficient to walk 
the page/translation table to check if the attributes actually need to 
be updated. Even on a platform with varied NX policy settings, the 
number of times the attributes need to be updated is less than 0.0005% 
of all calls to ApplyMemoryProtectionPolicy() (likely due to most 
allocations being BootServicesData).

Once the ARM and X64 implementations of the Memory Attribute Protocol 
are in, would you be open to updating this block to utilize the protocol 
to check the attributes of the region being updated?

On 2/8/2023 10:25 AM, Ard Biesheuvel wrote:
> On Wed, 8 Feb 2023 at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
>>
>> Instead of relying on a questionable heuristic that avoids calling into
>> the SetMemoryAttributes () DXE service when the old memory type and the
>> new one are subjected to the same NX memory protection policy, make this
>> call unconditionally. This avoids corner cases where memory region
>> attributes are out of sync with the policy, either due to the fact that
>> we are in the middle of ramping up the protections, or due to explicit
>> invocations of SetMemoryAttributes() by drivers.
>>
>> This requires the architecture page table code to be able to deal with
>> this, in particular, it needs to be robust against potential recursion
>> due to NX policies being applied to newly allocated page tables.
>>
>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>> ---
>>   MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c | 29 --------------------
>>   1 file changed, 29 deletions(-)
>>
>> diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
>> index 36987843f142..503feb72b5d0 100644
>> --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
>> +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
>> @@ -1263,9 +1263,7 @@ ApplyMemoryProtectionPolicy (
>>     IN  UINT64                Length
>>     )
>>   {
>> -  UINT64      OldAttributes;
>>     UINT64      NewAttributes;
>> -  EFI_STATUS  Status;
>>
>>     //
>>     // The policy configured in PcdDxeNxMemoryProtectionPolicy
>> @@ -1320,32 +1318,5 @@ ApplyMemoryProtectionPolicy (
>>     //
>>     NewAttributes = GetPermissionAttributeForMemoryType (NewType);
>>
>> -  if (OldType != EfiMaxMemoryType) {
>> -    OldAttributes = GetPermissionAttributeForMemoryType (OldType);
>> -    if (!mAfterDxeNxMemoryProtectionInit &&
>> -        (OldAttributes == NewAttributes)) {
>> -      return EFI_SUCCESS;
>> -    }
>> -
> 
> This removes some code that does not actually exist - apologies.
> 
> It comes down to just removing the conditional checks here, though,
> and perform the tail call below unconditionally.
> 
>> -    //
>> -    // If available, use the EFI memory attribute protocol to obtain
>> -    // the current attributes of the region. If the entire region is
>> -    // covered and the attributes match, we don't have to do anything.
>> -    //
>> -    if (mMemoryAttribute != NULL) {
>> -      Status = mMemoryAttribute->GetMemoryAttributes (mMemoryAttribute,
>> -                                                      Memory,
>> -                                                      Length,
>> -                                                      &OldAttributes
>> -                                                      );
>> -      if (!EFI_ERROR (Status) && (OldAttributes == NewAttributes)) {
>> -        return EFI_SUCCESS;
>> -      }
>> -    }
>> -  } else if (NewAttributes == 0) {
>> -    // newly added region of a type that does not require protection
>> -    return EFI_SUCCESS;
>> -  }
>> -
>>     return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes);
>>   }
>> --
>> 2.39.1
>>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections
  2023-02-08 19:12     ` Taylor Beebe
@ 2023-02-08 22:08       ` Ard Biesheuvel
  2023-02-08 22:24         ` Taylor Beebe
  0 siblings, 1 reply; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 22:08 UTC (permalink / raw)
  To: Taylor Beebe
  Cc: devel, Michael Kinney, Liming Gao, Jiewen Yao, Michael Kubacki,
	Sean Brogan, Rebecca Cran, Leif Lindholm, Sami Mujawar,
	Marvin Häuser

On Wed, 8 Feb 2023 at 20:12, Taylor Beebe <t@taylorbeebe.com> wrote:
>
> I ran some tests and did some quick napkin math. Based on the time it
> takes to perform the SetMemoryAttributes() routine on QEMU, as long as
> <79% of the calls to ApplyMemoryProtectionPolicy() actually require a
> subsequent call to SetMemoryAttributes(), it is more efficient to walk
> the page/translation table to check if the attributes actually need to
> be updated. Even on a platform with varied NX policy settings, the
> number of times the attributes need to be updated is less than 0.0005%
> of all calls to ApplyMemoryProtectionPolicy() (likely due to most
> allocations being BootServicesData).
>
> Once the ARM and X64 implementations of the Memory Attribute Protocol
> are in, would you be open to updating this block to utilize the protocol
> to check the attributes of the region being updated?
>

Yes, that was what I had in my initial prototype.

However, I'm not sure how walking the page tables to retrieve all
existing attributes is fundamentally different from walking the page
tables to set them, given that everything is cached and we are running
uniprocessor at this point.


> On 2/8/2023 10:25 AM, Ard Biesheuvel wrote:
> > On Wed, 8 Feb 2023 at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
> >>
> >> Instead of relying on a questionable heuristic that avoids calling into
> >> the SetMemoryAttributes () DXE service when the old memory type and the
> >> new one are subjected to the same NX memory protection policy, make this
> >> call unconditionally. This avoids corner cases where memory region
> >> attributes are out of sync with the policy, either due to the fact that
> >> we are in the middle of ramping up the protections, or due to explicit
> >> invocations of SetMemoryAttributes() by drivers.
> >>
> >> This requires the architecture page table code to be able to deal with
> >> this, in particular, it needs to be robust against potential recursion
> >> due to NX policies being applied to newly allocated page tables.
> >>
> >> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> >> ---
> >>   MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c | 29 --------------------
> >>   1 file changed, 29 deletions(-)
> >>
> >> diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> >> index 36987843f142..503feb72b5d0 100644
> >> --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> >> +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> >> @@ -1263,9 +1263,7 @@ ApplyMemoryProtectionPolicy (
> >>     IN  UINT64                Length
> >>     )
> >>   {
> >> -  UINT64      OldAttributes;
> >>     UINT64      NewAttributes;
> >> -  EFI_STATUS  Status;
> >>
> >>     //
> >>     // The policy configured in PcdDxeNxMemoryProtectionPolicy
> >> @@ -1320,32 +1318,5 @@ ApplyMemoryProtectionPolicy (
> >>     //
> >>     NewAttributes = GetPermissionAttributeForMemoryType (NewType);
> >>
> >> -  if (OldType != EfiMaxMemoryType) {
> >> -    OldAttributes = GetPermissionAttributeForMemoryType (OldType);
> >> -    if (!mAfterDxeNxMemoryProtectionInit &&
> >> -        (OldAttributes == NewAttributes)) {
> >> -      return EFI_SUCCESS;
> >> -    }
> >> -
> >
> > This removes some code that does not actually exist - apologies.
> >
> > It comes down to just removing the conditional checks here, though,
> > and perform the tail call below unconditionally.
> >
> >> -    //
> >> -    // If available, use the EFI memory attribute protocol to obtain
> >> -    // the current attributes of the region. If the entire region is
> >> -    // covered and the attributes match, we don't have to do anything.
> >> -    //
> >> -    if (mMemoryAttribute != NULL) {
> >> -      Status = mMemoryAttribute->GetMemoryAttributes (mMemoryAttribute,
> >> -                                                      Memory,
> >> -                                                      Length,
> >> -                                                      &OldAttributes
> >> -                                                      );
> >> -      if (!EFI_ERROR (Status) && (OldAttributes == NewAttributes)) {
> >> -        return EFI_SUCCESS;
> >> -      }
> >> -    }
> >> -  } else if (NewAttributes == 0) {
> >> -    // newly added region of a type that does not require protection
> >> -    return EFI_SUCCESS;
> >> -  }
> >> -
> >>     return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes);
> >>   }
> >> --
> >> 2.39.1
> >>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections
  2023-02-08 22:08       ` Ard Biesheuvel
@ 2023-02-08 22:24         ` Taylor Beebe
  0 siblings, 0 replies; 13+ messages in thread
From: Taylor Beebe @ 2023-02-08 22:24 UTC (permalink / raw)
  To: Ard Biesheuvel
  Cc: devel, Michael Kinney, Liming Gao, Jiewen Yao, Michael Kubacki,
	Sean Brogan, Rebecca Cran, Leif Lindholm, Sami Mujawar,
	Marvin Häuser



On 2/8/2023 2:08 PM, Ard Biesheuvel wrote:
> On Wed, 8 Feb 2023 at 20:12, Taylor Beebe <t@taylorbeebe.com> wrote:
>>
>> I ran some tests and did some quick napkin math. Based on the time it
>> takes to perform the SetMemoryAttributes() routine on QEMU, as long as
>> <79% of the calls to ApplyMemoryProtectionPolicy() actually require a
>> subsequent call to SetMemoryAttributes(), it is more efficient to walk
>> the page/translation table to check if the attributes actually need to
>> be updated. Even on a platform with varied NX policy settings, the
>> number of times the attributes need to be updated is less than 0.0005%
>> of all calls to ApplyMemoryProtectionPolicy() (likely due to most
>> allocations being BootServicesData).
>>
>> Once the ARM and X64 implementations of the Memory Attribute Protocol
>> are in, would you be open to updating this block to utilize the protocol
>> to check the attributes of the region being updated?
>>
> 
> Yes, that was what I had in my initial prototype.
> 
> However, I'm not sure how walking the page tables to retrieve all
> existing attributes is fundamentally different from walking the page
> tables to set them, given that everything is cached and we are running
> uniprocessor at this point.
> 

I was also skeptical, but running the experiment revealed that checking 
the page table is the clear winner. My guess is that branch prediction 
expects the table walk to reveal that the SetAttributes() call is 
unnecessary, and the cost of doing an instruction pipeline flush due to 
improper branch prediction is similar to the cost of doing a TLB 
flush/invalidation after the call to SetAttributes() making the upside 
outweigh the downside in almost all cases.

> 
>> On 2/8/2023 10:25 AM, Ard Biesheuvel wrote:
>>> On Wed, 8 Feb 2023 at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
>>>>
>>>> Instead of relying on a questionable heuristic that avoids calling into
>>>> the SetMemoryAttributes () DXE service when the old memory type and the
>>>> new one are subjected to the same NX memory protection policy, make this
>>>> call unconditionally. This avoids corner cases where memory region
>>>> attributes are out of sync with the policy, either due to the fact that
>>>> we are in the middle of ramping up the protections, or due to explicit
>>>> invocations of SetMemoryAttributes() by drivers.
>>>>
>>>> This requires the architecture page table code to be able to deal with
>>>> this, in particular, it needs to be robust against potential recursion
>>>> due to NX policies being applied to newly allocated page tables.
>>>>
>>>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
>>>> ---
>>>>    MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c | 29 --------------------
>>>>    1 file changed, 29 deletions(-)
>>>>
>>>> diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
>>>> index 36987843f142..503feb72b5d0 100644
>>>> --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
>>>> +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
>>>> @@ -1263,9 +1263,7 @@ ApplyMemoryProtectionPolicy (
>>>>      IN  UINT64                Length
>>>>      )
>>>>    {
>>>> -  UINT64      OldAttributes;
>>>>      UINT64      NewAttributes;
>>>> -  EFI_STATUS  Status;
>>>>
>>>>      //
>>>>      // The policy configured in PcdDxeNxMemoryProtectionPolicy
>>>> @@ -1320,32 +1318,5 @@ ApplyMemoryProtectionPolicy (
>>>>      //
>>>>      NewAttributes = GetPermissionAttributeForMemoryType (NewType);
>>>>
>>>> -  if (OldType != EfiMaxMemoryType) {
>>>> -    OldAttributes = GetPermissionAttributeForMemoryType (OldType);
>>>> -    if (!mAfterDxeNxMemoryProtectionInit &&
>>>> -        (OldAttributes == NewAttributes)) {
>>>> -      return EFI_SUCCESS;
>>>> -    }
>>>> -
>>>
>>> This removes some code that does not actually exist - apologies.
>>>
>>> It comes down to just removing the conditional checks here, though,
>>> and perform the tail call below unconditionally.
>>>
>>>> -    //
>>>> -    // If available, use the EFI memory attribute protocol to obtain
>>>> -    // the current attributes of the region. If the entire region is
>>>> -    // covered and the attributes match, we don't have to do anything.
>>>> -    //
>>>> -    if (mMemoryAttribute != NULL) {
>>>> -      Status = mMemoryAttribute->GetMemoryAttributes (mMemoryAttribute,
>>>> -                                                      Memory,
>>>> -                                                      Length,
>>>> -                                                      &OldAttributes
>>>> -                                                      );
>>>> -      if (!EFI_ERROR (Status) && (OldAttributes == NewAttributes)) {
>>>> -        return EFI_SUCCESS;
>>>> -      }
>>>> -    }
>>>> -  } else if (NewAttributes == 0) {
>>>> -    // newly added region of a type that does not require protection
>>>> -    return EFI_SUCCESS;
>>>> -  }
>>>> -
>>>>      return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes);
>>>>    }
>>>> --
>>>> 2.39.1
>>>>

-- 
Taylor Beebe
Software Engineer @ Microsoft

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [edk2-devel] [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory
  2023-02-08 18:57       ` Taylor Beebe
@ 2023-02-08 22:52         ` Ard Biesheuvel
  0 siblings, 0 replies; 13+ messages in thread
From: Ard Biesheuvel @ 2023-02-08 22:52 UTC (permalink / raw)
  To: Taylor Beebe
  Cc: devel, mhaeuser, Michael Kinney, Liming Gao, Jiewen Yao,
	Michael Kubacki, Sean Brogan, Rebecca Cran, Leif Lindholm,
	Sami Mujawar

On Wed, 8 Feb 2023 at 19:57, Taylor Beebe <t@taylorbeebe.com> wrote:
>
>
>
> On 2/8/2023 10:49 AM, Ard Biesheuvel wrote:
> > On Wed, 8 Feb 2023 at 19:32, Marvin Häuser <mhaeuser@posteo.de> wrote:
> >>
> >> Thanks!! :) Comments inline.
> >>
> >>> On 8. Feb 2023, at 18:58, Ard Biesheuvel <ardb@kernel.org> wrote:
> >>>
> >>> The DXE core implementation of PcdDxeNxMemoryProtectionPolicy already
> >>> contains an assertion that EfiConventionalMemory and EfiBootServicesData
> >>> are subjected to the same policy when it comes to the use of NX
> >>> permissions. The reason for this is that we may otherwise end up with
> >>> unbounded recursion in the page table code, given that allocating a page
> >>> table would then involve a permission attribute change, and this could
> >>> result in the need for a block entry to be split, which would trigger
> >>> the allocation of a page table recursively.
> >>>
> >>> For the same reason, a shortcut exists in ApplyMemoryProtectionPolicy()
> >>> where, instead of setting the memory attributes unconditionally, we
> >>> compare the NX policies and avoid touching the page tables if they are
> >>> the same for the old and the new memory types. Without this shortcut, we
> >>> may end up in a situation where, as the CPU arch protocol DXE driver is
> >>> ramping up, the same unbounded recursion is triggered, due to the fact
> >>> that the NX policy for EfiConventionalMemory has not been applied yet.
> >>>
> >>> To break this cycle, let's remap all EfiConventionalMemory regions
> >>> according to the NX policy for EfiBootServicesData before exposing the
> >>> CPU arch protocol to the DXE core and other drivers. This ensures that
> >>> creating EfiBootServicesData allocations does not result in memory
> >>> attribute changes, and therefore no recursion.
> >>>
> >>> Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
> >>> ---
> >>> ArmPkg/Drivers/CpuDxe/CpuDxe.c   | 77 ++++++++++++++++++++
> >>> ArmPkg/Drivers/CpuDxe/CpuDxe.inf |  2 +
> >>> 2 files changed, 79 insertions(+)
> >>>
> >>> diff --git a/ArmPkg/Drivers/CpuDxe/CpuDxe.c b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> >>> index d04958e79e52..83fd6fd4e476 100644
> >>> --- a/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> >>> +++ b/ArmPkg/Drivers/CpuDxe/CpuDxe.c
> >>> @@ -11,6 +11,8 @@
> >>>
> >>> #include <Guid/IdleLoopEvent.h>
> >>>
> >>> +#include <Library/MemoryAllocationLib.h>
> >>> +
> >>> BOOLEAN  mIsFlushingGCD;
> >>>
> >>> /**
> >>> @@ -227,6 +229,69 @@ InitializeDma (
> >>>    CpuArchProtocol->DmaBufferAlignment = ArmCacheWritebackGranule ();
> >>> }
> >>>
> >>> +STATIC
> >>> +VOID
> >>> +RemapUnusedMemoryNx (
> >>> +  VOID
> >>> +  )
> >>> +{
>
> This feels somewhat hacky but it's strictly better than what we have
> right now and a value add so I'm all for this change.
>
> >>> +  UINT64                     TestBit;
> >>> +  UINTN                      MemoryMapSize;
> >>> +  UINTN                      MapKey;
> >>> +  UINTN                      DescriptorSize;
> >>> +  UINT32                     DescriptorVersion;
> >>> +  EFI_MEMORY_DESCRIPTOR      *MemoryMap;
> >>> +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEntry;
> >>> +  EFI_MEMORY_DESCRIPTOR      *MemoryMapEnd;
> >>> +  EFI_STATUS                 Status;
> >>> +
> >>> +  TestBit = LShiftU64 (1, EfiBootServicesData);
> >>> +  if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) == 0) {
> >>> +    return;
> >>> +  }
> >>> +
> >>> +  MemoryMapSize = 0;
> >>> +  MemoryMap     = NULL;
> >>> +
> >>> +  Status = gBS->GetMemoryMap (
> >>> +                  &MemoryMapSize,
> >>> +                  MemoryMap,
> >>> +                  &MapKey,
> >>> +                  &DescriptorSize,
> >>> +                  &DescriptorVersion
> >>> +                  );
> >>> +  ASSERT (Status == EFI_BUFFER_TOO_SMALL);
> >>> +  do {
> >>> +    MemoryMap = (EFI_MEMORY_DESCRIPTOR *)AllocatePool (MemoryMapSize);
> >>
> >> nitpick: *If* there is a V2, maybe drop the cast?
> >>
> >>> +    ASSERT (MemoryMap != NULL);
> >>
> >> I know ASSERTing for NULL is a common pattern for some reason, but I'd rather not have more code with this added.
> >>
> >>> +    Status = gBS->GetMemoryMap (
> >>> +                    &MemoryMapSize,
> >>> +                    MemoryMap,
> >>> +                    &MapKey,
> >>> +                    &DescriptorSize,
> >>> +                    &DescriptorVersion
> >>> +                    );
> >>
> >> Another nitpick, isn't it common practice to call the Core* functions directly within *Core? I know there is code that doesn't, but the former should be more efficient.
> >>
> >>> +    if (EFI_ERROR (Status)) {
> >>> +      FreePool (MemoryMap);
> >>> +    }
> >>> +  } while (Status == EFI_BUFFER_TOO_SMALL);
> >>
> >> Is this guaranteed to terminate? I mean, I get the idea - try again with the larger size and when allocating the bigger buffer, its potential new entry will already be accounted for. However, I saw downstream code that tried something like this (they actually added a constant overhead ahead of time) bounded by something like 5 iterations. Might just have been defensive programming - probably was -, but it's not trivial to verify, I think, is it?
> >>
> >> Regarding the added constant, the spec actually says the following, which obviously is just to shortcut a second round of GetMemoryMap(), but still:
> >> "The actual size of the buffer allocated for the consequent call to GetMemoryMap() should be bigger then the value returned in MemoryMapSize"
> >>
> >> It appears the spec did not define a canonical way to call GetMemoryMap(), did it? :(
> >>
> >
> > This is all copy-pasted from MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c
> >
> >>> +
> >>> +  ASSERT_EFI_ERROR (Status);
> >>> +
> >>> +  MemoryMapEntry = MemoryMap;
> >>> +  MemoryMapEnd   = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + MemoryMapSize);
> >>> +  while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) {
> >>> +    if (MemoryMapEntry->Type == EfiConventionalMemory) {
> >>> +      ArmSetMemoryRegionNoExec (
> >>> +        MemoryMapEntry->PhysicalStart,
> >>> +        EFI_PAGES_TO_SIZE (MemoryMapEntry->NumberOfPages)
> >>> +        );
> >>> +    }
> >>> +
> >>> +    MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
> >>> +  }
> >>> +}
> >>> +
> >>> EFI_STATUS
> >>> CpuDxeInitialize (
> >>>    IN EFI_HANDLE        ImageHandle,
> >>> @@ -240,6 +305,18 @@ CpuDxeInitialize (
> >>>
> >>>    InitializeDma (&mCpu);
> >>>
> >>> +  //
> >>> +  // Once we install the CPU arch protocol, the DXE core's memory
> >>> +  // protection routines will invoke them to manage the permissions of page
> >>> +  // allocations as they are created. Given that this includes pages
> >>> +  // allocated for page tables by this driver, we must ensure that unused
> >>> +  // memory is mapped with the same permissions as boot services data
> >>> +  // regions. Otherwise, we may end up with unbounded recursion, due to the
> >>> +  // fact that updating permissions on a newly allocated page table may trigger
> >>> +  // a block entry split, which triggers a page table allocation, etc etc
> >>> +  //
> >>> +  RemapUnusedMemoryNx ();
> >>
> >> Hmm. I might be too tired, but why is this not already done by InitializeDxeNxMemoryProtectionPolicy(), assuming BS_Data and ConvMem have the same XP configuration?
> >>
> >> This reassures me that the CPU Arch protocol producers should be linked into DxeCore rather than loaded at some arbitrary point in time... Unrelated to the patch, of course.
> >>
> >
> > The ordering here is a bit tricky. As soon as the CPU arch protocol is
> > exposed, every allocation will be remapped individually, resulting in
> > page table splits and therefore recursion.
> >
> >
>
> As Ard says, this is done in InitializeDxeNxMemoryProtectionPolicy(),
> but at that point the calls to ApplyMemoryProtectionPolicy() will cause
> the infinite recursion issue when you no longer skip applying attributes
> if the to-from types have the same NX policy.
>
> Yes, it is arbitrary that protection is linked to the CPU Arch Protocol.
> But, it would also be bad practice to apply the memory protection policy
> before the interface for manipulating attributes has been published for
> use by modules outside of the core. Page table and translation table
> manipulation is architecturally defined, so I think a good long term
> goal should be to allow the manipulation of attributes via a library
> rather than a protocol in DXE as ARM platforms currently do.
>

Note that the routine above is slightly dodgy in the sense that the
memory map might change while it is being traversed, due to the page
allocations that result from splitting block entries. This is why it
is important that EfiConventionalMemory and EfiBootServicesData use
the same NX policy, so that such changes to the memory map do not
affect the outcome of the traversal.

If we want to move this into generic code, I think it should be
feasible for the DXE core to
- grab the CPU arch protocol but don't make it available globally
right away, so that AllocatePages() will not recurse
- call the CPU arch protocol to map all EfiConventionalMemory as NX
(with the caveat above)
- proceed with remapping the active regions, which will now set the
attributes on all EfiBootServicesData regions again.

That would make the thing a bit more robust, but I am reluctant to add
this to generic code and deal with the fallout on x86 or RISC-V if it
breaks something there.

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2023-02-08 22:53 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-02-08 17:58 [PATCH 0/3] Apply NX protections more strictly Ard Biesheuvel
2023-02-08 17:58 ` [PATCH 1/3] ArmPkg/ArmMmuLib: Avoid splitting block entries if possible Ard Biesheuvel
2023-02-08 17:58 ` [PATCH 2/3] ArmPkg/CpuDxe: Perform preliminary NX remap of free memory Ard Biesheuvel
2023-02-08 18:32   ` Marvin Häuser
2023-02-08 18:49     ` [edk2-devel] " Ard Biesheuvel
2023-02-08 18:57       ` Taylor Beebe
2023-02-08 22:52         ` Ard Biesheuvel
2023-02-08 17:58 ` [PATCH 3/3] MdeModulePkg/DxeCore: Unconditionally set memory protections Ard Biesheuvel
2023-02-08 18:25   ` Ard Biesheuvel
2023-02-08 18:55     ` Marvin Häuser
2023-02-08 19:12     ` Taylor Beebe
2023-02-08 22:08       ` Ard Biesheuvel
2023-02-08 22:24         ` Taylor Beebe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox