/*! @file ExtendedPageTables.c @brief Functions for EPT handling. @author Satoshi Tanda @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved. */ #include "ExtendedPageTables.h" #include "Asm.h" #include "Ia32.h" #include "MemoryManager.h" #include "Platform.h" #include "Logger.h" #include "MemoryType.h" #include "Ia32Utils.h" // // The set of EPT paging structure entries involved with to translate the GPA. // typedef struct _EPT_ENTRIES { EPT_PML4* Pml4e; union { EPDPTE_1GB* AsLargePage; EPDPTE* AsRegularPage; } Pdpte; union { EPDE_2MB* AsLargePage; EPDE* AsRegularPage; } Pde; EPTE* Pte; } EPT_ENTRIES; C_ASSERT(sizeof(EPT_ENTRIES) == sizeof(VOID*) * 4); /*! @brief Cleans up all EPT entries and the tables recursively. @param[in,out] EptTable - The pointer to the EPT table to clean up. @param[in] PageMapLevel - The level of the table. */ static VOID CleanupTables ( _Inout_ _Pre_notnull_ EPT_ENTRY* EptTable, _In_ UINT32 PageMapLevel ) { // // EPT PT does not have any subtables to delete, and so attempting so is // invalid. // MV_ASSERT(PageMapLevel != EPT_LEVEL_PTE); // // Go through all 512 entries in the table. // for (UINT32 i = 0; i < 512; ++i) { EPT_ENTRY eptEntry; VOID* subTable; eptEntry = EptTable[i]; // // Go to the next entry of the table if the entry is not initialized or // is a large page entry, which does not point to the next table. // if ((eptEntry.PageFrameNumber == 0) || (eptEntry.LargePage != FALSE)) { continue; } // // Get the address of the subtable. Free it if it is EPT PTE (ie, the // current table is EPT PD) as EPT PTE does not have any more subtables. // Otherwise, perform the same operations against the subtable. // subTable = GetVirtualAddress(eptEntry.PageFrameNumber << PAGE_SHIFT); if (PageMapLevel == EPT_LEVEL_PDE) { MmFreePages(subTable); } else { CleanupTables(subTable, PageMapLevel - 1); } } // // All subtables referenced from this table were freed. It is OK to free // this table as well. // MmFreePages(EptTable); } /*! @brief Builds EPT paging structures for the range of 512GB managed by the given EPT PML4E. @param[in,out] EptPml4 - The pointer to the EPT PML4. @param[in] EptPml4Index - The index within the EPT PML4 to build translation. @return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code. */ static _Must_inspect_result_ MV_STATUS BuildEptEntriesFor512Gb ( _Inout_ EPT_PML4* EptPml4, _In_ UINT32 EptPml4Index ) { static CONST UINT64 oneGigaByte = (1 * 1024 * 1024 * 1024); static CONST UINT64 twoMegaByte = (2 * 1024 * 1024); MV_STATUS status; EPT_ENTRY defaultPermissions; UINT64 hostPhysicalAddress; IA32_MEMORY_TYPE memoryType; EPT_ENTRIES eptEntries; EPDPTE* eptPdpt; EPDE_2MB* eptPd; EPTE* eptPt; defaultPermissions.Flags = 0; defaultPermissions.ReadAccess = TRUE; defaultPermissions.WriteAccess = TRUE; defaultPermissions.ExecuteAccess = TRUE; // // Allocate the EPT PDPT and fill with the default all allow permissions, and // initialize the EPT PML4 with it. // eptPdpt = MmAllocatePages(1); if (eptPdpt == NULL) { status = MV_STATUS_INSUFFICIENT_RESOURCES; goto Exit; } __stosq((UINT64*)eptPdpt, defaultPermissions.Flags, EPT_PDPTE_ENTRY_COUNT); eptEntries.Pml4e = &EptPml4[EptPml4Index]; eptEntries.Pml4e->ReadAccess = TRUE; eptEntries.Pml4e->WriteAccess = TRUE; eptEntries.Pml4e->ExecuteAccess = TRUE; eptEntries.Pml4e->PageFrameNumber = (GetPhysicalAddress(eptPdpt) >> PAGE_SHIFT); // // Initialize all 512 entries in the EPT PDPT pointed by the EPT PML4E. // for (UINT32 eptPdptIndex = 0; eptPdptIndex < EPT_PDPTE_ENTRY_COUNT; ++eptPdptIndex) { // // Allocate the EPT PD and fill with the default all allow permissions, // and initialize the EPT PDTE with it. // eptPd = MmAllocatePages(1); if (eptPd == NULL) { status = MV_STATUS_INSUFFICIENT_RESOURCES; goto Exit; } __stosq((UINT64*)eptPd, defaultPermissions.Flags, EPT_PDE_ENTRY_COUNT); eptEntries.Pdpte.AsRegularPage = &eptPdpt[eptPdptIndex]; eptEntries.Pdpte.AsRegularPage->PageFrameNumber = (GetPhysicalAddress(eptPd) >> PAGE_SHIFT); // // Initialize all 512 entries in the EPT PD pointed by the EPT PDPTE. // for (UINT32 eptPdIndex = 0; eptPdIndex < EPT_PDE_ENTRY_COUNT; ++eptPdIndex) { eptEntries.Pde.AsLargePage = &eptPd[eptPdIndex]; // // Use the 2MB translation if the entire 2MB managed by this PDE has // same memory type. Otherwise, this PDE points to the EPT PT. // hostPhysicalAddress = ComputeAddressFromIndexes(EptPml4Index, eptPdptIndex, eptPdIndex, 0); memoryType = GetMemoryTypeForRange(hostPhysicalAddress, twoMegaByte); if (memoryType != MEMORY_TYPE_INVALID) { eptEntries.Pde.AsLargePage->LargePage = TRUE; eptEntries.Pde.AsLargePage->MemoryType = memoryType; eptEntries.Pde.AsLargePage->PageFrameNumber = (hostPhysicalAddress >> PAGE_SHIFT_2BM); continue; } // // Cannot be the single 2MB page. Allocate the EPT PT and fill with // the default all allow permissions, and initialize the EPT PDE with it. // eptPt = MmAllocatePages(1); if (eptPt == NULL) { status = MV_STATUS_INSUFFICIENT_RESOURCES; goto Exit; } __stosq((UINT64*)eptPt, defaultPermissions.Flags, EPT_PTE_ENTRY_COUNT); eptEntries.Pde.AsRegularPage->PageFrameNumber = (GetPhysicalAddress(eptPt) >> PAGE_SHIFT); // // Initialize all 512 entries in the EPT PT pointed by the EPT PDE. // for (UINT32 eptPteIndex = 0; eptPteIndex < EPT_PTE_ENTRY_COUNT; ++eptPteIndex) { hostPhysicalAddress = ComputeAddressFromIndexes(EptPml4Index, eptPdptIndex, eptPdIndex, eptPteIndex); memoryType = GetMemoryTypeForRange(hostPhysicalAddress, PAGE_SIZE); MV_ASSERT(memoryType != MEMORY_TYPE_INVALID); eptEntries.Pte = &eptPt[eptPteIndex]; eptEntries.Pte->MemoryType = memoryType; eptEntries.Pte->PageFrameNumber = (hostPhysicalAddress >> PAGE_SHIFT); } } } status = MV_STATUS_SUCCESS; Exit: return status; } /*! @brief Split a 2MB EPT PDE to 512 EPT PTEs. @param[in,out] EptPdeLarge - The pointer to the 2MB EPT PDE to split. @return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code. */ static _Must_inspect_result_ MV_STATUS Split2MbPage ( _Inout_ EPDE_2MB* EptPdeLarge ) { MV_STATUS status; EPDE* eptPde; EPTE* eptPt; UINT64 hostPaBase; UINT64 hostPaToMap; MV_ASSERT(EptPdeLarge->LargePage != FALSE); // // Allocate the EPT PT as we are going to split one 2MB page to 512 4KB pages. // eptPt = MmAllocatePages(1); if (eptPt == NULL) { status = MV_STATUS_INSUFFICIENT_RESOURCES; goto Exit; } // // Clear the large page bit, and propagate the current permissions to the // all entries in the EPT PTE. // EptPdeLarge->LargePage = FALSE; __stosq((UINT64*)eptPt, EptPdeLarge->Flags, EPT_PTE_ENTRY_COUNT); // // Update the page frame of each EPT PTE. // hostPaBase = (EptPdeLarge->PageFrameNumber << PAGE_SHIFT_2BM); for (UINT32 eptPtIndex = 0; eptPtIndex < EPT_PTE_ENTRY_COUNT; ++eptPtIndex) { hostPaToMap = hostPaBase + ((UINT64)eptPtIndex * PAGE_SIZE); eptPt[eptPtIndex].PageFrameNumber = (hostPaToMap >> PAGE_SHIFT); } // // Finally, update the PDE by pointing to the EPT PT. // eptPde = (EPDE*)EptPdeLarge; eptPde->Reserved1 = eptPde->Reserved2 = eptPde->Reserved3 = eptPde->Reserved4 = 0; eptPde->PageFrameNumber = (GetPhysicalAddress(eptPt) >> PAGE_SHIFT); status = MV_STATUS_SUCCESS; Exit: return status; } _Use_decl_annotations_ MV_STATUS InitializeExtendedPageTables ( EPT_CONTEXT* EptContext ) { MV_STATUS status; EPT_PML4* eptPml4; eptPml4 = MmAllocatePages(1); if (eptPml4 == NULL) { status = MV_STATUS_INSUFFICIENT_RESOURCES; goto Exit; } status = BuildEptEntriesFor512Gb(eptPml4, 0); if (MV_ERROR(status)) { LOG_ERROR("BuildEptEntriesFor512Gb failed : %08x", status); goto Exit; } EptContext->EptPml4 = eptPml4; // // All EPT initialization completed successfully. Set up the // extended-page-table pointer (EPTP). // // The EPTP is the top level data structure for EPT that governs the vast // majority of EPT related behavior and is written to VMCS. This structure // can be understood as CR3 equivalent in EPT as it holds a physical address // of the EPT PML4 table. // See: 24.6.11 Extended-Page-Table Pointer (EPTP) // // // Specify the memory-type used for accessing to any of EPT paging-structures. // We use the memory-type as those structures are allocated, that is the // write-back memory type. We assume that CR0.CD (cache disabled) is 0, which // should always be the case here. // See: 28.2.6.1 Memory Type Used for Accessing EPT Paging Structures // EptContext->EptPointer.MemoryType = MEMORY_TYPE_WRITE_BACK; // // "This value is 1 less than the EPT page-walk length." // "The EPT translation mechanism (...) uses a page-walk length of 4". // See: Table 24-8. Format of Extended-Page-Table Pointer // See: 28.2.2 EPT Translation Mechanism // EptContext->EptPointer.PageWalkLength = EPT_PAGE_WALK_LENGTH_4; // // PFN of the EPT PML4 table. // EptContext->EptPointer.PageFrameNumber = GetPhysicalAddress(EptContext->EptPml4) >> PAGE_SHIFT; Exit: if (MV_ERROR(status)) { if (eptPml4 != NULL) { CleanupTables((EPT_ENTRY*)eptPml4, EPT_LEVEL_PML4E); } } return status; } _Use_decl_annotations_ VOID CleanupExtendedPageTables ( EPT_CONTEXT* EptContext ) { CleanupTables((EPT_ENTRY*)EptContext->EptPml4, EPT_LEVEL_PML4E); } _Use_decl_annotations_ MV_STATUS UpdateExtendPageTables ( EPT_PML4* EptPml4, UINT64 GuestPhysicalAddress, CONST UINT64* HostPhysicalAddress, CONST EPT_ENTRY* Permissions ) { MV_STATUS status; ADDRESS_TRANSLATION_HELPER helper; EPT_ENTRIES eptEntries; EPDPTE_1GB* eptPdpt; EPDE_2MB* eptPd; EPTE* eptPt; MV_ASSERT(ARGUMENT_PRESENT(HostPhysicalAddress) || ARGUMENT_PRESENT(Permissions)); // // Locate the EPT PML4E for the GPA. // helper.AsUInt64 = GuestPhysicalAddress; eptEntries.Pml4e = &EptPml4[helper.AsIndex.Pml4]; MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pml4e) != FALSE); // // Locate the EPT PDPTE for the GPA. The entry must not be large page as we // do not use 1GB page. // eptPdpt = GetVirtualAddress(eptEntries.Pml4e->PageFrameNumber << PAGE_SHIFT); eptEntries.Pdpte.AsLargePage = &eptPdpt[helper.AsIndex.Pdpt]; MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pdpte.AsRegularPage) != FALSE); MV_ASSERT(eptEntries.Pdpte.AsLargePage->LargePage == FALSE); // // Locate the EPT PDE for the GPA. If the entry is the 2MB page, split it. // eptPd = GetVirtualAddress(eptEntries.Pdpte.AsRegularPage->PageFrameNumber << PAGE_SHIFT); eptEntries.Pde.AsLargePage = &eptPd[helper.AsIndex.Pd]; MV_ASSERT(MV_IS_EPT_ENTRY_PRESENT(eptEntries.Pde.AsRegularPage) != FALSE); if (eptEntries.Pde.AsLargePage->LargePage != FALSE) { status = Split2MbPage(eptEntries.Pde.AsLargePage); if (MV_ERROR(status)) { goto Exit; } } MV_ASSERT(eptEntries.Pdpte.AsLargePage->LargePage == FALSE); // // Locate the EPT PTE for the GPA and update translation and permissions as // requested. // eptPt = GetVirtualAddress(eptEntries.Pde.AsRegularPage->PageFrameNumber << PAGE_SHIFT); eptEntries.Pte = &eptPt[helper.AsIndex.Pt]; if (ARGUMENT_PRESENT(HostPhysicalAddress)) { eptEntries.Pte->PageFrameNumber = (*HostPhysicalAddress >> PAGE_SHIFT); } if (ARGUMENT_PRESENT(Permissions)) { eptEntries.Pte->ReadAccess = Permissions->ReadAccess; eptEntries.Pte->WriteAccess = Permissions->WriteAccess; eptEntries.Pte->ExecuteAccess = Permissions->ExecuteAccess; } status = MV_STATUS_SUCCESS; Exit: return status; } _Use_decl_annotations_ VOID InvalidateEptDerivedCache ( UINT64 EptPointer ) { INVEPT_DESCRIPTOR descriptor; INVEPT_TYPE type; RtlZeroMemory(&descriptor, sizeof(descriptor)); descriptor.EptPointer = EptPointer; type = (EptPointer == 0) ? InveptAllContext : InveptSingleContext; MV_VERIFY(AsmInvept(type, &descriptor) == VmxResultOk); } _Use_decl_annotations_ VOID InvalidateVpidDerivedCache ( UINT16 VirtualProcessorId ) { INVVPID_DESCRIPTOR descriptor; INVVPID_TYPE type; RtlZeroMemory(&descriptor, sizeof(descriptor)); descriptor.Vpid = VirtualProcessorId; type = (VirtualProcessorId == 0) ? InvvpidAllContext : InvvpidSingleContext; MV_VERIFY(AsmInvvpid(type, &descriptor)== VmxResultOk); }