]> git.proxmox.com Git - mirror_edk2.git/blobdiff - UefiCpuPkg/CpuDxe/CpuPageTable.c
UefiCpuPkg/CpuDxe: Enable protection for newly added page table
[mirror_edk2.git] / UefiCpuPkg / CpuDxe / CpuPageTable.c
index 9658ed74c557dbfcd574a1995b899d4a4d556f44..a9c9bc9d5e9079f54b014ac880be5787c47e686c 100644 (file)
@@ -87,6 +87,8 @@ PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = {
   {Page1G,  SIZE_1GB, PAGING_1G_ADDRESS_MASK_64},\r
 };\r
 \r
+PAGE_TABLE_POOL   *mPageTablePool = NULL;\r
+\r
 /**\r
   Enable write protection function for AP.\r
 \r
@@ -172,10 +174,6 @@ GetCurrentPagingContext (
   }\r
   if ((AsmReadCr0 () & BIT31) != 0) {\r
     PagingContext->ContextData.X64.PageTableBase = (AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64);\r
-    if ((AsmReadCr0 () & BIT16) == 0) {\r
-      AsmWriteCr0 (AsmReadCr0 () | BIT16);\r
-      SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);\r
-    }\r
   } else {\r
     PagingContext->ContextData.X64.PageTableBase = 0;\r
   }\r
@@ -561,6 +559,59 @@ SplitPage (
   }\r
 }\r
 \r
+/**\r
+ Check the WP status in CR0 register. This bit is used to lock or unlock write\r
+ access to pages marked as read-only.\r
+\r
+  @retval TRUE    Write protection is enabled.\r
+  @retval FALSE   Write protection is disabled.\r
+**/\r
+BOOLEAN\r
+IsReadOnlyPageWriteProtected (\r
+  VOID\r
+  )\r
+{\r
+  return ((AsmReadCr0 () & BIT16) != 0);\r
+}\r
+\r
+/**\r
+  Disable write protection function for AP.\r
+\r
+  @param[in,out] Buffer  The pointer to private data buffer.\r
+**/\r
+VOID\r
+EFIAPI\r
+SyncCpuDisableWriteProtection (\r
+  IN OUT VOID *Buffer\r
+  )\r
+{\r
+  AsmWriteCr0 (AsmReadCr0() & ~BIT16);\r
+}\r
+\r
+/**\r
+ Disable Write Protect on pages marked as read-only.\r
+**/\r
+VOID\r
+DisableReadOnlyPageWriteProtect (\r
+  VOID\r
+  )\r
+{\r
+  AsmWriteCr0 (AsmReadCr0() & ~BIT16);\r
+  SyncMemoryPageAttributesAp (SyncCpuDisableWriteProtection);\r
+}\r
+\r
+/**\r
+ Enable Write Protect on pages marked as read-only.\r
+**/\r
+VOID\r
+EnableReadOnlyPageWriteProtect (\r
+  VOID\r
+  )\r
+{\r
+  AsmWriteCr0 (AsmReadCr0() | BIT16);\r
+  SyncMemoryPageAttributesAp (SyncCpuEnableWriteProtection);\r
+}\r
+\r
 /**\r
   This function modifies the page attributes for the memory region specified by BaseAddress and\r
   Length from their current attributes to the attributes specified by Attributes.\r
@@ -609,6 +660,7 @@ ConvertMemoryPageAttributes (
   PAGE_ATTRIBUTE                    SplitAttribute;\r
   RETURN_STATUS                     Status;\r
   BOOLEAN                           IsEntryModified;\r
+  BOOLEAN                           IsWpEnabled;\r
 \r
   if ((BaseAddress & (SIZE_4KB - 1)) != 0) {\r
     DEBUG ((DEBUG_ERROR, "BaseAddress(0x%lx) is not aligned!\n", BaseAddress));\r
@@ -665,14 +717,27 @@ ConvertMemoryPageAttributes (
   if (IsModified != NULL) {\r
     *IsModified = FALSE;\r
   }\r
+  if (AllocatePagesFunc == NULL) {\r
+    AllocatePagesFunc = AllocatePageTableMemory;\r
+  }\r
+\r
+  //\r
+  // Make sure that the page table is changeable.\r
+  //\r
+  IsWpEnabled = IsReadOnlyPageWriteProtected ();\r
+  if (IsWpEnabled) {\r
+    DisableReadOnlyPageWriteProtect ();\r
+  }\r
 \r
   //\r
   // Below logic is to check 2M/4K page to make sure we donot waist memory.\r
   //\r
+  Status = EFI_SUCCESS;\r
   while (Length != 0) {\r
     PageEntry = GetPageTableEntry (&CurrentPagingContext, BaseAddress, &PageAttribute);\r
     if (PageEntry == NULL) {\r
-      return RETURN_UNSUPPORTED;\r
+      Status = RETURN_UNSUPPORTED;\r
+      goto Done;\r
     }\r
     PageEntryLength = PageAttributeToLength (PageAttribute);\r
     SplitAttribute = NeedSplitPage (BaseAddress, Length, PageEntry, PageAttribute);\r
@@ -690,11 +755,13 @@ ConvertMemoryPageAttributes (
       Length -= PageEntryLength;\r
     } else {\r
       if (AllocatePagesFunc == NULL) {\r
-        return RETURN_UNSUPPORTED;\r
+        Status = RETURN_UNSUPPORTED;\r
+        goto Done;\r
       }\r
       Status = SplitPage (PageEntry, PageAttribute, SplitAttribute, AllocatePagesFunc);\r
       if (RETURN_ERROR (Status)) {\r
-        return RETURN_UNSUPPORTED;\r
+        Status = RETURN_UNSUPPORTED;\r
+        goto Done;\r
       }\r
       if (IsSplitted != NULL) {\r
         *IsSplitted = TRUE;\r
@@ -709,7 +776,14 @@ ConvertMemoryPageAttributes (
     }\r
   }\r
 \r
-  return RETURN_SUCCESS;\r
+Done:\r
+  //\r
+  // Restore page table write protection, if any.\r
+  //\r
+  if (IsWpEnabled) {\r
+    EnableReadOnlyPageWriteProtect ();\r
+  }\r
+  return Status;\r
 }\r
 \r
 /**\r
@@ -922,6 +996,127 @@ RefreshGcdMemoryAttributesFromPaging (
   FreePool (MemorySpaceMap);\r
 }\r
 \r
+/**\r
+  Initialize a buffer pool for page table use only.\r
+\r
+  To reduce the potential split operation on page table, the pages reserved for\r
+  page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and\r
+  at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always\r
+  initialized with number of pages greater than or equal to the given PoolPages.\r
+\r
+  Once the pages in the pool are used up, this method should be called again to\r
+  reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. Usually this won't happen\r
+  often in practice.\r
+\r
+  @param[in] PoolPages      The least page number of the pool to be created.\r
+\r
+  @retval TRUE    The pool is initialized successfully.\r
+  @retval FALSE   The memory is out of resource.\r
+**/\r
+BOOLEAN\r
+InitializePageTablePool (\r
+  IN  UINTN                           PoolPages\r
+  )\r
+{\r
+  VOID                      *Buffer;\r
+  BOOLEAN                   IsModified;\r
+\r
+  //\r
+  // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for\r
+  // header.\r
+  //\r
+  PoolPages += 1;   // Add one page for header.\r
+  PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) *\r
+              PAGE_TABLE_POOL_UNIT_PAGES;\r
+  Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT);\r
+  if (Buffer == NULL) {\r
+    DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n"));\r
+    return FALSE;\r
+  }\r
+\r
+  //\r
+  // Link all pools into a list for easier track later.\r
+  //\r
+  if (mPageTablePool == NULL) {\r
+    mPageTablePool = Buffer;\r
+    mPageTablePool->NextPool = mPageTablePool;\r
+  } else {\r
+    ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool;\r
+    mPageTablePool->NextPool = Buffer;\r
+    mPageTablePool = Buffer;\r
+  }\r
+\r
+  //\r
+  // Reserve one page for pool header.\r
+  //\r
+  mPageTablePool->FreePages  = PoolPages - 1;\r
+  mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1);\r
+\r
+  //\r
+  // Mark the whole pool pages as read-only.\r
+  //\r
+  ConvertMemoryPageAttributes (\r
+    NULL,\r
+    (PHYSICAL_ADDRESS)(UINTN)Buffer,\r
+    EFI_PAGES_TO_SIZE (PoolPages),\r
+    EFI_MEMORY_RO,\r
+    PageActionSet,\r
+    AllocatePageTableMemory,\r
+    NULL,\r
+    &IsModified\r
+    );\r
+  ASSERT (IsModified == TRUE);\r
+\r
+  return TRUE;\r
+}\r
+\r
+/**\r
+  This API provides a way to allocate memory for page table.\r
+\r
+  This API can be called more than once to allocate memory for page tables.\r
+\r
+  Allocates the number of 4KB pages and returns a pointer to the allocated\r
+  buffer. The buffer returned is aligned on a 4KB boundary.\r
+\r
+  If Pages is 0, then NULL is returned.\r
+  If there is not enough memory remaining to satisfy the request, then NULL is\r
+  returned.\r
+\r
+  @param  Pages                 The number of 4 KB pages to allocate.\r
+\r
+  @return A pointer to the allocated buffer or NULL if allocation fails.\r
+\r
+**/\r
+VOID *\r
+EFIAPI\r
+AllocatePageTableMemory (\r
+  IN UINTN           Pages\r
+  )\r
+{\r
+  VOID                            *Buffer;\r
+\r
+  if (Pages == 0) {\r
+    return NULL;\r
+  }\r
+\r
+  //\r
+  // Renew the pool if necessary.\r
+  //\r
+  if (mPageTablePool == NULL ||\r
+      Pages > mPageTablePool->FreePages) {\r
+    if (!InitializePageTablePool (Pages)) {\r
+      return NULL;\r
+    }\r
+  }\r
+\r
+  Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset;\r
+\r
+  mPageTablePool->Offset     += EFI_PAGES_TO_SIZE (Pages);\r
+  mPageTablePool->FreePages  -= Pages;\r
+\r
+  return Buffer;\r
+}\r
+\r
 /**\r
   Initialize the Page Table lib.\r
 **/\r
@@ -933,6 +1128,18 @@ InitializePageTableLib (
   PAGE_TABLE_LIB_PAGING_CONTEXT     CurrentPagingContext;\r
 \r
   GetCurrentPagingContext (&CurrentPagingContext);\r
+\r
+  //\r
+  // Reserve memory of page tables for future uses, if paging is enabled.\r
+  //\r
+  if (CurrentPagingContext.ContextData.X64.PageTableBase != 0 &&\r
+      (CurrentPagingContext.ContextData.Ia32.Attributes &\r
+       PAGE_TABLE_LIB_PAGING_CONTEXT_IA32_X64_ATTRIBUTES_PAE) != 0) {\r
+    DisableReadOnlyPageWriteProtect ();\r
+    InitializePageTablePool (1);\r
+    EnableReadOnlyPageWriteProtect ();\r
+  }\r
+\r
   DEBUG ((DEBUG_INFO, "CurrentPagingContext:\n", CurrentPagingContext.MachineType));\r
   DEBUG ((DEBUG_INFO, "  MachineType   - 0x%x\n", CurrentPagingContext.MachineType));\r
   DEBUG ((DEBUG_INFO, "  PageTableBase - 0x%x\n", CurrentPagingContext.ContextData.X64.PageTableBase));\r