diff --git a/UefiTestingPkg/Include/Library/FlatPageTableLib.h b/UefiTestingPkg/Include/Library/FlatPageTableLib.h new file mode 100644 index 0000000000..6ff503f8b0 --- /dev/null +++ b/UefiTestingPkg/Include/Library/FlatPageTableLib.h @@ -0,0 +1,174 @@ +/** @file + Library to parse page/translation table entries. This library + is restricted to UEFI_APPLICATION modules because it should be + used primarily for testing. For querying page attributes from + non-application modules, core services like the GCD or Memory + Attribute Protocol should be used to maintain coherency. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef FLAT_PAGE_TABLE_LIB_H_ +#define FLAT_PAGE_TABLE_LIB_H_ + +typedef struct { + UINT64 LinearAddress; + UINT64 Length; + UINT64 PageEntry; +} PAGE_MAP_ENTRY; + +typedef struct { + UINT32 ArchSignature; + PAGE_MAP_ENTRY *Entries; + UINTN EntryCount; + UINTN EntryPagesAllocated; +} PAGE_MAP; + +// The signature of the PAGE_MAP struct is used to determine the architecture of the page/translation table +// entries. +#define AARCH64_PAGE_MAP_SIGNATURE SIGNATURE_32 ('A','A','6','4') +#define X64_PAGE_MAP_SIGNATURE SIGNATURE_32 ('X','6','4',' ') + +// This union can be used to interpret each PAGE_MAP_ENTRY on an AARCH64 system. +// These translation table bit definitions were taken from the Armv8 A Architecture Manual version H.a. +typedef union { + struct { + UINT64 Valid : 1; // BIT0 + UINT64 BlockOrTable : 1; // BIT1 + UINT64 AttributeIndex : 3; // BIT2-4 + UINT64 NonSecure : 1; // BIT5 + UINT64 AccessPermissions : 2; // BIT6-7 + UINT64 Shareability : 2; // BIT8-9 + UINT64 AccessFlag : 1; // BIT10 + UINT64 NonGlobal : 1; // BIT11 + UINT64 Oa : 4; // BIT12-15 + UINT64 Nt : 1; // BIT16 + UINT64 OutputAddress : 33; // BIT17-49 + UINT64 Guarded : 1; // BIT50 + UINT64 Dirty : 1; // BIT51 + UINT64 Contiguous : 1; // BIT52 + UINT64 Pxn : 1; // BIT53 + UINT64 Xn : 1; // BIT54 + UINT64 Ignored : 4; // BIT55-58 + UINT64 PageBasedHardwareAttribute : 4; // BIT59-62 + UINT64 Reserved : 1; // BIT63 + } Bits; + UINT64 Uint64; +} AARCH64_PAGE_MAP_ENTRY; + +// This union can be used to interpret each PAGE_MAP_ENTRY on an X64 system. +// These page table bit defintions were taken from September 2023 IntelĀ® 64 and IA-32 Architectures SDM +typedef union { + struct { + UINT64 Present : 1; // BIT0 + UINT64 ReadWrite : 1; // BIT1 + UINT64 UserSupervisor : 1; // BIT2 + UINT64 WriteThrough : 1; // BIT3 + UINT64 CacheDisabled : 1; // BIT4 + UINT64 Accessed : 1; // BIT5 + UINT64 Dirty : 1; // BIT6 + UINT64 Pat : 1; // BIT7 + UINT64 Global : 1; // BIT8 + UINT64 Reserved1 : 3; // BIT9-11 + UINT64 PageTableBaseAddress : 40; // BIT12-51 + UINT64 Reserved2 : 7; // BIT52-58 + UINT64 ProtectionKey : 4; // BIT59-62 + UINT64 Nx : 1; // BIT63 + } Bits; + UINT64 Uint64; +} X64_PAGE_MAP_ENTRY; + +// When the page/translation table is parsed to create an array of PAGE_MAP_ENTRY, the following bits +// are used to determine the attributes of one page/translation table entry are the same as another +// page/translation table entry. If contiguous leaf/block entries have the same attributes, then they +// will be represented in a single PAGE_MAP_ENTRY. +// AArch64: BIT2-11, BIT52-63 +// X64: BIT0-11, BIT52-63 + +/** + Populate the input page/translation table map. + + @param[in, out] Map Pointer to the PAGE_MAP struct to be populated. + + @retval EFI_SUCCESS The translation table is parsed successfully. + @retval EFI_INVALID_PARAMETER MapCount is NULL, or Map is NULL but *MapCount is nonzero. + @retval EFI_BUFFER_TOO_SMALL *MapCount is too small. + MapCount is updated to indicate the expected number of entries. + Caller may still get EFI_BUFFER_TOO_SMALL with the new MapCount. +**/ +EFI_STATUS +EFIAPI +CreateFlatPageTable ( + IN OUT PAGE_MAP *Map + ); + +/** + Checks the input flat page/translation table for the input region and converts the associated + table entries to EFI access attributes (EFI_MEMORY_XP, EFI_MEMORY_RO, EFI_MEMORY_RP). If the + access attributes vary across the region, EFI_NO_MAPPING is returned. + + @param[in] Map Pointer to the PAGE_MAP struct to be parsed + @param[in] Length Length in bytes of the region + @param[in] Length Length of the region + @param[out] Attributes EFI Attributes of the region + + @retval EFI_SUCCESS The output Attributes is valid + @retval EFI_INVALID_PARAMETER The flat translation table has not been built or + Attributes was NULL or Length was 0 + @retval EFI_NOT_FOUND The input region could not be found + @retval EFI_NO_MAPPING The access attributes are not consistent across the region. +**/ +EFI_STATUS +EFIAPI +GetRegionAccessAttributes ( + IN PAGE_MAP *Map, + IN UINT64 Address, + IN UINT64 Length, + OUT UINT64 *Attributes + ); + +/** + Parses the input page to determine if it is writable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is writable. + @retval FALSE The page is not writable. +**/ +BOOLEAN +EFIAPI +IsPageWritable ( + IN UINT64 Page + ); + +/** + Parses the input page to determine if it is executable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is executable. + @retval FALSE The page is not executable. +**/ +BOOLEAN +EFIAPI +IsPageExecutable ( + IN UINT64 Page + ); + +/** + Parses the input page to determine if it is readable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is readable. + @retval FALSE The page is not readable. +**/ +BOOLEAN +EFIAPI +IsPageReadable ( + IN UINT64 Page + ); + +#endif diff --git a/UefiTestingPkg/Library/FlatPageTableLib/AArch64/FlatPageTableAArch64.c b/UefiTestingPkg/Library/FlatPageTableLib/AArch64/FlatPageTableAArch64.c new file mode 100644 index 0000000000..bf1ddf73e4 --- /dev/null +++ b/UefiTestingPkg/Library/FlatPageTableLib/AArch64/FlatPageTableAArch64.c @@ -0,0 +1,374 @@ +/** @file + AArch64 specific page table attribute library functions. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include + +#define TCR_EL1_HPD_FIELD BIT41 // Assumes translation table is located at TTBR0 (UEFI spec dictated) +#define TCR_EL2_HPD_FIELD BIT24 +#define ID_AA64MMFR1_EL1_HPD_MASK 0xF000 +#define TT_HERITABLE_ATTRIBUTES_MASK (TT_TABLE_AP_MASK | TT_TABLE_PXN | TT_TABLE_UXN) +#define AARCH64_ATTRIBUTES_MASK ((0xFFFULL << 52) | (0x3FFULL << 2)) + +#define IS_VALID(page) ((page & 0x1) != 0) +#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) +#define ARM_TT_BASE_ADDRESS(page) (page & TT_ADDRESS_MASK_BLOCK_ENTRY) +#define ARM_TT_BLOCK_ATTRIBUTES(page) (page & AARCH64_ATTRIBUTES_MASK) + +// All translation table bit definitions were taken from the Armv8 A +// Architecture Manual version H.a. +typedef union { + struct { + UINT64 Valid : 1; // BIT0 + UINT64 BlockOrTable : 1; // BIT1 + UINT64 LowerAttributes : 10; // BIT2-11 + UINT64 Address : 36; // BIT12-47 + UINT64 Reserved : 3; // BIT48-50 + UINT64 Ignored : 8; // BIT51-58 + UINT64 PxnTable : 1; // BIT59 + UINT64 XnTable : 1; // BIT60 + UINT64 ApTable : 2; // BIT61-62 + UINT64 NsTable : 1; // BIT63 + } Bits; + UINT64 Uint64; +} TRANSLATION_TABLE_ENTRY_HERITABLE; + +typedef union { + struct { + UINT64 Valid : 1; // BIT0 + UINT64 BlockOrTable : 1; // BIT1 + UINT64 LowerAttributes : 10; // BIT2-11 + UINT64 LevelZeroIndex : 9; // BIT12-20 + UINT64 LevelOneIndex : 9; // BIT21-29 + UINT64 LevelTwoIndex : 9; // BIT30-38 + UINT64 LevelThreeIndex : 9; // BIT39-47 + UINT64 Reserved : 3; // BIT48-50 + UINT64 Ignored : 8; // BIT51-58 + UINT64 PxnTable : 1; // BIT59 + UINT64 XnTable : 1; // BIT60 + UINT64 ApTable : 2; // BIT61-62 + UINT64 NsTable : 1; // BIT63 + } Bits; + UINT64 Uint64; +} TRANSLATION_TABLE_ENTRY_TABLE; + +typedef AARCH64_PAGE_MAP_ENTRY TRANSLATION_TABLE_ENTRY_BLOCK; + +typedef union { + TRANSLATION_TABLE_ENTRY_BLOCK Tteb; + TRANSLATION_TABLE_ENTRY_TABLE Ttet; + TRANSLATION_TABLE_ENTRY_HERITABLE Tteh; + UINT64 Uint64; +} TRANSLATION_TABLE_ENTRY_UNION; + +STATIC BOOLEAN mHierarchicalControlEnabled = FALSE; + +/** + Reads the ID_AA64MMFR1_EL1 special register. + + @retval The UINT64 value of the ID_AA64MMFR1_EL1 special register. +**/ +STATIC +UINT64 +Asm_Read_ID_AA64MMFR1_EL1 ( + VOID + ) +{ + UINT64 value = 0; + __asm volatile ("mrs %0, ID_AA64MMFR1_EL1" : "=r" (value) ::); + + return value; +} + +/** + Checks the ID_AA64MMFR1_EL1 and TCR special registers to see if hierarchical control is enabled. + + Function was derived from Armv8 A Architecture Manual version H.a + Section 13.2.65: "ID_AA64MMFR1_EL1, AArch64 Memory Model Feature Register 1" + Section D13.2.131: TCR_EL1, Translation Control Register (EL1) + Section D13.2.132: TCR_EL2, Translation Control Register (EL2) + + @retval TRUE Hierarchical control is enabled. + @retval FALSE Hierarchical control is disabled. +**/ +STATIC +BOOLEAN +IsHierarchicalControlEnabled ( + VOID + ) +{ + return ((Asm_Read_ID_AA64MMFR1_EL1 () & ID_AA64MMFR1_EL1_HPD_MASK) != 0) && + ((ArmReadCurrentEL () == AARCH64_EL2) ? (ArmGetTCR () & TCR_EL2_HPD_FIELD) == 0 : + (ArmGetTCR () & TCR_EL1_HPD_FIELD) == 0); +} + +/** + Recursively parse the translation table and populate the entries in the input Map. + + @param[in] PageTableBaseAddress The base address of the 512 page table entries in the specified level + @param[in] Level Page level (0, 1, 2, 3) + @param[in] RegionStart The base linear address of the region covered by the page table entries + @param[in] ParentHeritableAttributes The heritable attributes of parent table entries. + @param[in, out] Map Pointer to an array that describes multiple linear address ranges. + @param[in, out] MapCount Pointer to a UINTN that hold the actual number of entries in the Map. + @param[in] MapCapacity The maximum number of entries the Map can hold. + @param[in] LastEntry Pointer to last map entry. + @param[in] OneEntry Pointer to a library internal storage that holds one map entry which is + used when Map array is at capacity. +**/ +STATIC +VOID +TranslationTableParseRecursive ( + IN UINT64 PageTableBaseAddress, + IN UINTN Level, + IN UINT64 RegionStart, + IN TRANSLATION_TABLE_ENTRY_HERITABLE ParentHeritableAttributes, + IN OUT PAGE_MAP_ENTRY *Map, + IN OUT UINTN *MapCount, + IN UINTN MapCapacity, + IN PAGE_MAP_ENTRY **LastEntry, + IN PAGE_MAP_ENTRY *OneEntry + ) +{ + TRANSLATION_TABLE_ENTRY_UNION *PagingEntry; + UINTN Index; + TRANSLATION_TABLE_ENTRY_UNION ScratchEntry; + UINT64 RegionLength; + UINTN EntryCount; + + if ((OneEntry == NULL) || (MapCount == NULL) || (LastEntry == NULL)) { + return; + } + + PagingEntry = (TRANSLATION_TABLE_ENTRY_UNION *)(UINTN)PageTableBaseAddress; + RegionLength = TT_BLOCK_ENTRY_SIZE_AT_LEVEL (Level); + if (Level == 0) { + EntryCount = ROOT_TABLE_LEN (ArmGetTCR () & TCR_T0SZ_MASK); + } else { + EntryCount = TT_ENTRY_COUNT; + } + + for (Index = 0; Index < EntryCount; Index++, RegionStart += RegionLength) { + // Skip unmapped entries + if (!IS_VALID (PagingEntry[Index].Uint64)) { + continue; + } + + if (IS_BLOCK (PagingEntry[Index].Uint64, Level)) { + // FUTURE WORK: Should we check the feature register to see if blocks are allowed at level 1? + ASSERT (Level == 1 || Level == 2 || Level == 3); + + ScratchEntry.Uint64 = PagingEntry[Index].Tteb.Uint64; + + // If the entry is a block, then then some access attributes are inherited from the parent + // when FEAT_HPDS is active + if (mHierarchicalControlEnabled) { + // Check if the parent table entry has XnTable bits to pass down to the block entry. + // + // This logic was derived from the Armv8 A Architecture Manual version H.a + // Section D5.4.5: Data access permission controls + // Subsection: Hierarchical control of instruction fetching + // + // If in EL2, only the XN bit is valid + if ((ArmReadCurrentEL () == AARCH64_EL2) && (ParentHeritableAttributes.Bits.XnTable != 0)) { + ScratchEntry.Tteb.Bits.Xn = 1; + } + // if in EL1, both UXN and PXN bits are valid + else if ((ArmReadCurrentEL () == AARCH64_EL1)) { + // Xn is Uxn for EL1 + if (ParentHeritableAttributes.Bits.XnTable != 0) { + ScratchEntry.Tteb.Bits.Xn = 1; + } + + if (ParentHeritableAttributes.Bits.PxnTable != 0) { + ScratchEntry.Tteb.Bits.Pxn = 1; + } + } + + // Check if the parent table entry has ApTable bits to pass down to the block entry + // + // This logic was derived from the Armv8 A Architecture Manual version H.a + // Section D5.4.5: Data access permission controls + // Subsection: Hierarchical control of data access permissions + // + // 0b01 -> Access from EL0 is not allowed, no effect on write permissions + if ((ParentHeritableAttributes.Bits.ApTable & TT_TABLE_AP_MASK) == TT_TABLE_AP_EL0_NO_ACCESS) { + // BIT6 toggles access from EL0 + ScratchEntry.Tteb.Bits.AccessPermissions &= ~((UINT64)BIT6); // Clear BIT6 + // 0b10 -> Access from EL0 is read-only, no write permissions at any EL + } else if ((ParentHeritableAttributes.Bits.ApTable & TT_TABLE_AP_MASK) == TT_TABLE_AP_NO_WRITE_ACCESS) { + // BIT7 toggles write access for all ELs + ScratchEntry.Tteb.Bits.AccessPermissions |= BIT7; // Set BIT7 + // 0b11 -> Access from EL0 is not allowed, no write permissions at any EL + } else if ((ParentHeritableAttributes.Bits.ApTable & TT_TABLE_AP_MASK) == TT_TABLE_AP_MASK) { + ScratchEntry.Tteb.Bits.AccessPermissions |= BIT7; // Set BIT7 + ScratchEntry.Tteb.Bits.AccessPermissions &= ~((UINT64)BIT6); // Clear BIT6 + } + } + + if ((*LastEntry != NULL) && + ((*LastEntry)->LinearAddress + (*LastEntry)->Length == RegionStart) && + (ARM_TT_BASE_ADDRESS ((*LastEntry)->PageEntry) + (*LastEntry)->Length + == ARM_TT_BASE_ADDRESS (ScratchEntry.Uint64)) && + (ARM_TT_BLOCK_ATTRIBUTES ((*LastEntry)->PageEntry) == ARM_TT_BLOCK_ATTRIBUTES (ScratchEntry.Uint64)) + ) + { + // Extend LastEntry. + (*LastEntry)->Length += RegionLength; + } else { + if (*MapCount < MapCapacity) { + // LastEntry points to next map entry in the array. + *LastEntry = &Map[*MapCount]; + } else { + // LastEntry points to library internal map entry. + *LastEntry = OneEntry; + } + + // Set LastEntry. + (*LastEntry)->LinearAddress = RegionStart; + (*LastEntry)->Length = RegionLength; + (*LastEntry)->PageEntry = ScratchEntry.Uint64; + (*MapCount)++; + } + } else { + ScratchEntry.Uint64 = PagingEntry[Index].Ttet.Uint64 & TT_HERITABLE_ATTRIBUTES_MASK; + // If the entry is a table and not the root, then pass the heritable access attributes + // from the parent. + if (Level > 0) { + ScratchEntry.Uint64 |= (ParentHeritableAttributes.Uint64 & TT_HERITABLE_ATTRIBUTES_MASK); + } + + TranslationTableParseRecursive ( + ARM_TT_BASE_ADDRESS (PagingEntry[Index].Ttet.Uint64), + Level + 1, + RegionStart, + ScratchEntry.Tteh, + Map, + MapCount, + MapCapacity, + LastEntry, + OneEntry + ); + } + } +} + +/** + Populate the input page/translation table map. + + @param[in, out] Map Pointer to the PAGE_MAP struct to be populated. + + @retval EFI_SUCCESS The translation table is parsed successfully. + @retval EFI_INVALID_PARAMETER MapCount is NULL, or Map is NULL but *MapCount is nonzero. + @retval EFI_BUFFER_TOO_SMALL *MapCount is too small. + MapCount is updated to indicate the expected number of entries. + Caller may still get EFI_BUFFER_TOO_SMALL with the new MapCount. +**/ +EFI_STATUS +EFIAPI +CreateFlatPageTable ( + IN OUT PAGE_MAP *Map + ) +{ + UINTN LocalEntryCount; + PAGE_MAP_ENTRY *LastEntry; + PAGE_MAP_ENTRY OneEntry; + TRANSLATION_TABLE_ENTRY_HERITABLE HeritableAttributes; + UINTN T0SZ; + + ASSERT (sizeof (OneEntry.PageEntry) == sizeof (AARCH64_PAGE_MAP_ENTRY)); + + if ((Map == NULL) || ((Map->Entries == NULL) && (Map->EntryCount != 0))) { + return EFI_INVALID_PARAMETER; + } + + Map->ArchSignature = AARCH64_PAGE_MAP_SIGNATURE; + + T0SZ = ArmGetTCR () & TCR_T0SZ_MASK; + + mHierarchicalControlEnabled = IsHierarchicalControlEnabled (); + HeritableAttributes.Uint64 = 0; + LastEntry = NULL; + LocalEntryCount = 0; + + TranslationTableParseRecursive ( + (UINTN)ArmGetTTBR0BaseAddress (), + 0, + 0, + HeritableAttributes, + Map->Entries, + &LocalEntryCount, + Map->EntryCount, + &LastEntry, + &OneEntry + ); + + if (LocalEntryCount > Map->EntryCount) { + return EFI_BUFFER_TOO_SMALL; + } + + Map->EntryCount = LocalEntryCount; + return RETURN_SUCCESS; +} + +/** + Parses the input page to determine if it is writable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is writable. + @retval FALSE The page is not writable. +**/ +BOOLEAN +EFIAPI +IsPageWritable ( + IN UINT64 Page + ) +{ + return ((Page & TT_AP_RW_RW) != 0) || ((Page & TT_AP_MASK) == 0); +} + +/** + Parses the input page to determine if it is executable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is executable. + @retval FALSE The page is not executable. +**/ +BOOLEAN +EFIAPI +IsPageExecutable ( + IN UINT64 Page + ) +{ + return (ArmReadCurrentEL () == AARCH64_EL2) ? + ((Page & TT_XN_MASK) == 0) : ((Page & TT_UXN_MASK) == 0 || (Page & TT_PXN_MASK) == 0); +} + +/** + Parses the input page to determine if it is readable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is readable. + @retval FALSE The page is not readable. +**/ +BOOLEAN +EFIAPI +IsPageReadable ( + IN UINT64 Page + ) +{ + return (Page & TT_AF) != 0; +} diff --git a/UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.c b/UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.c new file mode 100644 index 0000000000..cdd471e5ba --- /dev/null +++ b/UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.c @@ -0,0 +1,114 @@ +/** @file + Library to parse page/translation table entries. This library + is restricted to UEFI_APPLICATION modules because it should be + used primarily for testing. For querying page attributes from + non-application modules, core services like the GCD or Memory + Attribute Protocol should be used to maintain coherency. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include + +// TRUE if A and B have overlapping intervals +#define CHECK_OVERLAP(AStart, AEnd, BStart, BEnd) \ + ((AEnd > AStart) && (BEnd > BStart) && \ + ((AStart <= BStart && AEnd > BStart) || \ + (BStart <= AStart && BEnd > AStart))) + +/** + Checks the input flat page/translation table for the input region and converts the associated + table entries to EFI access attributes (EFI_MEMORY_XP, EFI_MEMORY_RO, EFI_MEMORY_RP). If the + access attributes vary across the region, EFI_NOT_FOUND is returned. + + @param[in] Map Pointer to the PAGE_MAP struct to be parsed + @param[in] Length Length in bytes of the region + @param[in] Length Length of the region + @param[out] Attributes EFI Attributes of the region + + @retval EFI_SUCCESS The output Attributes is valid + @retval EFI_INVALID_PARAMETER The flat translation table has not been built or + Attributes was NULL or Length was 0 + @retval EFI_NOT_FOUND The input region could not be found + @retval EFI_NO_MAPPING The access attributes are not consistent across the region. +**/ +EFI_STATUS +EFIAPI +GetRegionAccessAttributes ( + IN PAGE_MAP *Map, + IN UINT64 Address, + IN UINT64 Length, + OUT UINT64 *Attributes + ) +{ + UINTN Index; + UINT64 EntryStartAddress; + UINT64 EntryEndAddress; + UINT64 InputEndAddress; + BOOLEAN FoundRange; + UINT64 FoundAttributes; + UINT64 FoundAttributesOriginal; + + if ((Map->Entries == NULL) || (Map->EntryCount == 0) || + (Attributes == NULL) || (Length == 0)) + { + return EFI_INVALID_PARAMETER; + } + + FoundRange = FALSE; + Index = 0; + InputEndAddress = 0; + + if (EFI_ERROR (SafeUint64Add (Address, Length - 1, &InputEndAddress))) { + return EFI_INVALID_PARAMETER; + } + + do { + EntryStartAddress = Map->Entries[Index].LinearAddress; + if (EFI_ERROR ( + SafeUint64Add ( + Map->Entries[Index].LinearAddress, + Map->Entries[Index].Length - 1, + &EntryEndAddress + ) + )) + { + return EFI_ABORTED; + } + + if (CHECK_OVERLAP (Address, InputEndAddress, EntryStartAddress, EntryEndAddress)) { + if (!FoundRange) { + FoundAttributesOriginal = 0; + FoundAttributesOriginal |= IsPageExecutable (Map->Entries[Index].PageEntry) ? 0 : EFI_MEMORY_XP; + FoundAttributesOriginal |= IsPageWritable (Map->Entries[Index].PageEntry) ? 0 : EFI_MEMORY_RO; + FoundAttributesOriginal |= IsPageReadable (Map->Entries[Index].PageEntry) ? 0 : EFI_MEMORY_RP; + FoundRange = TRUE; + } else { + FoundAttributes = 0; + FoundAttributes |= IsPageExecutable (Map->Entries[Index].PageEntry) ? 0 : EFI_MEMORY_XP; + FoundAttributes |= IsPageWritable (Map->Entries[Index].PageEntry) ? 0 : EFI_MEMORY_RO; + FoundAttributes |= IsPageReadable (Map->Entries[Index].PageEntry) ? 0 : EFI_MEMORY_RP; + if (FoundAttributesOriginal != FoundAttributes) { + return EFI_NO_MAPPING; + } + } + + Address = EntryEndAddress + 1; + } + + if (EntryEndAddress >= InputEndAddress) { + break; + } + } while (++Index < Map->EntryCount); + + if (FoundRange) { + *Attributes = FoundAttributesOriginal; + } + + return FoundRange ? EFI_SUCCESS : EFI_NOT_FOUND; +} diff --git a/UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.inf b/UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.inf new file mode 100644 index 0000000000..e0df49637f --- /dev/null +++ b/UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.inf @@ -0,0 +1,55 @@ +## @file +# Library to parse page/translation table entries. This library +# is restricted to UEFI_APPLICATION modules because it should be +# used primarily for testing. For querying page attributes from +# non-application modules, core services like the GCD or Memory +# Attribute Protocol should be used to maintain coherency. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = FlatPageTableLib + FILE_GUID = 9ef87293-dd33-4d4e-aa2b-3a6c982f4665 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = FlatPageTableLib|UEFI_APPLICATION + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = X64 AARCH64 +# + +[Sources] + FlatPageTableLib.c + +[Sources.X64] + X64/FlatPageTableX64.c + +[Sources.AARCH64] + AArch64/FlatPageTableAArch64.c + +[Packages] + MdePkg/MdePkg.dec + UefiTestingPkg/UefiTestingPkg.dec + +[Packages.X64] + UefiCpuPkg/UefiCpuPkg.dec + +[Packages.AARCH64] + ArmPkg/ArmPkg.dec + +[LibraryClasses] + BaseLib + SafeIntLib + DebugLib + +[LibraryClasses.X64] + CpuPageTableLib + +[LibraryClasses.AARCH64] + ArmLib diff --git a/UefiTestingPkg/Library/FlatPageTableLib/X64/FlatPageTableX64.c b/UefiTestingPkg/Library/FlatPageTableLib/X64/FlatPageTableX64.c new file mode 100644 index 0000000000..245006c7f1 --- /dev/null +++ b/UefiTestingPkg/Library/FlatPageTableLib/X64/FlatPageTableX64.c @@ -0,0 +1,108 @@ +/** @file + X64 specific page table attribute library functions. + + Copyright (c) Microsoft Corporation. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include + +#define X64_PRESENT_BIT BIT0 +#define X64_RW_BIT BIT1 +#define X64_NX_BIT BIT63 + +/** + Populate the input page/translation table map. + + @param[in, out] Map Pointer to the PAGE_MAP struct to be populated. + + @retval EFI_SUCCESS The translation table is parsed successfully. + @retval EFI_INVALID_PARAMETER MapCount is NULL, or Map is NULL but *MapCount is nonzero. + @retval EFI_BUFFER_TOO_SMALL *MapCount is too small. + MapCount is updated to indicate the expected number of entries. + Caller may still get EFI_BUFFER_TOO_SMALL with the new MapCount. +**/ +EFI_STATUS +EFIAPI +CreateFlatPageTable ( + IN OUT PAGE_MAP *Map + ) +{ + IA32_CR4 Cr4; + PAGING_MODE PagingMode; + + ASSERT (sizeof (PAGE_MAP_ENTRY) == sizeof (IA32_MAP_ENTRY)); + + if ((Map == NULL) || ((Map->Entries == NULL) && (Map->EntryCount != 0))) { + return EFI_INVALID_PARAMETER; + } + + Map->ArchSignature = X64_PAGE_MAP_SIGNATURE; + + // Poll CR4 to deterimine the page table depth + Cr4.UintN = AsmReadCr4 (); + + if (Cr4.Bits.LA57 != 0) { + PagingMode = Paging5Level; + } else { + PagingMode = Paging4Level; + } + + return PageTableParse (AsmReadCr3 (), PagingMode, (IA32_MAP_ENTRY *)Map->Entries, &Map->EntryCount); +} + +/** + Parses the input page to determine if it is writable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is writable. + @retval FALSE The page is not writable. +**/ +BOOLEAN +EFIAPI +IsPageWritable ( + IN UINT64 Page + ) +{ + return (Page & X64_RW_BIT) != 0; +} + +/** + Parses the input page to determine if it is executable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is executable. + @retval FALSE The page is not executable. +**/ +BOOLEAN +EFIAPI +IsPageExecutable ( + IN UINT64 Page + ) +{ + return (Page & X64_NX_BIT) == 0; +} + +/** + Parses the input page to determine if it is readable. + + @param[in] Page The page entry to parse. + + @retval TRUE The page is readable. + @retval FALSE The page is not readable. +**/ +BOOLEAN +EFIAPI +IsPageReadable ( + IN UINT64 Page + ) +{ + return (Page & X64_PRESENT_BIT) != 0; +} diff --git a/UefiTestingPkg/UefiTestingPkg.dec b/UefiTestingPkg/UefiTestingPkg.dec index ca15193c6e..d2216a25d3 100644 --- a/UefiTestingPkg/UefiTestingPkg.dec +++ b/UefiTestingPkg/UefiTestingPkg.dec @@ -20,6 +20,11 @@ ## PlatformSmmProtectionsTestLib|Include/Library/PlatformSmmProtectionsTestLib.h + ## @libraryclass Provides a way to parse the page/translation table + ## entries into a flat map. + ## + FlatPageTableLib|Include/Library/FlatPageTableLib.h + [Protocols] ## Include/Protocol/MpManagement.h gMpManagementProtocolGuid = { 0x2b0a3788, 0xe602, 0x424f, { 0xa8, 0x32, 0xa1, 0x13, 0x77, 0xa7, 0x6d, 0x73 } } diff --git a/UefiTestingPkg/UefiTestingPkg.dsc b/UefiTestingPkg/UefiTestingPkg.dsc index c3e9dd14bf..3e79ee6523 100644 --- a/UefiTestingPkg/UefiTestingPkg.dsc +++ b/UefiTestingPkg/UefiTestingPkg.dsc @@ -68,6 +68,7 @@ ExceptionPersistenceLib|MdeModulePkg/Library/BaseExceptionPersistenceLibNull/BaseExceptionPersistenceLibNull.inf CpuPageTableLib|UefiCpuPkg/Library/CpuPageTableLib/CpuPageTableLib.inf DxeMemoryProtectionHobLib|MdeModulePkg/Library/MemoryProtectionHobLibNull/DxeMemoryProtectionHobLibNull.inf + FlatPageTableLib|UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.inf [LibraryClasses.ARM, LibraryClasses.AARCH64] ArmDisassemblerLib|ArmPkg/Library/ArmDisassemblerLib/ArmDisassemblerLib.inf @@ -141,6 +142,7 @@ UefiTestingPkg/FunctionalSystemTests/MorLockTestApp/MorLockTestApp.inf UefiTestingPkg/FunctionalSystemTests/MpManagement/App/MpManagementTestApp.inf UefiTestingPkg/FunctionalSystemTests/MemoryAttributeProtocolFuncTestApp/MemoryAttributeProtocolFuncTestApp.inf + UefiTestingPkg/Library/FlatPageTableLib/FlatPageTableLib.inf [Components.IA32, Components.X64] UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMAIVRSProtectionUnitTestApp.inf