Skip to content

Commit e366f92

Browse files
tlendackybonzini
authored andcommitted
KVM: SEV: Support SEV-SNP AP Creation NAE event
Add support for the SEV-SNP AP Creation NAE event. This allows SEV-SNP guests to alter the register state of the APs on their own. This allows the guest a way of simulating INIT-SIPI. A new event, KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, is created and used so as to avoid updating the VMSA pointer while the vCPU is running. For CREATE The guest supplies the GPA of the VMSA to be used for the vCPU with the specified APIC ID. The GPA is saved in the svm struct of the target vCPU, the KVM_REQ_UPDATE_PROTECTED_GUEST_STATE event is added to the vCPU and then the vCPU is kicked. For CREATE_ON_INIT: The guest supplies the GPA of the VMSA to be used for the vCPU with the specified APIC ID the next time an INIT is performed. The GPA is saved in the svm struct of the target vCPU. For DESTROY: The guest indicates it wishes to stop the vCPU. The GPA is cleared from the svm struct, the KVM_REQ_UPDATE_PROTECTED_GUEST_STATE event is added to vCPU and then the vCPU is kicked. The KVM_REQ_UPDATE_PROTECTED_GUEST_STATE event handler will be invoked as a result of the event or as a result of an INIT. If a new VMSA is to be installed, the VMSA guest page is set as the VMSA in the vCPU VMCB and the vCPU state is set to KVM_MP_STATE_RUNNABLE. If a new VMSA is not to be installed, the VMSA is cleared in the vCPU VMCB and the vCPU state is set to KVM_MP_STATE_HALTED to prevent it from being run. Signed-off-by: Tom Lendacky <thomas.lendacky@amd.com> Co-developed-by: Michael Roth <michael.roth@amd.com> Signed-off-by: Michael Roth <michael.roth@amd.com> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com> Signed-off-by: Ashish Kalra <ashish.kalra@amd.com> Message-ID: <20240501085210.2213060-13-michael.roth@amd.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent c63cf13 commit e366f92

6 files changed

Lines changed: 266 additions & 3 deletions

File tree

