From ae476fe8ee5cec42dc0906cbd3b5be3466057fe6 Mon Sep 17 00:00:00 2001 From: Taylor Beebe <31827475+TaylorBeebe@users.noreply.github.com> Date: Fri, 11 Nov 2022 16:46:51 -0800 Subject: [PATCH] Update DXE Paging Audit App to Include RWX Test (#101) ## Description Our memory protection policy is now robust enough to ensure that platforms have no read/write/execute pages before ExitBootServices. This update adds a test to the DxePagingAuditApp to check the page table for RWX pages and only exempt them if the region is part of a nonprotected image or special region. Users can still utilize the app to dump paging data to the EFI partition by calling the application with the '-d' flag. By default, the app will run the RWX test. ## Breaking change? No ## How This Was Tested Running the test on Q35 ## Integration Instructions The test will identify RWX regions. Platforms should identify these regions to determine if they must be RWX. If they really must be RWX, the platform can utilize the Memory Protection Special Region Protocol to create a special region. --- .../UEFI/Dxe/App/DxePagingAuditTestApp.c | 264 +++++++++++++++++- .../UEFI/DxePagingAuditTestApp.inf | 5 + UefiTestingPkg/UefiTestingPkg.dsc | 1 + 3 files changed, 267 insertions(+), 3 deletions(-) diff --git a/UefiTestingPkg/AuditTests/PagingAudit/UEFI/Dxe/App/DxePagingAuditTestApp.c b/UefiTestingPkg/AuditTests/PagingAudit/UEFI/Dxe/App/DxePagingAuditTestApp.c index 5a49a9c019..0614092d06 100644 --- a/UefiTestingPkg/AuditTests/PagingAudit/UEFI/Dxe/App/DxePagingAuditTestApp.c +++ b/UefiTestingPkg/AuditTests/PagingAudit/UEFI/Dxe/App/DxePagingAuditTestApp.c @@ -1,5 +1,6 @@ /** @file -- DxePagingAuditTestApp.c -This Shell App writes page table and memory map information to SFS. +This Shell App tests the page table or writes page table and +memory map information to SFS Copyright (c) Microsoft Corporation. SPDX-License-Identifier: BSD-2-Clause-Patent @@ -8,10 +9,145 @@ SPDX-License-Identifier: BSD-2-Clause-Patent #include "../../PagingAuditCommon.h" +#include +#include +#include +#include +#include +#include + +#define UNIT_TEST_APP_NAME "Paging Audit Test" +#define UNIT_TEST_APP_VERSION "1" +#define MAX_CHARS_TO_READ 3 + +// TRUE if A interval subsumes B interval +#define CHECK_SUBSUMPTION(AStart, AEnd, BStart, BEnd) \ + ((AStart <= BStart) && (AEnd >= BEnd)) + +typedef struct _PAGING_AUDIT_TEST_CONTEXT { + IA32_MAP_ENTRY *Entries; + UINTN Count; +} PAGING_AUDIT_TEST_CONTEXT; + CHAR8 *mMemoryInfoDatabaseBuffer = NULL; UINTN mMemoryInfoDatabaseSize = 0; UINTN mMemoryInfoDatabaseAllocSize = 0; +/** + Check the page table for Read/Write/Execute regions. + + @param[in] Context Unit test context + + @retval UNIT_TEST_PASSED The unit test passed + @retval other The unit test failed + +**/ +UNIT_TEST_STATUS +EFIAPI +NoReadWriteExcecute ( + IN UNIT_TEST_CONTEXT Context + ) +{ + IA32_MAP_ENTRY *Map = ((PAGING_AUDIT_TEST_CONTEXT *)Context)->Entries; + UINTN MapCount = ((PAGING_AUDIT_TEST_CONTEXT *)Context)->Count; + UINTN Index = 0; + BOOLEAN FoundRWXAddress = FALSE; + BOOLEAN IgnoreRWXAddress = FALSE; + MEMORY_PROTECTION_DEBUG_PROTOCOL *MemoryProtectionProtocol = NULL; + MEMORY_PROTECTION_SPECIAL_REGION_PROTOCOL *SpecialRegionProtocol = NULL; + MEMORY_PROTECTION_SPECIAL_REGION *SpecialRegions = NULL; + UINTN SpecialRegionCount = 0; + UINTN SpecialRegionIndex = 0; + IMAGE_RANGE_DESCRIPTOR *NonProtectedImageList = NULL; + LIST_ENTRY *NonProtectedImageLink = NULL; + IMAGE_RANGE_DESCRIPTOR *NonProtectedImage = NULL; + + UT_ASSERT_NOT_EFI_ERROR ( + gBS->LocateProtocol ( + &gMemoryProtectionDebugProtocolGuid, + NULL, + (VOID **)&MemoryProtectionProtocol + ) + ); + + UT_ASSERT_NOT_EFI_ERROR ( + MemoryProtectionProtocol->GetImageList ( + &NonProtectedImageList, + NonProtected + ) + ); + + UT_ASSERT_NOT_EFI_ERROR ( + gBS->LocateProtocol ( + &gMemoryProtectionSpecialRegionProtocolGuid, + NULL, + (VOID **)&SpecialRegionProtocol + ) + ); + + UT_ASSERT_NOT_EFI_ERROR ( + SpecialRegionProtocol->GetSpecialRegions ( + &SpecialRegions, + &SpecialRegionCount + ) + ); + + for ( ; Index < MapCount; Index++) { + if ((Map[Index].Attribute.Bits.ReadWrite != 0) && (Map[Index].Attribute.Bits.Nx == 0)) { + IgnoreRWXAddress = FALSE; + if (NonProtectedImageList != NULL) { + for (NonProtectedImageLink = NonProtectedImageList->Link.ForwardLink; + NonProtectedImageLink != &NonProtectedImageList->Link; + NonProtectedImageLink = NonProtectedImageLink->ForwardLink) + { + NonProtectedImage = CR ( + NonProtectedImageLink, + IMAGE_RANGE_DESCRIPTOR, + Link, + IMAGE_RANGE_DESCRIPTOR_SIGNATURE + ); + if CHECK_SUBSUMPTION ( + NonProtectedImage->Base, + NonProtectedImage->Base + NonProtectedImage->Length, + Map[Index].LinearAddress, + Map[Index].LinearAddress + Map[Index].Length + ) { + IgnoreRWXAddress = TRUE; + break; + } + } + } + + if ((SpecialRegionCount > 0) && !IgnoreRWXAddress) { + for (SpecialRegionIndex = 0; SpecialRegionIndex < SpecialRegionCount; SpecialRegionIndex++) { + if (CHECK_SUBSUMPTION ( + SpecialRegions[SpecialRegionIndex].Start, + SpecialRegions[SpecialRegionIndex].Start + SpecialRegions[SpecialRegionIndex].Length, + Map[Index].LinearAddress, + Map[Index].LinearAddress + Map[Index].Length + ) && + (SpecialRegions[SpecialRegionIndex].EfiAttributes == 0)) + { + IgnoreRWXAddress = TRUE; + break; + } + } + } + + if (!IgnoreRWXAddress) { + UT_LOG_ERROR ("Memory Range 0x%llx-0x%llx is Read/Write/Execute\n", Map[Index].LinearAddress, Map[Index].LinearAddress + Map[Index].Length); + FoundRWXAddress = TRUE; + } else { + UT_LOG_WARNING ("Memory Range 0x%llx-0x%llx is Read/Write/Execute. This range is excepted from the test.\n", Map[Index].LinearAddress, Map[Index].LinearAddress + Map[Index].Length); + } + } + } + + UT_ASSERT_FALSE (FoundRWXAddress); + + return UNIT_TEST_PASSED; +} + /** DxePagingAuditTestAppEntryPoint @@ -29,7 +165,129 @@ DxePagingAuditTestAppEntryPoint ( IN EFI_SYSTEM_TABLE *SystemTable ) { - DumpPagingInfo (NULL, NULL); + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Fw = NULL; + UNIT_TEST_SUITE_HANDLE Misc = NULL; + PAGING_AUDIT_TEST_CONTEXT *Context; + IA32_CR4 Cr4; + PAGING_MODE PagingMode; + IA32_MAP_ENTRY *Map = NULL; + UINTN MapCount = 0; + UINTN PagesAllocated = 0; + BOOLEAN RunTests = TRUE; + EFI_SHELL_PARAMETERS_PROTOCOL *ShellParams; + + DEBUG ((DEBUG_ERROR, "%a()\n", __FUNCTION__)); + + DEBUG ((DEBUG_ERROR, "%a v%a\n", UNIT_TEST_APP_NAME, UNIT_TEST_APP_VERSION)); + + Status = gBS->HandleProtocol ( + gImageHandle, + &gEfiShellParametersProtocolGuid, + (VOID **)&ShellParams + ); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_INFO, "%a Could not retrieve command line args!\n", __FUNCTION__)); + return EFI_PROTOCOL_ERROR; + } + + if (ShellParams->Argc > 1) { + RunTests = FALSE; + if (StrnCmp (ShellParams->Argv[1], L"-r", 4) == 0) { + RunTests = TRUE; + } else if (StrnCmp (ShellParams->Argv[1], L"-d", 4) == 0) { + DumpPagingInfo (NULL, NULL); + } else { + if (StrnCmp (ShellParams->Argv[1], L"-h", 4) != 0) { + DEBUG ((DEBUG_INFO, "Invalid argument!\n")); + } + + DEBUG ((DEBUG_INFO, "-h : Print available flags\n")); + DEBUG ((DEBUG_INFO, "-d : Dump the page table files to the EFI partition\n")); + DEBUG ((DEBUG_INFO, "-r : Run the application tests\n")); + DEBUG ((DEBUG_INFO, "NOTE: Combined flags (i.e. -rd) is not supported\n")); + } + } + + if (RunTests) { + Context = (PAGING_AUDIT_TEST_CONTEXT *)AllocateZeroPool (sizeof (PAGING_AUDIT_TEST_CONTEXT)); + + if (Context == NULL) { + DEBUG ((DEBUG_ERROR, "Failed to allocate test context\n")); + goto EXIT; + } + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Fw, UNIT_TEST_APP_NAME, gEfiCallerBaseName, UNIT_TEST_APP_VERSION); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in InitUnitTestFramework. Status = %r\n", Status)); + goto EXIT; + } + + // Poll CR4 to deterimine the page table depth + Cr4.UintN = AsmReadCr4 (); + + if (Cr4.Bits.LA57 != 0) { + PagingMode = Paging5Level; + } else { + PagingMode = Paging4Level; + } + + // CR3 is the page table pointer + Status = PageTableParse (AsmReadCr3 (), PagingMode, NULL, &MapCount); + + while (Status == RETURN_BUFFER_TOO_SMALL) { + if ((Map != NULL) && (PagesAllocated > 0)) { + FreePages (Map, PagesAllocated); + } + + PagesAllocated = EFI_SIZE_TO_PAGES (MapCount * sizeof (IA32_MAP_ENTRY)); + Map = AllocatePages (PagesAllocated); + + if (Map == NULL) { + DEBUG ((DEBUG_ERROR, "Failed to allocate page table map\n")); + goto EXIT; + } + + Status = PageTableParse (AsmReadCr3 (), PagingMode, Map, &MapCount); + } + + Context->Entries = Map; + Context->Count = MapCount; + + // + // Create test suite + // + CreateUnitTestSuite (&Misc, Fw, "Miscellaneous tests", "Security.Misc", NULL, NULL); + + if (Misc == NULL) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for TestSuite\n")); + goto EXIT; + } + + AddTestCase (Misc, "No pages can be read,write,execute", "Security.Misc.NoReadWriteExecute", NoReadWriteExcecute, NULL, NULL, Context); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Fw); +EXIT: + + if (Fw) { + FreeUnitTestFramework (Fw); + } + + if ((Map != NULL) && (PagesAllocated > 0)) { + FreePages (Map, PagesAllocated); + } + + if (Context != NULL) { + FreePool (Context); + } + } return EFI_SUCCESS; -} // DxePagingAuditTestAppEntryPoint() +} // DxePagingAuditTestAppEntryPoint() diff --git a/UefiTestingPkg/AuditTests/PagingAudit/UEFI/DxePagingAuditTestApp.inf b/UefiTestingPkg/AuditTests/PagingAudit/UEFI/DxePagingAuditTestApp.inf index 57f8b1a67f..d13080adc4 100644 --- a/UefiTestingPkg/AuditTests/PagingAudit/UEFI/DxePagingAuditTestApp.inf +++ b/UefiTestingPkg/AuditTests/PagingAudit/UEFI/DxePagingAuditTestApp.inf @@ -44,6 +44,9 @@ UefiCpuLib HobLib DxeServicesTableLib + UnitTestLib + CpuPageTableLib + DxeMemoryProtectionHobLib [Guids] gEfiDebugImageInfoTableGuid ## SOMETIMES_CONSUMES ## GUID @@ -53,6 +56,8 @@ gEfiBlockIoProtocolGuid gMemoryProtectionDebugProtocolGuid gEfiSimpleFileSystemProtocolGuid + gMemoryProtectionSpecialRegionProtocolGuid + gEfiShellParametersProtocolGuid [FixedPcd] gUefiTestingPkgTokenSpaceGuid.PcdPlatformSmrrUnsupported ## SOMETIMES_CONSUMES diff --git a/UefiTestingPkg/UefiTestingPkg.dsc b/UefiTestingPkg/UefiTestingPkg.dsc index fefdd110e1..445741fbe6 100644 --- a/UefiTestingPkg/UefiTestingPkg.dsc +++ b/UefiTestingPkg/UefiTestingPkg.dsc @@ -58,6 +58,7 @@ PlatformSmmProtectionsTestLib|UefiTestingPkg/Library/PlatformSmmProtectionsTestLibNull/PlatformSmmProtectionsTestLibNull.inf ExceptionPersistenceLib|MdeModulePkg/Library/BaseExceptionPersistenceLibNull/BaseExceptionPersistenceLibNull.inf CpuPageTableLib|UefiCpuPkg/Library/CpuPageTableLib/CpuPageTableLib.inf + DxeMemoryProtectionHobLib|MdeModulePkg/Library/MemoryProtectionHobLibNull/DxeMemoryProtectionHobLibNull.inf [LibraryClasses.common.DXE_SMM_DRIVER] SmmServicesTableLib|MdePkg/Library/SmmServicesTableLib/SmmServicesTableLib.inf