Blame SOURCES/edk2-UefiCpuPkg-PiSmmCpuDxeSmm-fix-2M-4K-page-splitting-r.patch

63d87e
From 2613601640be75f79e9dd8d2db21ad45d227d907 Mon Sep 17 00:00:00 2001
63d87e
From: Laszlo Ersek <lersek@redhat.com>
63d87e
Date: Fri, 17 Jan 2020 11:33:43 +0100
63d87e
Subject: [PATCH] UefiCpuPkg/PiSmmCpuDxeSmm: fix 2M->4K page splitting
63d87e
 regression for PDEs
63d87e
MIME-Version: 1.0
63d87e
Content-Type: text/plain; charset=UTF-8
63d87e
Content-Transfer-Encoding: 8bit
63d87e
63d87e
RH-Author: Laszlo Ersek <lersek@redhat.com>
63d87e
Message-id: <20200117113343.30392-2-lersek@redhat.com>
63d87e
Patchwork-id: 93389
63d87e
O-Subject: [RHEL-8.2.0 edk2 PATCH 1/1] UefiCpuPkg/PiSmmCpuDxeSmm: fix 2M->4K page splitting regression for PDEs
63d87e
Bugzilla: 1789335
63d87e
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
63d87e
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
63d87e
63d87e
In commit 4eee0cc7cc0d ("UefiCpuPkg/PiSmmCpu: Enable 5 level paging when
63d87e
CPU supports", 2019-07-12), the Page Directory Entry setting was regressed
63d87e
(corrupted) when splitting a 2MB page to 512 4KB pages, in the
63d87e
InitPaging() function.
63d87e
63d87e
Consider the following hunk, displayed with
63d87e
63d87e
$ git show --function-context --ignore-space-change 4eee0cc7cc0db
63d87e
63d87e
>            //
63d87e
>            // If it is 2M page, check IsAddressSplit()
63d87e
>            //
63d87e
>            if (((*Pd & IA32_PG_PS) != 0) && IsAddressSplit (Address)) {
63d87e
>              //
63d87e
>              // Based on current page table, create 4KB page table for split area.
63d87e
>              //
63d87e
>              ASSERT (Address == (*Pd & PHYSICAL_ADDRESS_MASK));
63d87e
>
63d87e
>              Pt = AllocatePageTableMemory (1);
63d87e
>              ASSERT (Pt != NULL);
63d87e
>
63d87e
> +            *Pd = (UINTN) Pt | IA32_PG_RW | IA32_PG_P;
63d87e
> +
63d87e
>              // Split it
63d87e
> -          for (PtIndex = 0; PtIndex < SIZE_4KB / sizeof(*Pt); PtIndex++) {
63d87e
> -            Pt[PtIndex] = Address + ((PtIndex << 12) | mAddressEncMask | PAGE_ATTRIBUTE_BITS);
63d87e
> +            for (PtIndex = 0; PtIndex < SIZE_4KB / sizeof(*Pt); PtIndex++, Pt++) {
63d87e
> +              *Pt = Address + ((PtIndex << 12) | mAddressEncMask | PAGE_ATTRIBUTE_BITS);
63d87e
>              } // end for PT
63d87e
>              *Pd = (UINT64)(UINTN)Pt | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
63d87e
>            } // end if IsAddressSplit
63d87e
>          } // end for PD
63d87e
63d87e
First, the new assignment to the Page Directory Entry (*Pd) is
63d87e
superfluous. That's because (a) we set (*Pd) after the Page Table Entry
63d87e
loop anyway, and (b) here we do not attempt to access the memory starting
63d87e
at "Address" (which is mapped by the original value of the Page Directory
63d87e
Entry).
63d87e
63d87e
Second, appending "Pt++" to the incrementing expression of the PTE loop is
63d87e
a bug. It causes "Pt" to point *right past* the just-allocated Page Table,
63d87e
once we finish the loop. But the PDE assignment that immediately follows
63d87e
the loop assumes that "Pt" still points to the *start* of the new Page
63d87e
Table.
63d87e
63d87e
The result is that the originally mapped 2MB page disappears from the
63d87e
processor's view. The PDE now points to a "Page Table" that is filled with
63d87e
garbage. The random entries in that "Page Table" will cause some virtual
63d87e
addresses in the original 2MB area to fault. Other virtual addresses in
63d87e
the same range will no longer have a 1:1 physical mapping, but be
63d87e
scattered over random physical page frames.
63d87e
63d87e
The second phase of the InitPaging() function ("Go through page table and
63d87e
set several page table entries to absent or execute-disable") already
63d87e
manipulates entries in wrong Page Tables, for such PDEs that got split in
63d87e
the first phase.
63d87e
63d87e
This issue has been caught as follows:
63d87e
63d87e
- OVMF is started with 2001 MB of guest RAM.
63d87e
63d87e
- This places the main SMRAM window at 0x7C10_1000.
63d87e
63d87e
- The SMRAM management in the SMM Core links this SMRAM window into
63d87e
  "mSmmMemoryMap", with a FREE_PAGE_LIST record placed at the start of the
63d87e
  area.
63d87e
63d87e
- At "SMM Ready To Lock" time, PiSmmCpuDxeSmm calls InitPaging(). The
63d87e
  first phase (quoted above) decides to split the 2MB page at 0x7C00_0000
63d87e
  into 512 4KB pages, and corrupts the PDE. The new Page Table is
63d87e
  allocated at 0x7CE0_D000, but the PDE is set to 0x7CE0_E000 (plus
63d87e
  attributes 0x67).
63d87e
63d87e
- Due to the corrupted PDE, the second phase of InitPaging() already looks
63d87e
  up the PTE for Address=0x7C10_1000 in the wrong place. The second phase
63d87e
  goes on to mark bogus PTEs as "NX".
63d87e
63d87e
- PiSmmCpuDxeSmm calls SetMemMapAttributes(). Address 0x7C10_1000 is at
63d87e
  the base of the SMRAM window, therefore it happens to be listed in the
63d87e
  SMRAM map as an EfiConventionalMemory region. SetMemMapAttributes()
63d87e
  calls SmmSetMemoryAttributes() to mark the region as XP. However,
63d87e
  GetPageTableEntry() in ConvertMemoryPageAttributes() fails -- address
63d87e
  0x7C10_1000 is no longer mapped by anything! -- and so the attribute
63d87e
  setting fails with RETURN_UNSUPPORTED. This error goes unnoticed, as
63d87e
  SetMemMapAttributes() ignores the return value of
63d87e
  SmmSetMemoryAttributes().
63d87e
63d87e
- When SetMemMapAttributes() reaches another entry in the SMRAM map,
63d87e
  ConvertMemoryPageAttributes() decides it needs to split a 2MB page, and
63d87e
  calls SplitPage().
63d87e
63d87e
- SplitPage() calls AllocatePageTableMemory() for the new Page Table,
63d87e
  which takes us to InternalAllocMaxAddress() in the SMM Core.
63d87e
63d87e
- The SMM core attempts to read the FREE_PAGE_LIST record at 0x7C10_1000.
63d87e
  Because this virtual address is no longer mapped, the firmware crashes
63d87e
  in InternalAllocMaxAddress(), when accessing (Pages->NumberOfPages).
63d87e
63d87e
Remove the useless assignment to (*Pd) from before the loop. Revert the
63d87e
loop incrementing and the PTE assignment to the known good version.
63d87e
63d87e
Cc: Eric Dong <eric.dong@intel.com>
63d87e
Cc: Ray Ni <ray.ni@intel.com>
63d87e
Ref: https://bugzilla.redhat.com/show_bug.cgi?id=1789335
63d87e
Fixes: 4eee0cc7cc0db74489b99c19eba056b53eda6358
63d87e
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
63d87e
Reviewed-by: Philippe Mathieu-Daude <philmd@redhat.com>
63d87e
Reviewed-by: Ray Ni <ray.ni@intel.com>
63d87e
(cherry picked from commit a5235562444021e9c5aff08f45daa6b5b7952c7a)
63d87e
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
63d87e
---
63d87e
 UefiCpuPkg/PiSmmCpuDxeSmm/SmmProfile.c | 6 ++----
63d87e
 1 file changed, 2 insertions(+), 4 deletions(-)
63d87e
63d87e
diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/SmmProfile.c b/UefiCpuPkg/PiSmmCpuDxeSmm/SmmProfile.c
63d87e
index c513152..c47b557 100644
63d87e
--- a/UefiCpuPkg/PiSmmCpuDxeSmm/SmmProfile.c
63d87e
+++ b/UefiCpuPkg/PiSmmCpuDxeSmm/SmmProfile.c
63d87e
@@ -657,11 +657,9 @@ InitPaging (
63d87e
             Pt = AllocatePageTableMemory (1);
63d87e
             ASSERT (Pt != NULL);
63d87e
 
63d87e
-            *Pd = (UINTN) Pt | IA32_PG_RW | IA32_PG_P;
63d87e
-
63d87e
             // Split it
63d87e
-            for (PtIndex = 0; PtIndex < SIZE_4KB / sizeof(*Pt); PtIndex++, Pt++) {
63d87e
-              *Pt = Address + ((PtIndex << 12) | mAddressEncMask | PAGE_ATTRIBUTE_BITS);
63d87e
+            for (PtIndex = 0; PtIndex < SIZE_4KB / sizeof(*Pt); PtIndex++) {
63d87e
+              Pt[PtIndex] = Address + ((PtIndex << 12) | mAddressEncMask | PAGE_ATTRIBUTE_BITS);
63d87e
             } // end for PT
63d87e
             *Pd = (UINT64)(UINTN)Pt | mAddressEncMask | PAGE_ATTRIBUTE_BITS;
63d87e
           } // end if IsAddressSplit
63d87e
-- 
63d87e
1.8.3.1
63d87e