Skip to content

Commit

Permalink
KVM: VMX: Add ENCLS[EINIT] handler to support SGX Launch Control (LC)
Browse files Browse the repository at this point in the history
Add a VM-Exit handler to trap-and-execute EINIT when SGX LC is enabled
in the host.  When SGX LC is enabled, the host kernel may rewrite the
hardware values at will, e.g. to launch enclaves with different signers,
thus KVM needs to intercept EINIT to ensure it is executed with the
correct LE hash (even if the guest sees a hardwired hash).

Switching the LE hash MSRs on VM-Enter/VM-Exit is not a viable option as
writing the MSRs is prohibitively expensive, e.g. on SKL hardware each
WRMSR is ~400 cycles.  And because EINIT takes tens of thousands of
cycles to execute, the ~1500 cycle overhead to trap-and-execute EINIT is
unlikely to be noticed by the guest, let alone impact its overall SGX
performance.

Signed-off-by: Sean Christopherson <sean.j.christopherson@intel.com>
Signed-off-by: Kai Huang <kai.huang@intel.com>
Message-Id: <57c92fa4d2083eb3be9e6355e3882fc90cffea87.1618196135.git.kai.huang@intel.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
  • Loading branch information
Sean Christopherson authored and Paolo Bonzini committed Apr 20, 2021
1 parent 8f10244 commit b6f084c
Showing 1 changed file with 64 additions and 0 deletions.
64 changes: 64 additions & 0 deletions arch/x86/kvm/vmx/sgx.c
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,68 @@ static int handle_encls_ecreate(struct kvm_vcpu *vcpu)
return r;
}

static int handle_encls_einit(struct kvm_vcpu *vcpu)
{
unsigned long sig_hva, secs_hva, token_hva, rflags;
struct vcpu_vmx *vmx = to_vmx(vcpu);
gva_t sig_gva, secs_gva, token_gva;
gpa_t sig_gpa, secs_gpa, token_gpa;
int ret, trapnr;

if (sgx_get_encls_gva(vcpu, kvm_rbx_read(vcpu), 1808, 4096, &sig_gva) ||
sgx_get_encls_gva(vcpu, kvm_rcx_read(vcpu), 4096, 4096, &secs_gva) ||
sgx_get_encls_gva(vcpu, kvm_rdx_read(vcpu), 304, 512, &token_gva))
return 1;

/*
* Translate the SIGSTRUCT, SECS and TOKEN pointers from GVA to GPA.
* Resume the guest on failure to inject a #PF.
*/
if (sgx_gva_to_gpa(vcpu, sig_gva, false, &sig_gpa) ||
sgx_gva_to_gpa(vcpu, secs_gva, true, &secs_gpa) ||
sgx_gva_to_gpa(vcpu, token_gva, false, &token_gpa))
return 1;

/*
* ...and then to HVA. The order of accesses isn't architectural, i.e.
* KVM doesn't have to fully process one address at a time. Exit to
* userspace if a GPA is invalid. Note, all structures are aligned and
* cannot split pages.
*/
if (sgx_gpa_to_hva(vcpu, sig_gpa, &sig_hva) ||
sgx_gpa_to_hva(vcpu, secs_gpa, &secs_hva) ||
sgx_gpa_to_hva(vcpu, token_gpa, &token_hva))
return 0;

ret = sgx_virt_einit((void __user *)sig_hva, (void __user *)token_hva,
(void __user *)secs_hva,
vmx->msr_ia32_sgxlepubkeyhash, &trapnr);

if (ret == -EFAULT)
return sgx_inject_fault(vcpu, secs_gva, trapnr);

/*
* sgx_virt_einit() returns -EINVAL when access_ok() fails on @sig_hva,
* @token_hva or @secs_hva. This should never happen as KVM checks host
* addresses at memslot creation. sgx_virt_einit() has already warned
* in this case, so just return.
*/
if (ret < 0)
return ret;

rflags = vmx_get_rflags(vcpu) & ~(X86_EFLAGS_CF | X86_EFLAGS_PF |
X86_EFLAGS_AF | X86_EFLAGS_SF |
X86_EFLAGS_OF);
if (ret)
rflags |= X86_EFLAGS_ZF;
else
rflags &= ~X86_EFLAGS_ZF;
vmx_set_rflags(vcpu, rflags);

kvm_rax_write(vcpu, ret);
return kvm_skip_emulated_instruction(vcpu);
}

static inline bool encls_leaf_enabled_in_guest(struct kvm_vcpu *vcpu, u32 leaf)
{
if (!enable_sgx || !guest_cpuid_has(vcpu, X86_FEATURE_SGX))
Expand Down Expand Up @@ -319,6 +381,8 @@ int handle_encls(struct kvm_vcpu *vcpu)
} else {
if (leaf == ECREATE)
return handle_encls_ecreate(vcpu);
if (leaf == EINIT)
return handle_encls_einit(vcpu);
WARN(1, "KVM: unexpected exit on ENCLS[%u]", leaf);
vcpu->run->exit_reason = KVM_EXIT_UNKNOWN;
vcpu->run->hw.hardware_exit_reason = EXIT_REASON_ENCLS;
Expand Down

0 comments on commit b6f084c

Please sign in to comment.