arch/x86/include/asm/kvm_host.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
KVM_ARCH_REQ_FLAGS(31, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
122122
#define KVM_REQ_HV_TLB_FLUSH \
123123
KVM_ARCH_REQ_FLAGS(32, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
124+
#define KVM_REQ_UPDATE_PROTECTED_GUEST_STATE KVM_ARCH_REQ(34)
124125

125126
#define CR0_RESERVED_BITS \
126127
(~(unsigned long)(X86_CR0_PE | X86_CR0_MP | X86_CR0_EM | X86_CR0_TS \

arch/x86/include/asm/svm.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,14 @@ static_assert((X2AVIC_MAX_PHYSICAL_ID & AVIC_PHYSICAL_MAX_INDEX_MASK) == X2AVIC_
286286
#define AVIC_HPA_MASK ~((0xFFFULL << 52) | 0xFFF)
287287

288288
#define SVM_SEV_FEAT_SNP_ACTIVE BIT(0)
289+
#define SVM_SEV_FEAT_RESTRICTED_INJECTION BIT(3)
290+
#define SVM_SEV_FEAT_ALTERNATE_INJECTION BIT(4)
289291
#define SVM_SEV_FEAT_DEBUG_SWAP BIT(5)
290292

293+
#define SVM_SEV_FEAT_INT_INJ_MODES \
294+
(SVM_SEV_FEAT_RESTRICTED_INJECTION | \
295+
SVM_SEV_FEAT_ALTERNATE_INJECTION)
296+
291297
struct vmcb_seg {
292298
u16 selector;
293299
u16 attrib;

arch/x86/kvm/svm/sev.c

Lines changed: 229 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
#define GHCB_VERSION_DEFAULT 2ULL
3939
#define GHCB_VERSION_MIN 1ULL
4040

41-
#define GHCB_HV_FT_SUPPORTED GHCB_HV_FT_SNP
41+
#define GHCB_HV_FT_SUPPORTED (GHCB_HV_FT_SNP | GHCB_HV_FT_SNP_AP_CREATION)
4242

4343
/* enable/disable SEV support */
4444
static bool sev_enabled = true;
@@ -3267,6 +3267,13 @@ static int sev_es_validate_vmgexit(struct vcpu_svm *svm)
32673267
if (!kvm_ghcb_sw_scratch_is_valid(svm))
32683268
goto vmgexit_err;
32693269
break;
3270+
case SVM_VMGEXIT_AP_CREATION:
3271+
if (!sev_snp_guest(vcpu->kvm))
3272+
goto vmgexit_err;
3273+
if (lower_32_bits(control->exit_info_1) != SVM_VMGEXIT_AP_DESTROY)
3274+
if (!kvm_ghcb_rax_is_valid(svm))
3275+
goto vmgexit_err;
3276+
break;
32703277
case SVM_VMGEXIT_NMI_COMPLETE:
32713278
case SVM_VMGEXIT_AP_HLT_LOOP:
32723279
case SVM_VMGEXIT_AP_JUMP_TABLE:
@@ -3701,6 +3708,205 @@ static int snp_begin_psc(struct vcpu_svm *svm, struct psc_buffer *psc)
37013708
unreachable();
37023709
}
37033710

3711+
static int __sev_snp_update_protected_guest_state(struct kvm_vcpu *vcpu)
3712+
{
3713+
struct vcpu_svm *svm = to_svm(vcpu);
3714+
3715+
WARN_ON(!mutex_is_locked(&svm->sev_es.snp_vmsa_mutex));
3716+
3717+
/* Mark the vCPU as offline and not runnable */
3718+
vcpu->arch.pv.pv_unhalted = false;
3719+
vcpu->arch.mp_state = KVM_MP_STATE_HALTED;
3720+
3721+
/* Clear use of the VMSA */
3722+
svm->vmcb->control.vmsa_pa = INVALID_PAGE;
3723+
3724+
if (VALID_PAGE(svm->sev_es.snp_vmsa_gpa)) {
3725+
gfn_t gfn = gpa_to_gfn(svm->sev_es.snp_vmsa_gpa);
3726+
struct kvm_memory_slot *slot;
3727+
kvm_pfn_t pfn;
3728+
3729+
slot = gfn_to_memslot(vcpu->kvm, gfn);
3730+
if (!slot)
3731+
return -EINVAL;
3732+
3733+
/*
3734+
* The new VMSA will be private memory guest memory, so
3735+
* retrieve the PFN from the gmem backend.
3736+
*/
3737+
if (kvm_gmem_get_pfn(vcpu->kvm, slot, gfn, &pfn, NULL))
3738+
return -EINVAL;
3739+
3740+
/*
3741+
* From this point forward, the VMSA will always be a
3742+
* guest-mapped page rather than the initial one allocated
3743+
* by KVM in svm->sev_es.vmsa. In theory, svm->sev_es.vmsa
3744+
* could be free'd and cleaned up here, but that involves
3745+
* cleanups like wbinvd_on_all_cpus() which would ideally
3746+
* be handled during teardown rather than guest boot.
3747+
* Deferring that also allows the existing logic for SEV-ES
3748+
* VMSAs to be re-used with minimal SNP-specific changes.
3749+
*/
3750+
svm->sev_es.snp_has_guest_vmsa = true;
3751+
3752+
/* Use the new VMSA */
3753+
svm->vmcb->control.vmsa_pa = pfn_to_hpa(pfn);
3754+
3755+
/* Mark the vCPU as runnable */
3756+
vcpu->arch.pv.pv_unhalted = false;
3757+
vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
3758+
3759+
svm->sev_es.snp_vmsa_gpa = INVALID_PAGE;
3760+
3761+
/*
3762+
* gmem pages aren't currently migratable, but if this ever
3763+
* changes then care should be taken to ensure
3764+
* svm->sev_es.vmsa is pinned through some other means.
3765+
*/
3766+
kvm_release_pfn_clean(pfn);
3767+
}
3768+
3769+
/*
3770+
* When replacing the VMSA during SEV-SNP AP creation,
3771+
* mark the VMCB dirty so that full state is always reloaded.
3772+
*/
3773+
vmcb_mark_all_dirty(svm->vmcb);
3774+
3775+
return 0;
3776+
}
3777+
3778+
/*
3779+
* Invoked as part of svm_vcpu_reset() processing of an init event.
3780+
*/
3781+
void sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu)
3782+
{
3783+
struct vcpu_svm *svm = to_svm(vcpu);
3784+
int ret;
3785+
3786+
if (!sev_snp_guest(vcpu->kvm))
3787+
return;
3788+
3789+
mutex_lock(&svm->sev_es.snp_vmsa_mutex);
3790+
3791+
if (!svm->sev_es.snp_ap_waiting_for_reset)
3792+
goto unlock;
3793+
3794+
svm->sev_es.snp_ap_waiting_for_reset = false;
3795+
3796+
ret = __sev_snp_update_protected_guest_state(vcpu);
3797+
if (ret)
3798+
vcpu_unimpl(vcpu, "snp: AP state update on init failed\n");
3799+
3800+
unlock:
3801+
mutex_unlock(&svm->sev_es.snp_vmsa_mutex);
3802+
}
3803+
3804+
static int sev_snp_ap_creation(struct vcpu_svm *svm)
3805+
{
3806+
struct kvm_sev_info *sev = &to_kvm_svm(svm->vcpu.kvm)->sev_info;
3807+
struct kvm_vcpu *vcpu = &svm->vcpu;
3808+
struct kvm_vcpu *target_vcpu;
3809+
struct vcpu_svm *target_svm;
3810+
unsigned int request;
3811+
unsigned int apic_id;
3812+
bool kick;
3813+
int ret;
3814+
3815+
request = lower_32_bits(svm->vmcb->control.exit_info_1);
3816+
apic_id = upper_32_bits(svm->vmcb->control.exit_info_1);
3817+
3818+
/* Validate the APIC ID */
3819+
target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, apic_id);
3820+
if (!target_vcpu) {
3821+
vcpu_unimpl(vcpu, "vmgexit: invalid AP APIC ID [%#x] from guest\n",
3822+
apic_id);
3823+
return -EINVAL;
3824+
}
3825+
3826+
ret = 0;
3827+
3828+
target_svm = to_svm(target_vcpu);
3829+
3830+
/*
3831+
* The target vCPU is valid, so the vCPU will be kicked unless the
3832+
* request is for CREATE_ON_INIT. For any errors at this stage, the
3833+
* kick will place the vCPU in an non-runnable state.
3834+
*/
3835+
kick = true;
3836+
3837+
mutex_lock(&target_svm->sev_es.snp_vmsa_mutex);
3838+
3839+
target_svm->sev_es.snp_vmsa_gpa = INVALID_PAGE;
3840+
target_svm->sev_es.snp_ap_waiting_for_reset = true;
3841+
3842+
/* Interrupt injection mode shouldn't change for AP creation */
3843+
if (request < SVM_VMGEXIT_AP_DESTROY) {
3844+
u64 sev_features;
3845+
3846+
sev_features = vcpu->arch.regs[VCPU_REGS_RAX];
3847+
sev_features ^= sev->vmsa_features;
3848+
3849+
if (sev_features & SVM_SEV_FEAT_INT_INJ_MODES) {
3850+
vcpu_unimpl(vcpu, "vmgexit: invalid AP injection mode [%#lx] from guest\n",
3851+
vcpu->arch.regs[VCPU_REGS_RAX]);
3852+
ret = -EINVAL;
3853+
goto out;
3854+
}
3855+
}
3856+
3857+
switch (request) {
3858+
case SVM_VMGEXIT_AP_CREATE_ON_INIT:
3859+
kick = false;
3860+
fallthrough;
3861+
case SVM_VMGEXIT_AP_CREATE:
3862+
if (!page_address_valid(vcpu, svm->vmcb->control.exit_info_2)) {
3863+
vcpu_unimpl(vcpu, "vmgexit: invalid AP VMSA address [%#llx] from guest\n",
3864+
svm->vmcb->control.exit_info_2);
3865+
ret = -EINVAL;
3866+
goto out;
3867+
}
3868+
3869+
/*
3870+
* Malicious guest can RMPADJUST a large page into VMSA which
3871+
* will hit the SNP erratum where the CPU will incorrectly signal
3872+
* an RMP violation #PF if a hugepage collides with the RMP entry
3873+
* of VMSA page, reject the AP CREATE request if VMSA address from
3874+
* guest is 2M aligned.
3875+
*/
3876+
if (IS_ALIGNED(svm->vmcb->control.exit_info_2, PMD_SIZE)) {
3877+
vcpu_unimpl(vcpu,
3878+
"vmgexit: AP VMSA address [%llx] from guest is unsafe as it is 2M aligned\n",
3879+
svm->vmcb->control.exit_info_2);
3880+
ret = -EINVAL;
3881+
goto out;
3882+
}
3883+
3884+
target_svm->sev_es.snp_vmsa_gpa = svm->vmcb->control.exit_info_2;
3885+
break;
3886+
case SVM_VMGEXIT_AP_DESTROY:
3887+
break;
3888+
default:
3889+
vcpu_unimpl(vcpu, "vmgexit: invalid AP creation request [%#x] from guest\n",
3890+
request);
3891+
ret = -EINVAL;
3892+
break;
3893+
}
3894+
3895+
out:
3896+
if (kick) {
3897+
kvm_make_request(KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, target_vcpu);
3898+
3899+
if (target_vcpu->arch.mp_state == KVM_MP_STATE_UNINITIALIZED)
3900+
kvm_make_request(KVM_REQ_UNBLOCK, target_vcpu);
3901+
3902+
kvm_vcpu_kick(target_vcpu);
3903+
}
3904+
3905+
mutex_unlock(&target_svm->sev_es.snp_vmsa_mutex);
3906+
3907+
return ret;
3908+
}
3909+
37043910
static int sev_handle_vmgexit_msr_protocol(struct vcpu_svm *svm)
37053911
{
37063912
struct vmcb_control_area *control = &svm->vmcb->control;
@@ -3966,6 +4172,15 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
39664172

39674173
ret = snp_begin_psc(svm, svm->sev_es.ghcb_sa);
39684174
break;
4175+
case SVM_VMGEXIT_AP_CREATION:
4176+
ret = sev_snp_ap_creation(svm);
4177+
if (ret) {
4178+
ghcb_set_sw_exit_info_1(svm->sev_es.ghcb, 2);
4179+
ghcb_set_sw_exit_info_2(svm->sev_es.ghcb, GHCB_ERR_INVALID_INPUT);
4180+
}
4181+
4182+
ret = 1;
4183+
break;
39694184
case SVM_VMGEXIT_UNSUPPORTED_EVENT:
39704185
vcpu_unimpl(vcpu,
39714186
"vmgexit: unsupported event - exit_info_1=%#llx, exit_info_2=%#llx\n",
@@ -4060,7 +4275,7 @@ static void sev_es_init_vmcb(struct vcpu_svm *svm)
40604275
* the VMSA will be NULL if this vCPU is the destination for intrahost
40614276
* migration, and will be copied later.
40624277
*/
4063-
if (svm->sev_es.vmsa)
4278+
if (svm->sev_es.vmsa && !svm->sev_es.snp_has_guest_vmsa)
40644279
svm->vmcb->control.vmsa_pa = __pa(svm->sev_es.vmsa);
40654280

40664281
/* Can't intercept CR register access, HV can't modify CR registers */
@@ -4136,6 +4351,8 @@ void sev_es_vcpu_reset(struct vcpu_svm *svm)
41364351
set_ghcb_msr(svm, GHCB_MSR_SEV_INFO((__u64)sev->ghcb_version,
41374352
GHCB_VERSION_MIN,
41384353
sev_enc_bit));
4354+
4355+
mutex_init(&svm->sev_es.snp_vmsa_mutex);
41394356
}
41404357

41414358
void sev_es_prepare_switch_to_guest(struct vcpu_svm *svm, struct sev_es_save_area *hostsa)
@@ -4247,6 +4464,16 @@ struct page *snp_safe_alloc_page(struct kvm_vcpu *vcpu)
42474464
return p;
42484465
}
42494466

4467+
void sev_vcpu_unblocking(struct kvm_vcpu *vcpu)
4468+
{
4469+
if (!sev_snp_guest(vcpu->kvm))
4470+
return;
4471+
4472+
if (kvm_test_request(KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, vcpu) &&
4473+
vcpu->arch.mp_state == KVM_MP_STATE_UNINITIALIZED)
4474+
vcpu->arch.mp_state = KVM_MP_STATE_RUNNABLE;
4475+
}
4476+
42504477
void sev_handle_rmp_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code)
42514478
{
42524479
struct kvm_memory_slot *slot;

arch/x86/kvm/svm/svm.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,9 @@ static void svm_vcpu_reset(struct kvm_vcpu *vcpu, bool init_event)
13981398
svm->spec_ctrl = 0;
13991399
svm->virt_spec_ctrl = 0;
14001400

1401+
if (init_event)
1402+
sev_snp_init_protected_guest_state(vcpu);
1403+
14011404
init_vmcb(vcpu);
14021405

14031406
if (!init_event)
@@ -4940,6 +4943,12 @@ static void *svm_alloc_apic_backing_page(struct kvm_vcpu *vcpu)
49404943
return page_address(page);
49414944
}
49424945

4946+
static void svm_vcpu_unblocking(struct kvm_vcpu *vcpu)
4947+
{
4948+
sev_vcpu_unblocking(vcpu);
4949+
avic_vcpu_unblocking(vcpu);
4950+
}
4951+
49434952
static struct kvm_x86_ops svm_x86_ops __initdata = {
49444953
.name = KBUILD_MODNAME,
49454954

@@ -4962,7 +4971,7 @@ static struct kvm_x86_ops svm_x86_ops __initdata = {
49624971
.vcpu_load = svm_vcpu_load,
49634972
.vcpu_put = svm_vcpu_put,
49644973
.vcpu_blocking = avic_vcpu_blocking,
4965-
.vcpu_unblocking = avic_vcpu_unblocking,
4974+
.vcpu_unblocking = svm_vcpu_unblocking,
49664975

49674976
.update_exception_bitmap = svm_update_exception_bitmap,
49684977
.get_msr_feature = svm_get_msr_feature,

arch/x86/kvm/svm/svm.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ struct vcpu_sev_es_state {
216216
bool psc_2m;
217217

218218
u64 ghcb_registered_gpa;
219+
220+
struct mutex snp_vmsa_mutex; /* Used to handle concurrent updates of VMSA. */
221+
gpa_t snp_vmsa_gpa;
222+
bool snp_ap_waiting_for_reset;
223+
bool snp_has_guest_vmsa;
219224
};
220225

221226
struct vcpu_svm {
@@ -729,6 +734,8 @@ int sev_cpu_init(struct svm_cpu_data *sd);
729734
int sev_dev_get_attr(u32 group, u64 attr, u64 *val);
730735
extern unsigned int max_sev_asid;
731736
void sev_handle_rmp_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code);
737+
void sev_vcpu_unblocking(struct kvm_vcpu *vcpu);
738+
void sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu);
732739
#else
733740
static inline struct page *snp_safe_alloc_page(struct kvm_vcpu *vcpu) {
734741
return alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
@@ -743,6 +750,8 @@ static inline int sev_cpu_init(struct svm_cpu_data *sd) { return 0; }
743750
static inline int sev_dev_get_attr(u32 group, u64 attr, u64 *val) { return -ENXIO; }
744751
#define max_sev_asid 0
745752
static inline void sev_handle_rmp_fault(struct kvm_vcpu *vcpu, gpa_t gpa, u64 error_code) {}
753+
static inline void sev_vcpu_unblocking(struct kvm_vcpu *vcpu) {}
754+
static inline void sev_snp_init_protected_guest_state(struct kvm_vcpu *vcpu) {}
746755

747756
#endif
748757

arch/x86/kvm/x86.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10939,6 +10939,14 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
1093910939

1094010940
if (kvm_check_request(KVM_REQ_UPDATE_CPU_DIRTY_LOGGING, vcpu))
1094110941
static_call(kvm_x86_update_cpu_dirty_logging)(vcpu);
10942+
10943+
if (kvm_check_request(KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, vcpu)) {
10944+
kvm_vcpu_reset(vcpu, true);
10945+
if (vcpu->arch.mp_state != KVM_MP_STATE_RUNNABLE) {
10946+
r = 1;
10947+
goto out;
10948+
}
10949+
}
1094210950
}
1094310951

1094410952
if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win ||
@@ -13146,6 +13154,9 @@ static inline bool kvm_vcpu_has_events(struct kvm_vcpu *vcpu)
1314613154
if (kvm_test_request(KVM_REQ_PMI, vcpu))
1314713155
return true;
1314813156

13157+
if (kvm_test_request(KVM_REQ_UPDATE_PROTECTED_GUEST_STATE, vcpu))
13158+
return true;
13159+
1314913160
if (kvm_arch_interrupt_allowed(vcpu) &&
1315013161
(kvm_cpu_has_interrupt(vcpu) ||
1315113162
kvm_guest_apic_has_interrupt(vcpu)))

0 commit comments

Comments
 (0)