VMX adds new bit to both exit_reason and GUEST_INTERRUPT_STATE to indicate whether VMEXIT happens in Enclave. Several instructions are also invalid or behave differently in enclave according to SDM. This patch handles those cases. Signed-off-by: Kai Huang <kai.huang@xxxxxxxxxxxxxxx> --- arch/x86/include/asm/vmx.h | 1 + arch/x86/include/uapi/asm/vmx.h | 1 + arch/x86/kvm/vmx.c | 120 +++++++++++++++++++++++++++++++++------- 3 files changed, 103 insertions(+), 19 deletions(-) diff --git a/arch/x86/include/asm/vmx.h b/arch/x86/include/asm/vmx.h index 2f24290b7f9d..ec91f68f4511 100644 --- a/arch/x86/include/asm/vmx.h +++ b/arch/x86/include/asm/vmx.h @@ -351,6 +351,7 @@ enum vmcs_field { #define GUEST_INTR_STATE_MOV_SS 0x00000002 #define GUEST_INTR_STATE_SMI 0x00000004 #define GUEST_INTR_STATE_NMI 0x00000008 +#define GUEST_INTR_STATE_ENCLAVE_INTR 0x00000010 /* GUEST_ACTIVITY_STATE flags */ #define GUEST_ACTIVITY_ACTIVE 0 diff --git a/arch/x86/include/uapi/asm/vmx.h b/arch/x86/include/uapi/asm/vmx.h index 2bcd967d5c83..6f18898c003d 100644 --- a/arch/x86/include/uapi/asm/vmx.h +++ b/arch/x86/include/uapi/asm/vmx.h @@ -26,6 +26,7 @@ #define VMX_EXIT_REASONS_FAILED_VMENTRY 0x80000000 +#define VMX_EXIT_REASON_FROM_ENCLAVE 0x08000000 #define EXIT_REASON_EXCEPTION_NMI 0 #define EXIT_REASON_EXTERNAL_INTERRUPT 1 diff --git a/arch/x86/kvm/vmx.c b/arch/x86/kvm/vmx.c index b5f37982e975..1022295ba925 100644 --- a/arch/x86/kvm/vmx.c +++ b/arch/x86/kvm/vmx.c @@ -2628,6 +2628,24 @@ static void vmx_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask) vmcs_write32(GUEST_INTERRUPTIBILITY_INFO, interruptibility); } +static bool vmx_exit_from_enclave(struct kvm_vcpu *vcpu) +{ + /* + * We have 2 bits to indicate whether VMEXIT happens from enclave -- + * bit 27 in VM_EXIT_REASON, and bit 4 in GUEST_INTERRUPTIBILITY_INFO. + * Currently use latter to check whether VMEXIT happens from enclave, + * but note that we never clear this bit therefore we assume hardware + * will clear this bit when VMEXIT happens not from enclave, which + * should be the case. + * + * We can either do this via bit 27 in VM_EXIT_REASON, by adding a bool + * in vmx and set it in vmx_handle_exit when above bit is set, and clear + * the bool right before vmentry to guest. + */ + return vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) & + GUEST_INTR_STATE_ENCLAVE_INTR ? true : false; +} + static void skip_emulated_instruction(struct kvm_vcpu *vcpu) { unsigned long rip; @@ -5457,6 +5475,25 @@ static u32 vmx_secondary_exec_control(struct vcpu_vmx *vmx) return exec_control; } +static void vmcs_set_secondary_exec_control(u32 new_ctl) +{ + /* + * These bits in the secondary execution controls field + * are dynamic, the others are mostly based on the hypervisor + * architecture and the guest's CPUID. Do not touch the + * dynamic bits. + */ + u32 mask = + SECONDARY_EXEC_SHADOW_VMCS | + SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | + SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES; + + u32 cur_ctl = vmcs_read32(SECONDARY_VM_EXEC_CONTROL); + + vmcs_write32(SECONDARY_VM_EXEC_CONTROL, + (new_ctl & ~mask) | (cur_ctl & mask)); +} + static void ept_set_mmio_spte_mask(void) { /* @@ -6305,6 +6342,12 @@ static void vmx_set_dr7(struct kvm_vcpu *vcpu, unsigned long val) static int handle_cpuid(struct kvm_vcpu *vcpu) { + /* CPUID is invalid in enclave */ + if (vmx_exit_from_enclave(vcpu)) { + kvm_inject_gp(vcpu, 0); + return 1; + } + return kvm_emulate_cpuid(vcpu); } @@ -6378,6 +6421,16 @@ static int handle_vmcall(struct kvm_vcpu *vcpu) static int handle_invd(struct kvm_vcpu *vcpu) { + /* + * SDM 39.6.5 INVD Handling when Enclaves Are Enabled. + * + * Spec says INVD causes #GP if EPC is enabled. + */ + if (vmx_exit_from_enclave(vcpu)) { + kvm_inject_gp(vcpu, 0); + return 1; + } + return emulate_instruction(vcpu, 0) == EMULATE_DONE; } @@ -6399,6 +6452,18 @@ static int handle_rdpmc(struct kvm_vcpu *vcpu) static int handle_wbinvd(struct kvm_vcpu *vcpu) { + /* + * SDM 39.6.5 INVD Handling when Enclaves Are Enabled. + * + * Spec says INVD causes #GP if EPC is enabled. + * + * FIXME: Does this also apply to WBINVD? + */ + if (vmx_exit_from_enclave(vcpu)) { + kvm_inject_gp(vcpu, 0); + return 1; + } + return kvm_emulate_wbinvd(vcpu); } @@ -6977,6 +7042,31 @@ static __exit void hardware_unsetup(void) */ static int handle_pause(struct kvm_vcpu *vcpu) { + /* + * SDM 39.6.3 PAUSE Instruction. + * + * SDM suggests, if VMEXIT caused by 'PAUSE-loop exiting', VMM should + * disable 'PAUSE-loop exiting' so PAUSE can be executed in Enclave + * again without further PAUSE-looping VMEXIT. + * + * SDM suggests, if VMEXIT caused by 'PAUSE exiting', VMM should disable + * 'PAUSE exiting' so PAUSE can be executed in Enclave again without + * further PAUSE VMEXIT. + */ + if (vmx_exit_from_enclave(vcpu)) { + u32 exec_ctl, secondary_exec_ctl; + + exec_ctl = vmx_exec_control(to_vmx(vcpu)); + exec_ctl &= ~CPU_BASED_PAUSE_EXITING; + vmcs_write32(CPU_BASED_VM_EXEC_CONTROL, exec_ctl); + + secondary_exec_ctl = vmx_secondary_exec_control(to_vmx(vcpu)); + secondary_exec_ctl &= ~SECONDARY_EXEC_PAUSE_LOOP_EXITING; + vmcs_set_secondary_exec_control(secondary_exec_ctl); + + return 1; + } + if (ple_gap) grow_ple_window(vcpu); @@ -8876,6 +8966,17 @@ static int vmx_handle_exit(struct kvm_vcpu *vcpu) return 0; } + /* Bit 27 of exit_reason will be set if VMEXT is from SGX enclave. */ + if (exit_reason & VMX_EXIT_REASON_FROM_ENCLAVE) { + /* + * Need to clear bit 27 otherwise further check of calling + * kvm_vmx_exit_handlers would fail. We rely on bit 4 of + * GUEST_INTERRUPTIBILITY_INFO to determine whether VMEXIT + * is from enclave in the future. + */ + exit_reason &= ~VMX_EXIT_REASON_FROM_ENCLAVE; + } + /* * Note: * Do not try to fix EXIT_REASON_EPT_MISCONFIG if it caused by @@ -9768,25 +9869,6 @@ static int vmx_get_lpage_level(void) return PT_PDPE_LEVEL; } -static void vmcs_set_secondary_exec_control(u32 new_ctl) -{ - /* - * These bits in the secondary execution controls field - * are dynamic, the others are mostly based on the hypervisor - * architecture and the guest's CPUID. Do not touch the - * dynamic bits. - */ - u32 mask = - SECONDARY_EXEC_SHADOW_VMCS | - SECONDARY_EXEC_VIRTUALIZE_X2APIC_MODE | - SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES; - - u32 cur_ctl = vmcs_read32(SECONDARY_VM_EXEC_CONTROL); - - vmcs_write32(SECONDARY_VM_EXEC_CONTROL, - (new_ctl & ~mask) | (cur_ctl & mask)); -} - /* * Generate MSR_IA32_VMX_CR{0,4}_FIXED1 according to CPUID. Only set bits * (indicating "allowed-1") if they are supported in the guest's CPUID. -- 2.11.0