/*! @file MiniVisor.c @brief MiniVisor initialization. @author Satoshi Tanda @copyright Copyright (c) 2020 - , Satoshi Tanda. All rights reserved. */ #include "MiniVisor.h" #include "Asm.h" #include "HostInitialization.h" #include "ExtendedPageTables.h" #include "Platform.h" #include "MemoryManager.h" #include "Logger.h" #include "Public.h" #include "MemoryType.h" #include "HostNesting.h" #include "Ia32Utils.h" #include "MemoryAccess.h" // // Memory layout of hypervisor stack which is constructed by the kernel-mode // code. // typedef struct _HYPERVISOR_INITIAL_STACK_DATA { union { // // Low StackLimit[0] StackLimit // ^ ... // ^ ... Layout.Context (StackBase) // ^ ... // ^ StackLimit[KERNEL_STACK_SIZE - 2] // High StackLimit[KERNEL_STACK_SIZE - 1] // DECLSPEC_ALIGN(PAGE_SIZE) UINT8 StackLimit[KERNEL_STACK_SIZE]; struct { // // Available for the hypervisor to freely use. // UINT8 AvailableAsStack[KERNEL_STACK_SIZE - sizeof(HYPERVISOR_CONTEXT)]; // // Set up by the kernel-mode code before starting the hypervisor. // The hypervisor never overwrites this contents. // HYPERVISOR_CONTEXT Context; } Layout; } u; } HYPERVISOR_INITIAL_STACK_DATA; C_ASSERT(sizeof(HYPERVISOR_INITIAL_STACK_DATA) == KERNEL_STACK_SIZE); // // Data structure used for virtualizing a processor. Allocated one for each // processor on the system within the ALL_PROCESSORS_CONTEXTS structure. // This structure is freed after virtualization is terminated because many of // those data is used by the hypervisor or the processors indirectly. For example, // th VmcsRegion member must be valid for the processor to perform VMX operations, // and HypervisorStack too for the hypervisor to run. // typedef struct _PER_PROCESSOR_CONTEXT { // // Indicates a result of the EnableHypervisor function. // MV_STATUS Status; // // The initial RSP value used for the guest (i.e., the initial value set // right after VMLAUNCH). // UINT64 GuestStackPointer; // // The initial RIP value used for the guest (i.e., the initial value set // right after VMLAUNCH). // UINT64 GuestInstructionPointer; // // The EPT related context for this processor. // EPT_CONTEXT EptContext; // // The context structure used to track state related to accessing guest // virtual memory address space from the hypervisor. // MEMORY_ACCESS_CONTEXT MemoryAccessContext; // // The context structure used to track state of the nested hypervisor. // NEXTED_VMX_CONTEXT NestedVmxContext; // // The global descriptor table (GDT) for the host. The size (16 entries) is // just a large enough size for systems seen during development. One could // dynamically allocate this table according with the Limit field of the GDTR // if wish to do so. // SEGMENT_DESCRIPTOR_64 HostGuestGdt[16]; // // The task state segment (TSS) used for both the guest and the host if the // system does not configure it. This is the case on EFI environment. // TASK_STATE_SEGMENT_64 HostGuestTss; // // The copy of GDTR taken before MiniVisor is installed. Used during unload // to restore the value to original. // GDTR OriginalGdtr; // // The page-aligned, 4KB-size region used for the VMXON instruction. // DECLSPEC_ALIGN(PAGE_SIZE) VMXON VmxOnRegion; // // The page-aligned, 4KB size region used for the VMX operations such as // VMREAD, VM-exits and -entry. // DECLSPEC_ALIGN(PAGE_SIZE) VMCS VmcsRegion; // // The page-aligned region used as a hypervisor's stack. Some data is // populated by set up code before hypervisor starts. // DECLSPEC_ALIGN(PAGE_SIZE) HYPERVISOR_INITIAL_STACK_DATA HypervisorStack; } PER_PROCESSOR_CONTEXT; // // Data shared across all processors during virtualization of them and execution // of the hypervisor. Allocated once in the EnableHypervisorOnAllProcessors // function and freed in the DisableHypervisorOnAllProcessors function. // typedef struct _SHARED_PROCESSOR_CONTEXT { // // The number of the PER_PROCESSOR_CONTEXT data in the Contexts member. It // equals to the number of logical processors on the system. // UINT32 NumberOfContexts; // // The MSR bitmap used across all processors. // DECLSPEC_ALIGN(PAGE_SIZE) MSR_BITMAPS MsrBitmaps; // // An array of PER_PROCESSOR_CONTEXTs. Each context is associated with and // used by one logical processor exclusively. // PER_PROCESSOR_CONTEXT Contexts[ANYSIZE_ARRAY]; } SHARED_PROCESSOR_CONTEXT; /*! @brief Returns the VM control value that is adjusted in consideration with the VMX capability MSR. @param[in] VmxCapabilityMsr - The VMX capability MSR to consult to adjust the RequestedValue, @param[in] RequestedValue - The VM control value that needs adjustment. @return The adjusted control value. */ static UINT32 AdjustControlValue ( _In_ IA32_MSR_ADDRESS VmxCapabilityMsr, _In_ UINT32 RequestedValue ) { IA32_VMX_TRUE_CTLS_REGISTER capabilities; UINT32 effectiveValue; MV_ASSERT((VmxCapabilityMsr == IA32_VMX_PINBASED_CTLS) || (VmxCapabilityMsr == IA32_VMX_PROCBASED_CTLS) || (VmxCapabilityMsr == IA32_VMX_EXIT_CTLS) || (VmxCapabilityMsr == IA32_VMX_ENTRY_CTLS) || (VmxCapabilityMsr == IA32_VMX_TRUE_PINBASED_CTLS) || (VmxCapabilityMsr == IA32_VMX_TRUE_PROCBASED_CTLS) || (VmxCapabilityMsr == IA32_VMX_TRUE_EXIT_CTLS) || (VmxCapabilityMsr == IA32_VMX_TRUE_ENTRY_CTLS) || (VmxCapabilityMsr == IA32_VMX_PROCBASED_CTLS2)); capabilities.Flags = __readmsr(VmxCapabilityMsr); effectiveValue = RequestedValue; // // Each bit of the following VMCS values might have to be set or cleared // according with the value indicated by the VMX capability MSRs. // - pin-based VM-execution controls, // - primary processor-based VM-execution controls, // - secondary processor-based VM-execution controls. // // The VMX capability MSR is composed of two 32bit values, the lower 32bits // indicate bits can be 0, and the higher 32bits indicates bits can be 1. // In other words, they indicate bits MUST BE 1 and MUST BE 0 respectively. // The following logic enforces this logic by setting bits that must be 1, // and clearing bits that must be 0. // // See: A.3.1 Pin-Based VM-Execution Controls // See: A.3.2 Primary Processor-Based VM-Execution Controls // See: A.3.3 Secondary Processor-Based VM-Execution Controls // effectiveValue |= capabilities.Allowed0Settings; effectiveValue &= capabilities.Allowed1Settings; return effectiveValue; } /*! @brief Adjusts a pin-based control value in consideration with the VMX capability MSR. @param[in,out] PinBasedControls - The pointer to the pin-based control value to adjust. */ static VOID AdjustPinBasedControls ( _Inout_ IA32_VMX_PINBASED_CTLS_REGISTER* PinBasedControls ) { IA32_MSR_ADDRESS vmxCapabilityMsr; IA32_VMX_BASIC_REGISTER vmxBasicMsr; // // This determines the right VMX capability MSR based on the value of // IA32_VMX_BASIC. With the right VMX capability MSR, the // AdjustControlValue function implements the logic described as below. // "It is necessary for software to consult only one of the capability MSRs // to determine the allowed settings of the pin based VM-execution controls:" // See: A.3.1 Pin-Based VM-Execution Controls // vmxBasicMsr.Flags = __readmsr(IA32_VMX_BASIC); vmxCapabilityMsr = (vmxBasicMsr.VmxControls != FALSE) ? IA32_VMX_TRUE_PINBASED_CTLS : IA32_VMX_PINBASED_CTLS; PinBasedControls->Flags = AdjustControlValue(vmxCapabilityMsr, (UINT32)PinBasedControls->Flags); } /*! @brief Adjusts a processor-based control value in consideration with the VMX capability MSR. @param[in,out] PrimaryProcBasedControls - The pointer to the processor-based control value to adjust. */ static VOID AdjustProcessorBasedControls ( _Inout_ IA32_VMX_PROCBASED_CTLS_REGISTER* PrimaryProcBasedControls ) { IA32_MSR_ADDRESS vmxCapabilityMsr; IA32_VMX_BASIC_REGISTER vmxBasicMsr; // // See AdjustPinBasedControls for the details of the below logic. // vmxBasicMsr.Flags = __readmsr(IA32_VMX_BASIC); vmxCapabilityMsr = (vmxBasicMsr.VmxControls != FALSE) ? IA32_VMX_TRUE_PROCBASED_CTLS : IA32_VMX_PROCBASED_CTLS; PrimaryProcBasedControls->Flags = AdjustControlValue( vmxCapabilityMsr, (UINT32)PrimaryProcBasedControls->Flags); } /*! @brief Adjusts a VM-exit control value in consideration with the VMX capability MSR. @param[in,out] VmExitControls - The pointer to the VM-exit control value to adjust. */ static VOID AdjustVmExitControls ( _Inout_ IA32_VMX_EXIT_CTLS_REGISTER* VmExitControls ) { IA32_MSR_ADDRESS vmxCapabilityMsr; IA32_VMX_BASIC_REGISTER vmxBasicMsr; // // See AdjustPinBasedControls for the details of the below logic. // vmxBasicMsr.Flags = __readmsr(IA32_VMX_BASIC); vmxCapabilityMsr = (vmxBasicMsr.VmxControls != FALSE) ? IA32_VMX_TRUE_EXIT_CTLS : IA32_VMX_EXIT_CTLS; VmExitControls->Flags = AdjustControlValue(vmxCapabilityMsr, (UINT32)VmExitControls->Flags); } /*! @brief Adjusts a VM-entry control value in consideration with the VMX capability MSR. @param[in,out] VmEntryControls - The pointer to the VM-entry control value to adjust. */ static VOID AdjustVmEntryControls ( _Inout_ IA32_VMX_ENTRY_CTLS_REGISTER* VmEntryControls ) { IA32_MSR_ADDRESS vmxCapabilityMsr; IA32_VMX_BASIC_REGISTER vmxBasicMsr; // // See AdjustPinBasedControls for the details of the below logic. // vmxBasicMsr.Flags = __readmsr(IA32_VMX_BASIC); vmxCapabilityMsr = (vmxBasicMsr.VmxControls != FALSE) ? IA32_VMX_TRUE_ENTRY_CTLS : IA32_VMX_ENTRY_CTLS; VmEntryControls->Flags = AdjustControlValue(vmxCapabilityMsr, (UINT32)VmEntryControls->Flags); } /*! @brief Adjusts a secondary processor-based control value in consideration with the VMX capability MSR. @param[in,out] SecondaryProcBasedControls - The pointer to the secondary processor-based control value to adjust. */ static VOID AdjustSecondaryProcessorBasedControls ( _Inout_ IA32_VMX_PROCBASED_CTLS2_REGISTER* SecondaryProcBasedControls ) { // // There is no TRUE MSR for IA32_VMX_PROCBASED_CTLS2. Just use // IA32_VMX_PROCBASED_CTLS2 unconditionally. // SecondaryProcBasedControls->Flags = AdjustControlValue( IA32_VMX_PROCBASED_CTLS2, (UINT32)SecondaryProcBasedControls->Flags); } /*! @brief Tests whether our hypervisor is installed on the system. @return TRUE when our hypervisor is installed on the system; otherwise FALSE. */ static _Must_inspect_result_ BOOLEAN IsMiniVisorInstalled ( ) { int registers[4]; // EAX, EBX, ECX, and EDX char vendorId[13]; // // When our hypervisor is installed, CPUID leaf 40000000h will return // "MiniVisor " as the vendor name. // __cpuid(registers, CPUID_HV_VENDOR_AND_MAX_FUNCTIONS); RtlCopyMemory(vendorId + 0, ®isters[1], sizeof(registers[1])); RtlCopyMemory(vendorId + 4, ®isters[2], sizeof(registers[2])); RtlCopyMemory(vendorId + 8, ®isters[3], sizeof(registers[3])); vendorId[12] = ANSI_NULL; return (strcmp(vendorId, "MiniVisor ") == 0); } /*! @brief Disables the hypervisor on the current processor. @param[in,out] Context - The pointer to the location to receive the address of the shared processor context. */ MV_SECTION_PAGED static _IRQL_requires_max_(PASSIVE_LEVEL) VOID DisableHypervisor ( _Inout_ VOID* Context ) { SHARED_PROCESSOR_CONTEXT* returnedAddress; SHARED_PROCESSOR_CONTEXT** sharedProcessorContextsAddress; PER_PROCESSOR_CONTEXT* processorContext; PAGED_CODE(); if (IsMiniVisorInstalled() == FALSE) { goto Exit; } // // Issues the hypercall to uninstall the hypervisor. This hypercall returns // the address of the shared processor context on success. // returnedAddress = (SHARED_PROCESSOR_CONTEXT*)AsmVmxCall(VmcallUninstall, 0, 0, 0); MV_ASSERT(returnedAddress != NULL); // // Context here is shared across all processors and could already be set the // address of the shared processor context by the other processors. This assert // says the address specified by the Context is either not updated yet (== NULL) // or updated by the same value as this processor has received (== returnedAddress // == the address of the shared processor context). // sharedProcessorContextsAddress = Context; MV_ASSERT((*sharedProcessorContextsAddress == NULL) || (*sharedProcessorContextsAddress == returnedAddress)); *sharedProcessorContextsAddress = returnedAddress; // // Clean up the per-processor data structures. // processorContext = &(*sharedProcessorContextsAddress)->Contexts[GetCurrentProcessorNumber()]; CleanupExtendedPageTables(&processorContext->EptContext); CleanupMemoryAccess(&processorContext->MemoryAccessContext); CleanupGdt(&processorContext->OriginalGdtr); Exit: MV_ASSERT(IsMiniVisorInstalled() == FALSE); } /*! @brief Disables the hypervisor on the all processors. */ MV_SECTION_PAGED static _IRQL_requires_max_(PASSIVE_LEVEL) VOID DisableHypervisorOnAllProcessors ( ) { SHARED_PROCESSOR_CONTEXT* sharedProcessorContext; PAGED_CODE(); sharedProcessorContext = NULL; RunOnAllProcessors(DisableHypervisor, &sharedProcessorContext); if (sharedProcessorContext != NULL) { MmFreePages(sharedProcessorContext); } } /*! @brief Tests whether VT-x is available with required capabilities for this hypervisor. @return TRUE when VT-x is available for this hypervisor; otherwise FALSE. */ static _Must_inspect_result_ BOOLEAN IsVmxAvailable ( ) { BOOLEAN vmxAvailable; int registers[4]; CPUID_EAX_01 cpuidVersionInfo; IA32_VMX_BASIC_REGISTER vmxBasicMsr; IA32_FEATURE_CONTROL_REGISTER vmxFeatureControlMsr; IA32_VMX_EPT_VPID_CAP_REGISTER eptVpidCapabilityMsr; vmxAvailable = FALSE; // // "If CPUID.1:ECX.VMX[bit 5] = 1, then VMX operation is supported." // See: 23.6 DISCOVERING SUPPORT FOR VMX // __cpuid(registers, CPUID_VERSION_INFORMATION); cpuidVersionInfo.CpuidFeatureInformationEcx.Flags = (UINT32)registers[2]; if (cpuidVersionInfo.CpuidFeatureInformationEcx.VirtualMachineExtensions == FALSE) { LOG_ERROR("VT-x is not available."); goto Exit; } // // Check the processor support the write-back type for VMCS. We do not // support the processor that does not support the write-back type for // simplicity. It is practically not an issue since // "As of this writing, all processors that support VMX operation indicate // the write-back type." // // Note that supporting the write-back type means we can access virtual // memory holding VMCS as Windows allocates, as the write-back is the default // memory type Windows uses. // // See: A.1 BASIC VMX INFORMATION // vmxBasicMsr.Flags = __readmsr(IA32_VMX_BASIC); if (vmxBasicMsr.MemoryType != MEMORY_TYPE_WRITE_BACK) { LOG_ERROR("The write-back type is not supported on this processor."); goto Exit; } // // Check that: // - the lock bit is set // - the VMXON outside SMX operation bit is set // // "To enable VMX support in a platform, BIOS must set bit 1, bit 2, or both // (see below), as well as the lock bit." // See: 23.7 ENABLING AND ENTERING VMX OPERATION // vmxFeatureControlMsr.Flags = __readmsr(IA32_FEATURE_CONTROL); if ((vmxFeatureControlMsr.LockBit == FALSE) || (vmxFeatureControlMsr.EnableVmxOutsideSmx == FALSE)) { LOG_ERROR("The lock bit is not set, or VMXON outside SMX is unsupported."); goto Exit; } // // Check the followings to confirm availability of EPT related capabilities. // eptVpidCapabilityMsr.Flags = __readmsr(IA32_VMX_EPT_VPID_CAP); if ((eptVpidCapabilityMsr.PageWalkLength4 == FALSE) || (eptVpidCapabilityMsr.MemoryTypeWriteBack == FALSE) || (eptVpidCapabilityMsr.Pde2MbPages == FALSE) || (eptVpidCapabilityMsr.Invept == FALSE) || (eptVpidCapabilityMsr.InveptSingleContext == FALSE) || (eptVpidCapabilityMsr.InveptAllContexts == FALSE) || (eptVpidCapabilityMsr.Invvpid == FALSE) || (eptVpidCapabilityMsr.InvvpidSingleContext == FALSE) || (eptVpidCapabilityMsr.InvvpidAllContexts == FALSE)) { LOG_ERROR("EPT is not supported."); goto Exit; } vmxAvailable = TRUE; Exit: return vmxAvailable; } /*! @brief Sets up a VMCS and initial hypervisor stack values. @details This function roughly follows the sequence stated in the Intel SDM to set up hypervisor. See: 31.5 VMM SETUP & TEAR DOWN See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE @param[in] SharedProcessorContext - The pointer to the shared processor context. @param[in,out] ProcessorContext - The pointer to the per-processor context for this processor. @return MV_STATUS_SUCCESS on success, or an appropriate status code on error. */ static _Must_inspect_result_ MV_STATUS SetupVmcs ( _In_ SHARED_PROCESSOR_CONTEXT* SharedProcessorContext, _Inout_ PER_PROCESSOR_CONTEXT* ProcessorContext ) { // // Masks RPL (bits 1:0) and the TI flag (bit 2) of segment selectors. // static const UINT32 hostSegmentSelectorMask = 0x7; MV_STATUS status; CR0 newCr0; CR4 newCr4; IA32_VMX_BASIC_REGISTER vmxBasicMsr; UINT64 vmxOnPa, vmcsPa; BOOLEAN vmxOn, eptInitialized; GDTR gdtr; IDTR idtr; IA32_VMX_ENTRY_CTLS_REGISTER vmEntryControls; IA32_VMX_EXIT_CTLS_REGISTER vmExitControls; IA32_VMX_PINBASED_CTLS_REGISTER pinBasedControls; IA32_VMX_PROCBASED_CTLS_REGISTER primaryProcBasedControls; IA32_VMX_PROCBASED_CTLS2_REGISTER secondaryProcBasedControls; UINT64 hypervisorStackPointer; UINT32 exceptionBitmap; vmxOn = FALSE; eptInitialized = FALSE; // // First things first. Check the availability of VT-x to run MiniVisor. // // "Check VMX support in processor using CPUID." // "Determine the VMX capabilities supported by the processor through the // VMX capability MSRs." // See: 31.5 VMM SETUP & TEAR DOWN // if (IsVmxAvailable() == FALSE) { LOG_ERROR("IsVmxAvailable failed."); status = MV_STATUS_HV_OPERATION_FAILED; goto Exit; } // // VMX requires TR to be configured properly (ie, non zero). This requirement // is not satisfied yet in case of EFI environment. Set it up by updating // GDT as necessary. // // "The selector fields for CS and TR cannot be 0000H." // See: 26.2.3 Checks on Host Segment and Descriptor-Table Registers // // "TR. The different sub-fields are considered separately:" // See: 26.3.1.2 Checks on Guest Segment Registers // InitializeGdt(&ProcessorContext->HostGuestTss, ProcessorContext->HostGuestGdt, sizeof(ProcessorContext->HostGuestGdt), &ProcessorContext->OriginalGdtr); MV_ASSERT(AsmReadTr() != 0); // // The per-processor context contains the VMXON region that is aligned to a // 4-KByte boundary. We also made sure that VMXON region can be accessed // through the write-back memory type in the IsVmxAvailable function. // // "Create a VMXON region in non-pageable memory of a size specified by // IA32_VMX_BASIC MSR and aligned to a 4-KByte boundary. (...) Also, // software must ensure that the VMXON region is hosted in cache-coherent // memory." // See: 31.5 VMM SETUP & TEAR DOWN // MV_ASSERT(PAGE_ALIGN(&ProcessorContext->VmxOnRegion) == &ProcessorContext->VmxOnRegion); // // "Initialize the version identifier in the VMXON region (the first 31 bits) // with the VMCS revision identifier reported by capability MSRs. Clear bit // 31 of the first 4 bytes of the VMXON region" // See: 31.5 VMM SETUP & TEAR DOWN // vmxBasicMsr.Flags = __readmsr(IA32_VMX_BASIC); ProcessorContext->VmxOnRegion.RevisionId = (UINT32)vmxBasicMsr.VmcsRevisionId; MV_ASSERT(ProcessorContext->VmxOnRegion.MustBeZero == 0); // // In order to enter the VMX-mode, the bits in CR0 and CR4 have to be // certain values as indicated by the FIXED0 and FIXED1 MSRs. The rule is // summarized as below: // // IA32_VMX_CRx_FIXED0 IA32_VMX_CRx_FIXED1 Meaning // Bit X 1 (Always 1) The bit X of CRx is fixed to 1 // Bit X 0 1 The bit X of CRx is flexible // Bit X (Always 0) 0 The bit X of CRx is fixed to 0 // // See: A.7 VMX-FIXED BITS IN CR0 // // "Ensure the current processor operating mode meets the required CR0 fixed // bits (...). Other required CR0 fixed bits can be detected through the // IA32_VMX_CR0_FIXED0 and IA32_VMX_CR0_FIXED1 MSRs." // See: 31.5 VMM SETUP & TEAR DOWN // newCr0.Flags = __readcr0(); newCr0 = AdjustCr0(newCr0); __writecr0(newCr0.Flags); MV_ASSERT(newCr0.PagingEnable != FALSE); MV_ASSERT(newCr0.ProtectionEnable != FALSE); // // "Enable VMX operation by setting CR4.VMXE = 1. Ensure the resultant CR4 // value supports all the CR4 fixed bits reported in the IA32_VMX_CR4_FIXED0 // and IA32_VMX_CR4_FIXED1 MSRs". // See: 31.5 VMM SETUP & TEAR DOWN // newCr4.Flags = __readcr4(); newCr4 = AdjustCr4(newCr4); __writecr4(newCr4.Flags); MV_ASSERT(newCr4.VmxEnable != FALSE); // // The below has been made sure with the IsVmxAvailable function. // // "Ensure that the IA32_FEATURE_CONTROL MSR (MSR index 3AH) has been // properly programmed and that its lock bit is set (Bit 0 = 1)." // See: 31.5 VMM SETUP & TEAR DOWN // // // Enter VMX root operation. // // "Execute VMXON with the physical address of the VMXON region as the // operand." // See: 31.5 VMM SETUP & TEAR DOWN // vmxOnPa = GetPhysicalAddress(&ProcessorContext->VmxOnRegion); if (__vmx_on(&vmxOnPa) != VmxResultOk) { LOG_ERROR("__vmx_on failed"); status = MV_STATUS_HV_OPERATION_FAILED; goto Exit; } vmxOn = TRUE; // // Clear stale cache that potentially exists. // // "Software can use the INVVPID instruction with the "all-context" INVVPID // type immediately after execution of the VMXON instruction (...)." // "Software can use the INVEPT instruction with the "all-context" INVEPT // type immediately after execution of the VMXON instruction (...)." // See: 28.3.3.3 Guidelines for Use of the INVVPID Instruction // See: 28.3.3.4 Guidelines for Use of the INVEPT Instruction // InvalidateEptDerivedCache(0); InvalidateVpidDerivedCache(0); // // The per-processor context contains the VMCS region that is aligned to a // 4-KByte boundary. // // "Create a VMCS region in non-pageable memory of size specified by the VMX // capability MSR IA32_VMX_BASIC and aligned to 4-KBytes. // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // MV_ASSERT(PAGE_ALIGN(&ProcessorContext->VmcsRegion) == &ProcessorContext->VmcsRegion); // // "Initialize the version identifier in the VMCS (first 31 bits) with the // VMCS revision identifier reported by the VMX capability MSR // IA32_VMX_BASIC. Clear bit 31 of the first 4 bytes of the VMCS region." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // ProcessorContext->VmcsRegion.RevisionId = (UINT32)vmxBasicMsr.VmcsRevisionId; MV_ASSERT(ProcessorContext->VmcsRegion.ShadowVmcsIndicator == FALSE); // // "The term "guest-VMCS address" refers to the physical address of the new // VMCS region for the following steps. // "Execute the VMCLEAR instruction by supplying the guest-VMCS address." // "Execute the VMPTRLD instruction by supplying the guest-VMCS address." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // vmcsPa = GetPhysicalAddress(&ProcessorContext->VmcsRegion); if ((__vmx_vmclear(&vmcsPa) != VmxResultOk) || (__vmx_vmptrld(&vmcsPa) != VmxResultOk)) { LOG_ERROR("__vmx_vmclear or __vmx_vmptrld failed"); status = MV_STATUS_HV_OPERATION_FAILED; goto Exit; } // // The processor is in VMX root operation now. This means that the processor // can execute VMREAD, VMWRITE to configure VMCS and VMLAUNCH to start a VM. // Before start issuing VMWRITE, let us prepare data to write. // // // Initialize EPT specific data structures. // status = InitializeExtendedPageTables(&ProcessorContext->EptContext); if (MV_ERROR(status)) { LOG_ERROR("InitializeExtendedPageTables failed : %08x", status); goto Exit; } eptInitialized = TRUE; // // Take the address of the initial stack pointer for hypervisor. // hypervisorStackPointer = (UINT64)&ProcessorContext->HypervisorStack.u.Layout.Context; MV_ASSERT((hypervisorStackPointer % 0x10) == 0); // // Capture the current GDTR and IDTR. // _sgdt(&gdtr); __sidt(&idtr); // // Intercept #DB. This is purely for demonstration and can be removed. // exceptionBitmap = (1 << DivideError); // // VM-entry and -exit controls define how processor should operate on // VM-entry and exit. The following configurations are to achieve that: // - Host always runs on the 64bit mode by setting vmExitControls.LoadIa32Efer // and vmExitControls.HostAddressSpaceSize. // - Guest always runs with IA32_EFER as it writes to it. // - Guest starts as on the 64bit mode as the system is currently so. // vmEntryControls.Flags = 0; vmEntryControls.Ia32EModeGuest = TRUE; vmEntryControls.LoadIa32Efer = TRUE; AdjustVmEntryControls(&vmEntryControls); vmExitControls.Flags = 0; vmExitControls.HostAddressSpaceSize = TRUE; vmExitControls.LoadIa32Efer = TRUE; vmExitControls.SaveIa32Efer = TRUE; AdjustVmExitControls(&vmExitControls); // // The pin-based VM-execution controls governs the handling of asynchronous // events (for example: interrupts). We do not need any of them. // pinBasedControls.Flags = 0; AdjustPinBasedControls(&pinBasedControls); // // The processor-based VM-execution controls govern the handling of // synchronous events, mainly those caused by the execution of specific // instructions. // // - MSR bitmaps are used; this is not to cause VM-exit as much as possible. // We are setting the MSR bitmaps that are mostly cleared below (see // InitializeMsrBitmaps). This prevents VM-exits from occurring when // 0x0 - 0x1fff and 0xc0000000 - 0xc0001fff are accessed. VM-exit still // occurs if outside the range is accessed, and it is not possible to // prevent this. // // - The secondary processor-based controls are used; this is to let the // guest (Windows) executes RDTSCP, INVPCID and the XSAVE/XRSTORS family // instructions. Those instructions are used in Windows 10. If those are // not set, attempt to execute them causes #UD, which results in a bug // check. VPID is enabled, which could lead to better performance for free // by not flushing all TLB on every VM-exit and VM-entry. Finally, enabling // EPT and unrestricted guest which are required for the UEFI hypervisor to // handle the real-mode guest. // primaryProcBasedControls.Flags = 0; primaryProcBasedControls.UseMsrBitmaps = TRUE; primaryProcBasedControls.ActivateSecondaryControls = TRUE; AdjustProcessorBasedControls(&primaryProcBasedControls); secondaryProcBasedControls.Flags = 0; secondaryProcBasedControls.EnableEpt = TRUE; secondaryProcBasedControls.EnableVpid = TRUE; secondaryProcBasedControls.EnableRdtscp = TRUE; secondaryProcBasedControls.UnrestrictedGuest = TRUE; secondaryProcBasedControls.EnableInvpcid = TRUE; secondaryProcBasedControls.EnableXsaves = TRUE; AdjustSecondaryProcessorBasedControls(&secondaryProcBasedControls); // // "Issue a sequence of VMWRITEs to initialize various host-state area // fields in the working VMCS." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // /* 16-Bit Host-State Fields */ // // "In the selector field for each of CS, SS, DS, ES, FS, GS and TR, the // RPL (bits 1:0) and the TI flag (bit 2) must be 0" // See: 26.2.3 Checks on Host Segment and Descriptor-Table Registers // MV_ASSERT(FlagOn(AsmReadEs(), hostSegmentSelectorMask) == FALSE); MV_ASSERT(FlagOn(AsmReadCs(), hostSegmentSelectorMask) == FALSE); MV_ASSERT(FlagOn(AsmReadSs(), hostSegmentSelectorMask) == FALSE); MV_ASSERT(FlagOn(AsmReadDs(), hostSegmentSelectorMask) == FALSE); MV_ASSERT(FlagOn(AsmReadFs(), hostSegmentSelectorMask) == FALSE); MV_ASSERT(FlagOn(AsmReadGs(), hostSegmentSelectorMask) == FALSE); MV_ASSERT(FlagOn(AsmReadTr(), hostSegmentSelectorMask) == FALSE); VmxWrite(VMCS_HOST_ES_SELECTOR, AsmReadEs()); VmxWrite(VMCS_HOST_CS_SELECTOR, AsmReadCs()); VmxWrite(VMCS_HOST_SS_SELECTOR, AsmReadSs()); VmxWrite(VMCS_HOST_DS_SELECTOR, AsmReadDs()); VmxWrite(VMCS_HOST_FS_SELECTOR, AsmReadFs()); VmxWrite(VMCS_HOST_GS_SELECTOR, AsmReadGs()); VmxWrite(VMCS_HOST_TR_SELECTOR, AsmReadTr()); /* 64-Bit Host-State Fields */ VmxWrite(VMCS_HOST_EFER, __readmsr(IA32_EFER)); /* 32-Bit Host-State Field */ VmxWrite(VMCS_HOST_SYSENTER_CS, __readmsr(IA32_SYSENTER_CS)); /* Natural-Width Host-State Fields */ VmxWrite(VMCS_HOST_CR0, newCr0.Flags); VmxWrite(VMCS_HOST_CR3, GetHostCr3().Flags); VmxWrite(VMCS_HOST_CR4, newCr4.Flags); VmxWrite(VMCS_HOST_FS_BASE, __readmsr((IA32_MSR_ADDRESS)IA32_FS_BASE)); VmxWrite(VMCS_HOST_GS_BASE, __readmsr((IA32_MSR_ADDRESS)IA32_GS_BASE)); VmxWrite(VMCS_HOST_TR_BASE, GetSegmentBase(gdtr.BaseAddress, AsmReadTr())); VmxWrite(VMCS_HOST_GDTR_BASE, gdtr.BaseAddress); VmxWrite(VMCS_HOST_IDTR_BASE, GetHostIdtr()->BaseAddress); VmxWrite(VMCS_HOST_SYSENTER_ESP, __readmsr(IA32_SYSENTER_ESP)); VmxWrite(VMCS_HOST_SYSENTER_EIP, __readmsr(IA32_SYSENTER_EIP)); VmxWrite(VMCS_HOST_RSP, hypervisorStackPointer); VmxWrite(VMCS_HOST_RIP, (UINT64)AsmHypervisorEntryPoint); // // "Use VMWRITEs to set up the various VM-exit control fields, VM-entry // control fields, and VM-execution control fields in the VMCS." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // /* 16-Bit Control Field */ VmxWrite(VMCS_CTRL_VIRTUAL_PROCESSOR_IDENTIFIER, 1); /* 64-Bit Control Fields */ VmxWrite(VMCS_CTRL_MSR_BITMAP_ADDRESS, GetPhysicalAddress(&SharedProcessorContext->MsrBitmaps)); VmxWrite(VMCS_CTRL_EPT_POINTER, ProcessorContext->EptContext.EptPointer.Flags); /* 32-Bit Control Fields */ VmxWrite(VMCS_CTRL_EXCEPTION_BITMAP, exceptionBitmap); VmxWrite(VMCS_CTRL_PIN_BASED_VM_EXECUTION_CONTROLS, pinBasedControls.Flags); VmxWrite(VMCS_CTRL_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, primaryProcBasedControls.Flags); VmxWrite(VMCS_CTRL_VMEXIT_CONTROLS, vmExitControls.Flags); VmxWrite(VMCS_CTRL_VMENTRY_CONTROLS, vmEntryControls.Flags); VmxWrite(VMCS_CTRL_SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, secondaryProcBasedControls.Flags); /* Natural-Width Control Fields */ VmxWrite(VMCS_CTRL_CR0_GUEST_HOST_MASK, CR0_NUMERIC_ERROR_FLAG | CR0_PAGING_ENABLE_FLAG); VmxWrite(VMCS_CTRL_CR0_READ_SHADOW, newCr0.Flags); VmxWrite(VMCS_CTRL_CR4_GUEST_HOST_MASK, CR4_VMX_ENABLE_FLAG); VmxWrite(VMCS_CTRL_CR4_READ_SHADOW, newCr4.Flags); // // "Use VMWRITE to initialize various guest-state area fields in the working // VMCS." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // /* 16-Bit Guest-State Fields */ VmxWrite(VMCS_GUEST_ES_SELECTOR, AsmReadEs()); VmxWrite(VMCS_GUEST_CS_SELECTOR, AsmReadCs()); VmxWrite(VMCS_GUEST_SS_SELECTOR, AsmReadSs()); VmxWrite(VMCS_GUEST_DS_SELECTOR, AsmReadDs()); VmxWrite(VMCS_GUEST_FS_SELECTOR, AsmReadFs()); VmxWrite(VMCS_GUEST_GS_SELECTOR, AsmReadGs()); VmxWrite(VMCS_GUEST_LDTR_SELECTOR, AsmReadLdtr()); VmxWrite(VMCS_GUEST_TR_SELECTOR, AsmReadTr()); /* 64-Bit Guest-State Fields */ // // "If the “VMCS shadowing” VM-execution control is 1, (...). Otherwise, // software should set this field to FFFFFFFF_FFFFFFFFH to avoid VM-entry // failures." // See: 24.4.2 Guest Non-Register State // VmxWrite(VMCS_GUEST_VMCS_LINK_POINTER, MAXUINT64); VmxWrite(VMCS_GUEST_EFER, __readmsr(IA32_EFER)); /* 32-Bit Guest-State Fields */ VmxWrite(VMCS_GUEST_ES_LIMIT, __segmentlimit(AsmReadEs())); VmxWrite(VMCS_GUEST_CS_LIMIT, __segmentlimit(AsmReadCs())); VmxWrite(VMCS_GUEST_SS_LIMIT, __segmentlimit(AsmReadSs())); VmxWrite(VMCS_GUEST_DS_LIMIT, __segmentlimit(AsmReadDs())); VmxWrite(VMCS_GUEST_FS_LIMIT, __segmentlimit(AsmReadFs())); VmxWrite(VMCS_GUEST_GS_LIMIT, __segmentlimit(AsmReadGs())); VmxWrite(VMCS_GUEST_LDTR_LIMIT, __segmentlimit(AsmReadLdtr())); VmxWrite(VMCS_GUEST_TR_LIMIT, __segmentlimit(AsmReadTr())); VmxWrite(VMCS_GUEST_GDTR_LIMIT, gdtr.Limit); VmxWrite(VMCS_GUEST_IDTR_LIMIT, idtr.Limit); VmxWrite(VMCS_GUEST_ES_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadEs())); VmxWrite(VMCS_GUEST_CS_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadCs())); VmxWrite(VMCS_GUEST_SS_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadSs())); VmxWrite(VMCS_GUEST_DS_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadDs())); VmxWrite(VMCS_GUEST_FS_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadFs())); VmxWrite(VMCS_GUEST_GS_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadGs())); VmxWrite(VMCS_GUEST_LDTR_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadLdtr())); VmxWrite(VMCS_GUEST_TR_ACCESS_RIGHTS, GetSegmentAccessRight(AsmReadTr())); VmxWrite(VMCS_GUEST_SYSENTER_CS, __readmsr(IA32_SYSENTER_CS)); /* Natural-Width Guest-State Fields */ VmxWrite(VMCS_GUEST_CR0, newCr0.Flags); VmxWrite(VMCS_GUEST_CR3, __readcr3()); VmxWrite(VMCS_GUEST_CR4, newCr4.Flags); VmxWrite(VMCS_GUEST_FS_BASE, __readmsr((IA32_MSR_ADDRESS)IA32_FS_BASE)); VmxWrite(VMCS_GUEST_GS_BASE, __readmsr((IA32_MSR_ADDRESS)IA32_GS_BASE)); VmxWrite(VMCS_GUEST_LDTR_BASE, GetSegmentBase(gdtr.BaseAddress, AsmReadLdtr())); VmxWrite(VMCS_GUEST_TR_BASE, GetSegmentBase(gdtr.BaseAddress, AsmReadTr())); VmxWrite(VMCS_GUEST_GDTR_BASE, gdtr.BaseAddress); VmxWrite(VMCS_GUEST_IDTR_BASE, idtr.BaseAddress); VmxWrite(VMCS_GUEST_RSP, ProcessorContext->GuestStackPointer); VmxWrite(VMCS_GUEST_RIP, ProcessorContext->GuestInstructionPointer); VmxWrite(VMCS_GUEST_RFLAGS, __readeflags()); VmxWrite(VMCS_GUEST_SYSENTER_ESP, __readmsr(IA32_SYSENTER_ESP)); VmxWrite(VMCS_GUEST_SYSENTER_EIP, __readmsr(IA32_SYSENTER_EIP)); // // Finally, place necessary data for hypervisor into the hypervisor stack. // ProcessorContext->HypervisorStack.u.Layout.Context.ProcessorNumber = GetCurrentProcessorNumber(); ProcessorContext->HypervisorStack.u.Layout.Context.SharedMsrBitmaps = &SharedProcessorContext->MsrBitmaps; ProcessorContext->HypervisorStack.u.Layout.Context.SharedProcessorContext = SharedProcessorContext; ProcessorContext->HypervisorStack.u.Layout.Context.EptContext = &ProcessorContext->EptContext; ProcessorContext->HypervisorStack.u.Layout.Context.MemoryAccessContext = &ProcessorContext->MemoryAccessContext; ProcessorContext->HypervisorStack.u.Layout.Context.NestedVmxContext = &ProcessorContext->NestedVmxContext; status = MV_STATUS_SUCCESS; Exit: if (MV_ERROR(status)) { if (eptInitialized != FALSE) { CleanupExtendedPageTables(&ProcessorContext->EptContext); } if (vmxOn != FALSE) { __vmx_off(); } if (ProcessorContext->OriginalGdtr.BaseAddress != 0) { CleanupGdt(&ProcessorContext->OriginalGdtr); } } return status; } /*! @brief Enables hypervisor on the current processor. @param[in,out] Context - The pointer to the shared processor context. */ MV_SECTION_PAGED static _IRQL_requires_max_(PASSIVE_LEVEL) VOID EnableHypervisor ( _Inout_ VOID* Context ) { UINT32 processorNumber; SHARED_PROCESSOR_CONTEXT* sharedProcessorContext; PER_PROCESSOR_CONTEXT* processorContext; UINT64 guestRsp; UINT64 guestRip; BOOLEAN maCtxInitialized; PAGED_CODE(); maCtxInitialized = FALSE; sharedProcessorContext = Context; processorNumber = GetCurrentProcessorNumber(); MV_ASSERT(processorNumber < sharedProcessorContext->NumberOfContexts); processorContext = &sharedProcessorContext->Contexts[processorNumber]; // // Initialize the context of API to access guest virtual address from the // host. // processorContext->Status = InitializeMemoryAccess(&processorContext->MemoryAccessContext, GetHostCr3()); if (MV_ERROR(processorContext->Status)) { LOG_ERROR("InitializeMemoryAccess failed : %08x", processorContext->Status); goto Exit; } maCtxInitialized = TRUE; // // Save the current stack and instruction pointers. When the processor // successfully runs VMLAUNCH, it sets the stack and instruction pointers to // those values since we will set them to the guest state in the VMCS as we // see in the SetupVmcs function. This means, after VMLAUNCH, the // processor starts running at right after those function calls with the same // stack pointer value again. // guestRsp = AsmGetCurrentStackPointer(); guestRip = AsmGetCurrentInstructionPointer(); // // Check whether our hypervisor is already installed. At the first time, it // should not, and so, we will enable hypervisor ending with VMLAUNCH. After // successful VMLAUNCH, the processor starts running at here. At that time, // hypervisor is already installed, so the below block is skipped and this // function returns MV_STATUS_SUCCESS. // // Comment in the below debug break if you would like to see that this place // is executed twice. // //MV_DEBUG_BREAK(); if (IsMiniVisorInstalled() == FALSE) { VMX_RESULT result; VMX_ERROR_NUMBER vmxErrorStatus; processorContext->GuestStackPointer = guestRsp; processorContext->GuestInstructionPointer = guestRip; processorContext->Status = SetupVmcs(sharedProcessorContext, processorContext); if (MV_ERROR(processorContext->Status)) { LOG_ERROR("SetupVmcs failed : %08x", processorContext->Status); goto Exit; } // // It is important to be aware of that several registers are not saved // to and loaded from VMCS on VMLAUNCH, and the current register values // are used. This means that such register values must be the same between // when VMLAUNCH is executed and when RIP and RSP are captured. Otherwise, // when RIP and RSP are loaded from VMCS with VMLAUNCH, code running // after that will operate with unexpected register values. Think the // situation when you step through a program with a Windbg and just // overwrite RIP to rerun the same code again, but without paying // attention to the general purpose registers. The code will not run // proper if one of general purpose registers used in the code has stale // values. // // We avoid this issue by trying not to change any register values // between when RIP and RSP are captured and when VMLAUNCH is executed // in a way that breaks execution after successful VMLAUNCH. That is, // 1) executing VMLAUNCH in the same function, which ensures that all // non-volatile registers remain unchanged, and 2) avoiding dependency // on volatile registers in the code path after successful VMLAUNCH // (notice that it only calls a function without parameters and return). // // "The VMM may need to initialize additional guest execution state that is // not captured in the VMCS guest state area by loading them directly on // the respective processor registers. Examples include general purpose // registers, the CR2 control register, debug registers, floating point // registers and so forth." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // // // Enable hypervisor. If it is successful, this call does not return and // the processor starts running at the above point. // // "Execute VMLAUNCH to launch the guest VM." // See: 31.6 PREPARATION AND LAUNCHING A VIRTUAL MACHINE // result = __vmx_vmlaunch(); // // We have failed to run VMLAUNCH. Retrieve and log the error number. // See: Table 30-1. VM-Instruction Error Numbers // MV_ASSERT(result != VmxResultOk); vmxErrorStatus = (result == VmxResultErrorWithStatus) ? (VMX_ERROR_NUMBER)VmxRead(VMCS_VM_INSTRUCTION_ERROR) : 0; LOG_ERROR("__vmx_vmlaunch failed : %u", vmxErrorStatus); processorContext->Status = MV_STATUS_HV_OPERATION_FAILED; CleanupExtendedPageTables(&processorContext->EptContext); __vmx_off(); CleanupGdt(&processorContext->OriginalGdtr); goto Exit; } Exit: if (MV_ERROR(processorContext->Status) && (maCtxInitialized != FALSE)) { CleanupMemoryAccess(&processorContext->MemoryAccessContext); } } /*! @brief Initializes the MSR bitmaps. @details This function clears the bitmaps to avoid VM-exits that do not require manual handling. The MSR that requires manual handling for MiniVisor is IA32_BIOS_SIGN_ID for read to prevent the guest from attempting update BIOS microcode which is not allowed. See HandleMsrAccess for more details. @param[out] Bitmaps - The pointer to the MSR bitmaps to initialize. */ MV_SECTION_PAGED static VOID InitializeMsrBitmaps ( _Out_ MSR_BITMAPS* Bitmaps ) { typedef struct _INTERCEPT_MSR_REGISTRATION { IA32_MSR_ADDRESS Msr; OPERATION_TYPE InterceptType; } INTERCEPT_MSR_REGISTRATION; static CONST INTERCEPT_MSR_REGISTRATION registrations[] = { { IA32_BIOS_SIGN_ID, OperationRead, }, }; PAGED_CODE(); RtlZeroMemory(Bitmaps, sizeof(*Bitmaps)); for (int i = 0; i < RTL_NUMBER_OF(registrations); ++i) { UpdateMsrBitmaps(Bitmaps, registrations[i].Msr, registrations[i].InterceptType, TRUE); } } /*! @brief Enables the hypervisor on the all processors. @return MV_STATUS_SUCCESS on success; otherwise, an appropriate error code. */ MV_SECTION_PAGED static _IRQL_requires_max_(PASSIVE_LEVEL) _Must_inspect_result_ MV_STATUS EnableHypervisorOnAllProcessors ( ) { MV_STATUS status; UINT32 numberOfProcessors; UINT32 allocationSize; SHARED_PROCESSOR_CONTEXT* sharedProcessorContext; BOOLEAN virtualized; PAGED_CODE(); virtualized = FALSE; // // Compute the size of shared processor contexts. The shared processor contexts // contain VT-x related data shared across all processors and as well as // a per-processor context for each processor. The per-processor context // contains VT-x related data that are specific to the processor. // // "numberOfProcessors - 1" because "sizeof(ALL_PROCESSORS_CONTEXTS)" includes // one PER_PROCESSOR_CONTEXT. // numberOfProcessors = GetActiveProcessorCount(); allocationSize = sizeof(SHARED_PROCESSOR_CONTEXT) + (sizeof(PER_PROCESSOR_CONTEXT) * (numberOfProcessors - 1)); // // Allocate the context. // sharedProcessorContext = MmAllocatePages((UINT8)BYTES_TO_PAGES(allocationSize)); if (sharedProcessorContext == NULL) { status = MV_STATUS_INSUFFICIENT_RESOURCES; goto Exit; } sharedProcessorContext->NumberOfContexts = numberOfProcessors; InitializeMsrBitmaps(&sharedProcessorContext->MsrBitmaps); // // Start virtualizing processors one-by-one. This is done by changing // thread affinity and executing callback on each processor at PASSIVE_LEVEL. // This is slow as virtualization code is not parallelized but makes debugging // MUCH easier exactly because of the reason. Production code normally use // DPC or IPI instead, so that this operation is parallelized and eliminates // any chance of race. // RunOnAllProcessors(EnableHypervisor, sharedProcessorContext); // // Assume success first, then inspect the results from each processor and // update status as necessary. // status = MV_STATUS_SUCCESS; for (UINT32 i = 0; i < sharedProcessorContext->NumberOfContexts; ++i) { if (MV_ERROR(sharedProcessorContext->Contexts[i].Status)) { // // At least one processor failed to enable hypervisor. // LOG_ERROR("EnableHypervisor on processor %lu failed : %08x", i, sharedProcessorContext->Contexts[i].Status); status = sharedProcessorContext->Contexts[i].Status; } else { // // At least one processor enabled hypervisor and so needs clean up // in case of failure. // virtualized = TRUE; LOG_INFO("EnableHypervisor on processor %lu succeeded.", i); } } Exit: if (MV_ERROR(status)) { if (virtualized != FALSE) { // // Disable hypervisor if one or more processors enabled but also the // other processor(s) failed. This takes care of freeing the // shared processor contexts. // DisableHypervisorOnAllProcessors(); } else { if (sharedProcessorContext != NULL) { MmFreePages(sharedProcessorContext); } } } return status; } _Use_decl_annotations_ MV_STATUS InitializeMiniVisor ( ) { MV_STATUS status; BOOLEAN platformInitialized; BOOLEAN memoryManagerInitialized; platformInitialized = FALSE; memoryManagerInitialized = FALSE; MV_DEBUG_BREAK(); // // Initialize platform dependent bits, including the logging facility. Until // this succeeds, only the LOG_EARLY_ERROR macro can be used for logging. // status = InitializePlatform(); if (MV_ERROR(status)) { LOG_EARLY_ERROR("InitializePlatform failed : %08x", status); goto Exit; } platformInitialized = TRUE; // // Bail out if MiniVisor is already installed. // if (IsMiniVisorInstalled() != FALSE) { LOG_INFO("MiniVisor already installed"); status = MV_STATUS_HV_OPERATION_FAILED; goto Exit; } LOG_INFO("Installing MiniVisor."); // // Compute the required memory size and initialize the memory manager with it. // // The memory manager is the component that allocates the specified size of // non-paged pool at first and uses it as a pool to process memory allocation // requests. This pre-allocation approach is required because host code // cannot call almost any kernel API including memory allocation functions. // // The page count allocated are based on the fact that EPT paging structures // for 0-1GB of physical memory addresses often need to be constructed without // large page EPT entries because of fixed MTRRs. This requires 512 pages ( // 512 EPT page tables). Add 64 pages for the rest of EPT tables, control // structures etc. // status = MmInitializeMemoryManager(GetActiveProcessorCount() * (512 + 64)); if (MV_ERROR(status)) { LOG_ERROR("MmInitializeMemoryManager failed : %08x", status); goto Exit; } memoryManagerInitialized = TRUE; // // Prepare environments for the host. This includes initialization of CR3 and // IDTR, but how those set up depend on the platform. // InitializeHostEnvironment(); // // Build the mapping of "physical address to memory type", based on the memory // type range registers (MTRRs). This information is required to set up // extended page tables. // InitializeMemoryTypeMapping(); // // Install the hypervisor. // status = EnableHypervisorOnAllProcessors(); if (MV_ERROR(status)) { LOG_ERROR("EnableHypervisorOnAllProcessors failed : %08x", status); goto Exit; } LOG_INFO("MiniVisor installed successfully."); Exit: if (MV_ERROR(status)) { if (memoryManagerInitialized != FALSE) { MmCleanupMemoryManager(); } if (platformInitialized != FALSE) { CleanupPlatform(); } } return status; } _Use_decl_annotations_ VOID CleanupMiniVisor ( ) { MV_DEBUG_BREAK(); DisableHypervisorOnAllProcessors(); MmCleanupMemoryManager(); LOG_INFO("MiniVisor uninstalled successfully."); CleanupPlatform(); }