Files
MiniVisorPkg/Sources/ExtendedPageTables.c
2020-03-08 20:46:35 -07:00

481 lines
14 KiB
C

/*!
@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);
}