diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/AARCH64/PageSplitTest.c b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/AARCH64/PageSplitTest.c new file mode 100644 index 000000000..7ff865904 --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/AARCH64/PageSplitTest.c @@ -0,0 +1,122 @@ +/** @file -- PageSplitTest.c + +Platform specific memory audit functions. +Copyright (c) Microsoft Corporation. All rights reserved. + +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +// MdePkg is the ideal place for this test, but because the AARCH64 PageSplitTest relies on +// ArmLib to get the translation table base address and TCR, this test is in the UefiTestingPkg to avoid +// circular repo dependencies in Project Mu. +#include +#include "MemoryAttributeProtocolFuncTestApp.h" + +#define TT_ADDRESS_MASK (0xFFFFFFFFFULL << 12) + +#define IS_TABLE(page, level) ((level == 3) ? FALSE : (((page) & TT_TYPE_MASK) == TT_TYPE_TABLE_ENTRY)) +#define IS_BLOCK(page, level) ((level == 3) ? (((page) & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY_LEVEL3) : ((page & TT_TYPE_MASK) == TT_TYPE_BLOCK_ENTRY)) +#define ROOT_TABLE_LEN(T0SZ) (TT_ENTRY_COUNT >> ((T0SZ) - 16) % 9) + +/** + Get an un-split page table entry and allocate entire region so the page + doesn't need to be split on allocation + + @param[out] Address Address of allocated 2MB page region +**/ +EFI_STATUS +EFIAPI +GetUnsplitPageTableEntry ( + OUT EFI_PHYSICAL_ADDRESS *Address + ) +{ + UINT64 *Pml0 = NULL; + UINT64 *Pte1G = NULL; + UINT64 *Pte2M = NULL; + UINT64 Index2 = 0; + UINT64 Index1 = 0; + UINT64 Index0 = 0; + UINT64 RootEntryCount = 0; + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS BaseAddress; + + Pml0 = (UINT64 *)ArmGetTTBR0BaseAddress (); + + if (Pml0 == NULL) { + return EFI_NOT_FOUND; + } + + RootEntryCount = ROOT_TABLE_LEN (ArmGetTCR () & TCR_T0SZ_MASK); + + for (Index0 = 0x0; Index0 < RootEntryCount; Index0++) { + if (!IS_TABLE (Pml0[Index0], 0)) { + continue; + } + + Pte1G = (UINT64 *)(Pml0[Index0] & TT_ADDRESS_MASK); + + for (Index1 = 0x1; Index1 < TT_ENTRY_COUNT; Index1++ ) { + if ((Pte1G[Index1] & 0x1) == 0) { + continue; + } + + if (!IS_BLOCK (Pte1G[Index1], 1)) { + Pte2M = (UINT64 *)(Pte1G[Index1] & TT_ADDRESS_MASK); + + for (Index2 = 0x0; Index2 < TT_ENTRY_COUNT; Index2++ ) { + if ((Pte2M[Index2] & 0x1) == 0) { + continue; + } + + if (!IS_BLOCK (Pte2M[Index2], 2)) { + continue; + } else { + BaseAddress = (EFI_PHYSICAL_ADDRESS)(Pte2M[Index2] & TT_ADDRESS_MASK); + Status = gBS->AllocatePages (AllocateAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES (PTE2MB), &BaseAddress); + if (!EFI_ERROR (Status)) { + *Address = BaseAddress; + return EFI_SUCCESS; + } + } + } + } + } + } + + return EFI_OUT_OF_RESOURCES; +} + +/** + Check if the 2MB page entry correlating with the input address + is set to no-execute + + @param[in] Address Address of the page table entry +**/ +UINT64 +EFIAPI +GetSpitPageTableEntryNoExecute ( + IN PHYSICAL_ADDRESS Address + ) +{ + UINT64 *Pml0 = NULL; + UINT64 *Pte1G = NULL; + UINT64 *Pte2M = NULL; + UINT64 Index2 = 0; + UINT64 Index1 = 0; + UINT64 Index0 = 0; + + Pml0 = (UINT64 *)ArmGetTTBR0BaseAddress (); + + if (Pml0 == NULL) { + return TT_UXN_MASK; + } + + Index0 = (Address >> 39) & (TT_ENTRY_COUNT - 1); + Index1 = (Address >> 30) & (TT_ENTRY_COUNT - 1); + Index2 = (Address >> 21) & (TT_ENTRY_COUNT - 1); + + Pte1G = (UINT64 *)(Pml0[Index0] & TT_ADDRESS_MASK); + Pte2M = (UINT64 *)(Pte1G[Index1] & TT_ADDRESS_MASK); + return Pte2M[Index2] & TT_UXN_MASK; +} diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.c b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.c new file mode 100644 index 000000000..642d0370a --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.c @@ -0,0 +1,512 @@ +/** @file +TCBZ3519 +UEFI Shell based application for unit testing the Memory Attribute Protocol. + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent +***/ + +#define UNIT_TEST_APP_NAME "Memory Attribute Protocol Unit Test Application" +#define UNIT_TEST_APP_VERSION "0.2" + +#include "MemoryAttributeProtocolFuncTestApp.h" + +/** + Ensure this image is protected. + +**/ +EFI_STATUS +STATIC +IsThisImageProtected ( + VOID + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 Length; + UINT64 Attributes; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + + if (EFI_ERROR (Status)) { + return RETURN_UNSUPPORTED; + } + + BaseAddress = (UINTN)IsThisImageProtected & ~(EFI_PAGE_SIZE - 1); + Attributes = 0; + Length = EFI_PAGE_SIZE; + + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + + if (EFI_ERROR (Status) || (Attributes == 0)) { + return RETURN_UNSUPPORTED; + } + + return EFI_SUCCESS; +} + +/** + Allocate a page, set the page to read-only, no-execute, and read-protected, then free the page to + test if the free process clears those attributes before attempting to free the memory. + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +FreePagesWithProtectionAttributesTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINTN Length; + UINT64 Attributes; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + Attributes = 0; + BaseAddress = 0; + Length = EFI_PAGE_SIZE; + + // Allocate any pages + Status = gBS->AllocatePages (AllocateAnyPages, EfiLoaderCode, Length, &BaseAddress); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL ((VOID *)((UINTN)BaseAddress)); + + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_RP | EFI_MEMORY_XP | EFI_MEMORY_RO); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Get the attributes of allocated pages + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + DEBUG ((DEBUG_INFO, "%a - Attributes for memory at base: 0x%llx 0x%llx\n", __FUNCTION__, BaseAddress, Attributes)); + + // Free the pages + Status = gBS->FreePages (BaseAddress, Length); + UT_ASSERT_NOT_EFI_ERROR (Status); + + return UNIT_TEST_PASSED; +} + +/** + Allocate a pool, set the page table entry managing that pool to read-only, no-execute, + and read-protected, then free the pool to test if the free process clears those + attributes before attempting to free the pool. + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +FreePoolWithProtectionAttributesTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINTN Length; + UINT64 Attributes; + VOID *Buffer; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + Attributes = 0; + BaseAddress = 0; + Length = EFI_PAGE_SIZE; + + // Allocate a pool + Status = gBS->AllocatePool (EfiLoaderCode, Length, &Buffer); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (Buffer); + + BaseAddress = ((EFI_PHYSICAL_ADDRESS)((UINTN)ALIGN_POINTER (Buffer, EFI_PAGE_SIZE))) - EFI_PAGE_SIZE; + + // Get the attributes of the pages representing the allocated pool + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_RP | EFI_MEMORY_XP | EFI_MEMORY_RO); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Free the pool + Status = gBS->FreePool (Buffer); + UT_ASSERT_NOT_EFI_ERROR (Status); + + return UNIT_TEST_PASSED; +} + +/** + Allocate a page, free that page, then reallocate a page specifying the previously + allocated address to ensure that the attributes are as expected + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +AllocateFreeAllocateAtAddressTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINTN Length; + UINT64 Attributes; + UINT64 CachedAttributes; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + Attributes = 0; + BaseAddress = 0; + Length = EFI_PAGE_SIZE; + + // Allocate any pages + Status = gBS->AllocatePages (AllocateAnyPages, EfiLoaderCode, Length, &BaseAddress); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL ((VOID *)((UINTN)BaseAddress)); + + // Get the attributes of allocated pages + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + UT_LOG_INFO ("%a - Attributes for memory at base: 0x%llx 0x%llx\n", __FUNCTION__, BaseAddress, Attributes); + + CachedAttributes = Attributes; + + // Flip all the attributes of the page range + if ((CachedAttributes & EFI_MEMORY_XP) != 0) { + Status = MemoryAttribute->ClearMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_XP); + UT_ASSERT_NOT_EFI_ERROR (Status); + } else { + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_XP); + UT_ASSERT_NOT_EFI_ERROR (Status); + } + + if ((CachedAttributes & EFI_MEMORY_RO) != 0) { + Status = MemoryAttribute->ClearMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_RO); + UT_ASSERT_NOT_EFI_ERROR (Status); + } else { + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_RO); + UT_ASSERT_NOT_EFI_ERROR (Status); + } + + if ((CachedAttributes & EFI_MEMORY_RP) != 0) { + Status = MemoryAttribute->ClearMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_RP); + UT_ASSERT_NOT_EFI_ERROR (Status); + } else { + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, Length, EFI_MEMORY_RP); + UT_ASSERT_NOT_EFI_ERROR (Status); + } + + // Free the pages + Status = gBS->FreePages (BaseAddress, Length); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Allocate pages at the previously allocated address + Status = gBS->AllocatePages (AllocateAddress, EfiLoaderCode, Length, &BaseAddress); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL ((VOID *)((UINTN)BaseAddress)); + + // Get the attributes of allocated pages and check them against the cached attributes + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_EQUAL (Attributes, CachedAttributes); + + UT_LOG_INFO ("%a - Attributes for memory after reallocation at base: 0x%llx 0x%llx\n", __FUNCTION__, BaseAddress, Attributes); + + // Free the pages + Status = gBS->FreePages (BaseAddress, Length); + UT_ASSERT_NOT_EFI_ERROR (Status); + + return UNIT_TEST_PASSED; +} + +/** + Allocate an un-split page range, clear the no-execute attribute from a subsection of that region + to force a split, then ensure the previously un-split region has also had the no-execute attribute + cleared. + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +UpdateAttributesRequiresPageSplitTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 Attributes; + + Status = GetUnsplitPageTableEntry (&BaseAddress); + UT_ASSERT_NOT_EQUAL (BaseAddress, 0); + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + // Get the attributes of allocated 2MB page + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, PTE2MB, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + UT_LOG_INFO ("Attributes for 2MB page at address: 0x%llx 0x%llx\n", BaseAddress, Attributes); + + if ((Attributes & EFI_MEMORY_XP) == 0) { + // Set the 2MB region to NX + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, PTE2MB, EFI_MEMORY_XP); + UT_ASSERT_NOT_EFI_ERROR (Status); + } + + // Set subsection of 2MB region to executable + Status = MemoryAttribute->ClearMemoryAttributes (MemoryAttribute, BaseAddress + EFI_PAGE_SIZE, EFI_PAGE_SIZE, EFI_MEMORY_XP); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // Check that the 2MB page is no longer NX + UT_ASSERT_EQUAL (GetSpitPageTableEntryNoExecute (BaseAddress), 0); + + // Free the pages + Status = gBS->FreePages (BaseAddress, EFI_SIZE_TO_PAGES (PTE2MB)); + UT_ASSERT_NOT_EFI_ERROR (Status); + + return UNIT_TEST_PASSED; +} + +/** + Make sure test environment has protocol + + @param Context + **/ +UNIT_TEST_STATUS +EFIAPI +ProtocolExistsTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + return UNIT_TEST_PASSED; +} + +/** + Fetch the attributes, clear them, then set them back to their original value + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +GetClearSetAttributesRunningCodeTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 Length; + UINT64 Attributes; + UINT64 CachedAttributes; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + BaseAddress = (UINTN)GetClearSetAttributesRunningCodeTestCase & ~(EFI_PAGE_SIZE - 1); // Page align address of this page + Attributes = 0; + Length = EFI_PAGE_SIZE; + + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + UT_LOG_INFO ("Attributes for Memory at base: 0x%llx 0x%llx\n", BaseAddress, Attributes); + + CachedAttributes = Attributes; // save them for later to set + UT_ASSERT_NOT_EQUAL (0, CachedAttributes); // If attributes are zero this is unexpected and test will not work + + Status = MemoryAttribute->ClearMemoryAttributes (MemoryAttribute, BaseAddress, Length, CachedAttributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_LOG_INFO ("Attributes after clear for Memory at base: 0x%llX 0x%llx\n", BaseAddress, Attributes); + UT_ASSERT_NOT_EQUAL (Attributes, CachedAttributes); // should not be equal since we just cleared them + + Status = MemoryAttribute->SetMemoryAttributes (MemoryAttribute, BaseAddress, Length, CachedAttributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_LOG_INFO ("Attributes after set for Memory at base: 0x%llX 0x%llx\n", BaseAddress, Attributes); + UT_ASSERT_EQUAL (Attributes, CachedAttributes); // should be equal since we just cleared them + + return UNIT_TEST_PASSED; +} + +/** + Check that EfiLoaderCode is allocated as no-execute and read/write + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +GetAttributesNewBufferEfiLoaderCodeTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINTN Length; + UINT64 Attributes; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + Attributes = 0; + BaseAddress = 0; + Length = EFI_PAGE_SIZE; + + Status = gBS->AllocatePages (AllocateAnyPages, EfiLoaderCode, Length, &BaseAddress); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL ((VOID *)((UINTN)BaseAddress)); + + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + gBS->FreePages (BaseAddress, Length); // Free the page in case of any failures + UT_ASSERT_NOT_EFI_ERROR (Status); + + UT_LOG_INFO ("Attributes: 0x%llx\n", Attributes); + + // newly allocated efi loader code page should be RW- + UT_ASSERT_TRUE ((Attributes & EFI_MEMORY_XP) == EFI_MEMORY_XP); // should be No Execute + UT_ASSERT_FALSE ((Attributes & EFI_MEMORY_RO) == EFI_MEMORY_RO); // Should be not read only + + return UNIT_TEST_PASSED; +} + +/** + Check that code region of image is set as read-only and executable + + @param[in] Context Unit test context + +**/ +UNIT_TEST_STATUS +EFIAPI +GetAttributesRunningCodeTestCase ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_ATTRIBUTE_PROTOCOL *MemoryAttribute; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 Length; + UINT64 Attributes; + + Status = gBS->LocateProtocol (&gEfiMemoryAttributeProtocolGuid, NULL, (VOID **)&MemoryAttribute); + UT_ASSERT_NOT_EFI_ERROR (Status); + UT_ASSERT_NOT_NULL (MemoryAttribute); + + BaseAddress = (UINTN)GetAttributesRunningCodeTestCase & ~(EFI_PAGE_SIZE - 1); // Page align address of this page + Attributes = 0; + Length = EFI_PAGE_SIZE; + + Status = MemoryAttribute->GetMemoryAttributes (MemoryAttribute, BaseAddress, Length, &Attributes); + UT_ASSERT_NOT_EFI_ERROR (Status); + + UT_LOG_INFO ("Attributes: 0x%llx\n", Attributes); + // This page is the current executing code page. Should be RO and not XP + UT_ASSERT_FALSE ((Attributes & EFI_MEMORY_XP) == EFI_MEMORY_XP); // should be allowed to execute + UT_ASSERT_TRUE ((Attributes & EFI_MEMORY_RO) == EFI_MEMORY_RO); // Should be read only + + return UNIT_TEST_PASSED; +} + +/** + The driver's entry point. + + @param[in] ImageHandle The firmware allocated handle for the EFI image. + @param[in] SystemTable A pointer to the EFI System Table. + + @retval EFI_SUCCESS The entry point executed successfully. + @retval other Some error occurred when executing this entry point. +**/ +EFI_STATUS +EFIAPI +UefiMain ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + UNIT_TEST_FRAMEWORK_HANDLE Framework; + UNIT_TEST_SUITE_HANDLE TestSuite; + + Framework = NULL; + TestSuite = NULL; + + DEBUG ((DEBUG_INFO, "%a v%a\n", UNIT_TEST_APP_NAME, UNIT_TEST_APP_VERSION)); + + Status = IsThisImageProtected (); + + if (EFI_ERROR (Status)) { + goto EXIT; + } + + // + // Start setting up the test framework for running the tests. + // + Status = InitUnitTestFramework (&Framework, 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; + } + + // + // Test Suite + // + Status = CreateUnitTestSuite (&TestSuite, Framework, "Basic", "Dxe.MemoryManagement.MemoryAttributesProtocol", NULL, NULL); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Failed in CreateUnitTestSuite for the Test Suite\n")); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + AddTestCase (TestSuite, "Memory Attributes Protocol Exists", "PrototocolExists", ProtocolExistsTestCase, NULL, NULL, NULL); + AddTestCase (TestSuite, "Get Clear Set Attributes work as expected", "BasicGetClearSet", GetClearSetAttributesRunningCodeTestCase, ProtocolExistsTestCase, NULL, NULL); + AddTestCase (TestSuite, "New EfiLoaderCode buffer attributes expected", "NewEfiLoaderCodeAttributes", GetAttributesNewBufferEfiLoaderCodeTestCase, ProtocolExistsTestCase, NULL, NULL); + AddTestCase (TestSuite, "Loaded Code Attributes Allow Execution", "RunningCodeAttributes", GetAttributesRunningCodeTestCase, ProtocolExistsTestCase, NULL, NULL); + AddTestCase (TestSuite, "Allocate, free, then reallocate at the previous address", "AllocateFreeAllocateAtAddress", AllocateFreeAllocateAtAddressTestCase, ProtocolExistsTestCase, NULL, NULL); + AddTestCase (TestSuite, "Ensure higher level pages have loose page attributes after split", "UpdateAttributesRequiresPageSplit", UpdateAttributesRequiresPageSplitTestCase, ProtocolExistsTestCase, NULL, NULL); + AddTestCase (TestSuite, "Pages with protection attributes set can still be freed", "FreePagesWithProtectionAttributes", FreePagesWithProtectionAttributesTestCase, ProtocolExistsTestCase, NULL, NULL); + + // + // Execute the tests. + // + Status = RunAllTestSuites (Framework); + +EXIT: + if (Framework != NULL) { + FreeUnitTestFramework (Framework); + } + + return Status; +} diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.h b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.h new file mode 100644 index 000000000..084682768 --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.h @@ -0,0 +1,48 @@ +/** @file -- MemoryAttributeProtocolFuncTestApp.h +TCBZ3519 +Functionality to support MemoryAttributeProtocolFuncTestApp.c + +Copyright (c) Microsoft Corporation. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _MEM_ATTR_PROTOCOL_FUNC_TEST_APP_ +#define _MEM_ATTR_PROTOCOL_FUNC_TEST_APP_ + +#include +#include +#include +#include +#include +#include + +#define PTE2MB 0x200000 +#define PTE1GB 0x40000000 +#define PTE512GB 0x8000000000 + +/** + Get an un-split page table entry and allocate entire region so the page + doesn't need to be split on allocation + + @param[out] Address Address of allocated 2MB page region +**/ +EFI_STATUS +EFIAPI +GetUnsplitPageTableEntry ( + OUT EFI_PHYSICAL_ADDRESS *Address + ); + +/** + Check if the 2MB page entry correlating with the input address + is set to no-execute + + @param[in] Address Address of the page table entry +**/ +UINT64 +EFIAPI +GetSpitPageTableEntryNoExecute ( + IN PHYSICAL_ADDRESS Address + ); + +#endif // _MEM_ATTR_PROTOCOL_FUNC_TEST_APP_ diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.inf b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.inf new file mode 100644 index 000000000..401aa71bd --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.inf @@ -0,0 +1,47 @@ +## @file +# TCBZ3519 +# Uefi Shell based Application that unit tests the Memory Attribute Protocol. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = MemoryAttributeProtocolFuncTestApp + FILE_GUID = BB7F9547-68DB-4BF0-A247-C022C6146DF3 + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + ENTRY_POINT = UefiMain + +[Sources] + MemoryAttributeProtocolFuncTestApp.c + MemoryAttributeProtocolFuncTestApp.h + +[Sources.X64] + X64/PageSplitTest.c + +[Sources.IA32, Sources.ARM] + PageSplitTest.c + +[Sources.AARCH64] + AARCH64/PageSplitTest.c + +[Packages] + MdePkg/MdePkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + +[Packages.AARCH64] + ArmPkg/ArmPkg.dec + +[LibraryClasses] + UefiApplicationEntryPoint + UnitTestLib + UefiBootServicesTableLib + DebugLib + +[LibraryClasses.AARCH64] + ArmLib + +[Protocols] + gEfiMemoryAttributeProtocolGuid diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/PageSplitTest.c b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/PageSplitTest.c new file mode 100644 index 000000000..64480da29 --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/PageSplitTest.c @@ -0,0 +1,39 @@ +/** @file PageSplitTest.c +TCBZ3519 +Functionality to support MemoryAttributeProtocolFuncTestApp.c + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent +***/ + +#include "MemoryAttributeProtocolFuncTestApp.h" + +/** + Get an un-split page table entry and allocate entire region so the page + doesn't need to be split on allocation + + @param[out] Address Address of allocated 2MB page region +**/ +EFI_STATUS +EFIAPI +GetUnsplitPageTableEntry ( + OUT EFI_PHYSICAL_ADDRESS *Address + ) +{ + return EFI_UNSUPPORTED; +} + +/** + Check if the 2MB page entry correlating with the input address + is set to no-execute + + @param[in] Address Address of the page table entry +**/ +UINT64 +EFIAPI +GetSpitPageTableEntryNoExecute ( + IN PHYSICAL_ADDRESS Address + ) +{ + return 0; +} diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/Readme.md b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/Readme.md new file mode 100644 index 000000000..432a97048 --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/Readme.md @@ -0,0 +1,25 @@ +# Memory Attribute Protocol UEFI shell functional Test + +## 🔹 Copyright + +Copyright (C) Microsoft Corporation. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +## Attribution + +This test is a modified version of . + +## About This Test + +Tests does basic verification of the Memory Attribute Protocol + +* Make sure protocol exists +* Basic "good path" usage of Get/Clear/Set functions +* Get Attributes of a newly allocated EfiLoaderCode buffer +* Verify Attributes of running code (this test code) +* Test the freeing of pages with protection attributes +* Test splitting pages with protection attributes + +MdePkg is the ideal place for this test, but because the AARCH64 PageSplitTest relies on +ArmLib to get the translation table base address and TCR, this test is in the UefiTestingPkg to avoid +circular repo dependencies in Project Mu. diff --git a/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/X64/PageSplitTest.c b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/X64/PageSplitTest.c new file mode 100644 index 000000000..ca0d2c41f --- /dev/null +++ b/UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/X64/PageSplitTest.c @@ -0,0 +1,100 @@ +/** @file PageSplitTest.c +TCBZ3519 +Functionality to support PageSplitTest.c + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent +***/ + +#include "MemoryAttributeProtocolFuncTestApp.h" + +#define PAGING_PAE_INDEX_MASK 0x1FF +#define PAGE_TABLE_PRESENT_BIT 0x1 +#define PAGE_TABLE_BASE_ADDRESS 0xFFFFFFFFFF000 +#define PAGE_TABLE_IS_LEAF 0x80 +#define PAGE_TABLE_NX 0x8000000000000000 + +/** + Get an un-split page table entry and allocate entire region so the page + doesn't need to be split on allocation + + @param[out] Address Address of allocated 2MB page region +**/ +EFI_STATUS +EFIAPI +GetUnsplitPageTableEntry ( + OUT EFI_PHYSICAL_ADDRESS *Address + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS BaseAddress; + UINT64 *L4Table; + UINT64 *L3Table; + UINT64 *L2Table; + UINTN Index2; + UINTN Index3; + UINTN Index4; + + L4Table = (UINT64 *)AsmReadCr3 (); + + for (Index4 = 0x0; Index4 < 0x200; Index4++) { + if (!L4Table[Index4] & PAGE_TABLE_PRESENT_BIT) { + continue; + } + + L3Table = (UINT64 *)(L4Table[Index4] & PAGE_TABLE_BASE_ADDRESS); + + for (Index3 = 0x0; Index3 < 0x200; Index3++ ) { + if (!L3Table[Index3] & PAGE_TABLE_PRESENT_BIT) { + continue; + } + + if (!(L3Table[Index3] & PAGE_TABLE_IS_LEAF)) { + L2Table = (UINT64 *)(L3Table[Index3] & PAGE_TABLE_BASE_ADDRESS); + + for (Index2 = 0x0; Index2 < 0x200; Index2++ ) { + if (L2Table[Index2] & PAGE_TABLE_IS_LEAF) { + BaseAddress = (Index4 * PTE512GB) + (Index3 * PTE1GB) + (Index2 * PTE2MB); + Status = gBS->AllocatePages (AllocateAddress, EfiLoaderCode, EFI_SIZE_TO_PAGES (PTE2MB), &BaseAddress); + if (!EFI_ERROR (Status)) { + *Address = BaseAddress; + return EFI_SUCCESS; + } + } + } + } + } + } + + return EFI_OUT_OF_RESOURCES; +} + +/** + Check if the 2MB page entry correlating with the input address + is set to no-execute + + @param[in] Address Address of the page table entry +**/ +UINT64 +EFIAPI +GetSpitPageTableEntryNoExecute ( + IN PHYSICAL_ADDRESS Address + ) +{ + UINT64 *L4Table; + UINT64 *L3Table; + UINT64 *L2Table; + UINTN Index4; + UINTN Index3; + UINTN Index2; + + Index4 = ((UINTN)RShiftU64 (Address, 39)) & PAGING_PAE_INDEX_MASK; + Index3 = ((UINTN)Address >> 30) & PAGING_PAE_INDEX_MASK; + Index2 = ((UINTN)Address >> 21) & PAGING_PAE_INDEX_MASK; + + L4Table = (UINT64 *)AsmReadCr3 (); + L3Table = (UINT64 *)(L4Table[Index4] & PAGE_TABLE_BASE_ADDRESS); + L2Table = (UINT64 *)(L3Table[Index3] & PAGE_TABLE_BASE_ADDRESS); + + return L2Table[Index2] & PAGE_TABLE_NX; +} diff --git a/UefiTestingPkg/UefiTestingPkg.ci.yaml b/UefiTestingPkg/UefiTestingPkg.ci.yaml index e59dd3bd9..957410974 100644 --- a/UefiTestingPkg/UefiTestingPkg.ci.yaml +++ b/UefiTestingPkg/UefiTestingPkg.ci.yaml @@ -88,7 +88,8 @@ "IVMDs", "MPIDRs", "PAGESs", - "SMRRs" + "SMRRs", + "unsplit" ], "AdditionalIncludePaths": [] # Additional paths to spell check relative to package root (wildcards supported) } diff --git a/UefiTestingPkg/UefiTestingPkg.dsc b/UefiTestingPkg/UefiTestingPkg.dsc index 5c01dd0b0..67617837f 100644 --- a/UefiTestingPkg/UefiTestingPkg.dsc +++ b/UefiTestingPkg/UefiTestingPkg.dsc @@ -134,6 +134,7 @@ UefiTestingPkg/FunctionalSystemTests/MemmapAndMatTestApp/MemmapAndMatTestApp.inf UefiTestingPkg/FunctionalSystemTests/MorLockTestApp/MorLockTestApp.inf UefiTestingPkg/FunctionalSystemTests/MpManagement/App/MpManagementTestApp.inf + UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.inf [Components.IA32, Components.X64] UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMAIVRSProtectionUnitTestApp.inf