diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c b/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c index 97058a2810..bbc536a567 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/Ia32/PageTbl.c @@ -10,24 +10,6 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include "PiSmmCpuDxeSmm.h" -/** - Disable CET. -**/ -VOID -EFIAPI -DisableCet ( - VOID - ); - -/** - Enable CET. -**/ -VOID -EFIAPI -EnableCet ( - VOID - ); - /** Create PageTable for SMM use. @@ -320,6 +302,8 @@ SetPageTableAttributes ( EnableCet (); } + mIsReadOnlyPageTable = TRUE; + return; } diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.c b/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.c index 37e3cfc449..655175a2c6 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.c @@ -1322,36 +1322,6 @@ ConfigSmmCodeAccessCheck ( } } -/** - This API provides a way to allocate memory for page table. - - This API can be called more once to allocate memory for page tables. - - Allocates the number of 4KB pages of type EfiRuntimeServicesData and returns a pointer to the - allocated buffer. The buffer returned is aligned on a 4KB boundary. If Pages is 0, then NULL - is returned. If there is not enough memory remaining to satisfy the request, then NULL is - returned. - - @param Pages The number of 4 KB pages to allocate. - - @return A pointer to the allocated buffer or NULL if allocation fails. - -**/ -VOID * -AllocatePageTableMemory ( - IN UINTN Pages - ) -{ - VOID *Buffer; - - Buffer = SmmCpuFeaturesAllocatePageTableMemory (Pages); - if (Buffer != NULL) { - return Buffer; - } - - return AllocatePages (Pages); -} - /** Allocate pages for code. diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h b/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h index 0bfba7e359..3e69e043ca 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/PiSmmCpuDxeSmm.h @@ -260,12 +260,43 @@ extern UINTN mNumberOfCpus; extern EFI_SMM_CPU_PROTOCOL mSmmCpu; extern EFI_MM_MP_PROTOCOL mSmmMp; extern BOOLEAN m5LevelPagingNeeded; +extern BOOLEAN mIsReadOnlyPageTable; /// /// The mode of the CPU at the time an SMI occurs /// extern UINT8 mSmmSaveStateRegisterLma; +#define PAGE_TABLE_POOL_ALIGNMENT BASE_128KB +#define PAGE_TABLE_POOL_UNIT_SIZE BASE_128KB +#define PAGE_TABLE_POOL_UNIT_PAGES EFI_SIZE_TO_PAGES (PAGE_TABLE_POOL_UNIT_SIZE) +#define PAGE_TABLE_POOL_ALIGN_MASK \ + (~(EFI_PHYSICAL_ADDRESS)(PAGE_TABLE_POOL_ALIGNMENT - 1)) + +typedef struct { + VOID *NextPool; + UINTN Offset; + UINTN FreePages; +} PAGE_TABLE_POOL; + +/** + Disable CET. +**/ +VOID +EFIAPI +DisableCet ( + VOID + ); + +/** + Enable CET. +**/ +VOID +EFIAPI +EnableCet ( + VOID + ); + // // SMM CPU Protocol function prototypes. // diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c b/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c index 773ab927e6..11df7af016 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/SmmCpuMemoryManagement.c @@ -35,6 +35,143 @@ PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = { BOOLEAN mIsShadowStack = FALSE; BOOLEAN m5LevelPagingNeeded = FALSE; +// +// Global variable to keep track current available memory used as page table. +// +PAGE_TABLE_POOL *mPageTablePool = NULL; + +// +// If memory used by SMM page table has been mareked as ReadOnly. +// +BOOLEAN mIsReadOnlyPageTable = FALSE; + +/** + Initialize a buffer pool for page table use only. + + To reduce the potential split operation on page table, the pages reserved for + page table should be allocated in the times of PAGE_TABLE_POOL_UNIT_PAGES and + at the boundary of PAGE_TABLE_POOL_ALIGNMENT. So the page pool is always + initialized with number of pages greater than or equal to the given PoolPages. + + Once the pages in the pool are used up, this method should be called again to + reserve at least another PAGE_TABLE_POOL_UNIT_PAGES. But usually this won't + happen in practice. + + @param PoolPages The least page number of the pool to be created. + + @retval TRUE The pool is initialized successfully. + @retval FALSE The memory is out of resource. +**/ +BOOLEAN +InitializePageTablePool ( + IN UINTN PoolPages + ) +{ + VOID *Buffer; + BOOLEAN CetEnabled; + + // + // Always reserve at least PAGE_TABLE_POOL_UNIT_PAGES, including one page for + // header. + // + PoolPages += 1; // Add one page for header. + PoolPages = ((PoolPages - 1) / PAGE_TABLE_POOL_UNIT_PAGES + 1) * + PAGE_TABLE_POOL_UNIT_PAGES; + Buffer = AllocateAlignedPages (PoolPages, PAGE_TABLE_POOL_ALIGNMENT); + if (Buffer == NULL) { + DEBUG ((DEBUG_ERROR, "ERROR: Out of aligned pages\r\n")); + return FALSE; + } + + // + // Link all pools into a list for easier track later. + // + if (mPageTablePool == NULL) { + mPageTablePool = Buffer; + mPageTablePool->NextPool = mPageTablePool; + } else { + ((PAGE_TABLE_POOL *)Buffer)->NextPool = mPageTablePool->NextPool; + mPageTablePool->NextPool = Buffer; + mPageTablePool = Buffer; + } + + // + // Reserve one page for pool header. + // + mPageTablePool->FreePages = PoolPages - 1; + mPageTablePool->Offset = EFI_PAGES_TO_SIZE (1); + + // + // If page table memory has been marked as RO, mark the new pool pages as read-only. + // + if (mIsReadOnlyPageTable) { + CetEnabled = ((AsmReadCr4 () & CR4_CET_ENABLE) != 0) ? TRUE : FALSE; + if (CetEnabled) { + // + // CET must be disabled if WP is disabled. + // + DisableCet (); + } + + AsmWriteCr0 (AsmReadCr0 () & ~CR0_WP); + SmmSetMemoryAttributes ((EFI_PHYSICAL_ADDRESS)(UINTN)Buffer, EFI_PAGES_TO_SIZE (PoolPages), EFI_MEMORY_RO); + AsmWriteCr0 (AsmReadCr0 () | CR0_WP); + if (CetEnabled) { + // + // re-enable CET. + // + EnableCet (); + } + } + + return TRUE; +} + +/** + This API provides a way to allocate memory for page table. + + This API can be called more once to allocate memory for page tables. + + Allocates the number of 4KB pages of type EfiRuntimeServicesData and returns a pointer to the + allocated buffer. The buffer returned is aligned on a 4KB boundary. If Pages is 0, then NULL + is returned. If there is not enough memory remaining to satisfy the request, then NULL is + returned. + + @param Pages The number of 4 KB pages to allocate. + + @return A pointer to the allocated buffer or NULL if allocation fails. + +**/ +VOID * +AllocatePageTableMemory ( + IN UINTN Pages + ) +{ + VOID *Buffer; + + if (Pages == 0) { + return NULL; + } + + // + // Renew the pool if necessary. + // + if ((mPageTablePool == NULL) || + (Pages > mPageTablePool->FreePages)) + { + if (!InitializePageTablePool (Pages)) { + return NULL; + } + } + + Buffer = (UINT8 *)mPageTablePool + mPageTablePool->Offset; + + mPageTablePool->Offset += EFI_PAGES_TO_SIZE (Pages); + mPageTablePool->FreePages -= Pages; + + return Buffer; +} + /** Return length according to page attributes. diff --git a/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c b/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c index bf90050503..8d42d89801 100644 --- a/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c +++ b/UefiCpuPkg/PiSmmCpuDxeSmm/X64/PageTbl.c @@ -20,24 +20,6 @@ BOOLEAN m1GPageTableSupport = FALSE; BOOLEAN mCpuSmmRestrictedMemoryAccess; X86_ASSEMBLY_PATCH_LABEL gPatch5LevelPagingNeeded; -/** - Disable CET. -**/ -VOID -EFIAPI -DisableCet ( - VOID - ); - -/** - Enable CET. -**/ -VOID -EFIAPI -EnableCet ( - VOID - ); - /** Check if 1-GByte pages is supported by processor or not. @@ -1305,6 +1287,8 @@ SetPageTableAttributes ( EnableCet (); } + mIsReadOnlyPageTable = TRUE; + return; }