Publish the files
This commit is contained in:
480
Sources/ExtendedPageTables.c
Normal file
480
Sources/ExtendedPageTables.c
Normal file
@@ -0,0 +1,480 @@
|
||||
/*!
|
||||
@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 "Utils.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 table recursively.
|
||||
|
||||
@param[in,out] EptTable - A pointer to the EPT table to clean up.
|
||||
|
||||
@param[in] PageMapLevel - A 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);
|
||||
}
|
||||
Reference in New Issue
Block a user