Skip to contentSkip to conversation

KVM: nSVM: ondemand nested state allocation

PATCH Superseded Maxim Levitsky 3 participants 10 messages Sep 17, 2020 – Sep 21, 2020

Subsystems (3)

v4 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

v4 cover KVM: nSVM: ondemand nested state allocation

This is new version of ondemand nested state allocation.

In this version I dropped the changes to set_efer and instead added a new request KVM_REQ_OUT_OF_MEMORY which makes the kvm exit to userspace with KVM_EXIT_INTERNAL_ERROR

This request is used in (unlikely) case of memory allocation failure.

Maxim Levitsky (2):
KVM: add request KVM_REQ_OUT_OF_MEMORY
KVM: nSVM: implement ondemand allocation of the nested state

 arch/x86/kvm/svm/nested.c | 42 ++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c    | 54 ++++++++++++++++++++++-----------------
 arch/x86/kvm/svm/svm.h    |  7 +++++
 arch/x86/kvm/x86.c        |  7 +++++
 include/linux/kvm_host.h  |  1 +
 5 files changed, 87 insertions(+), 24 deletions(-)
Signature · 1 line
2.26.2
#1 lore raw

PATCH v4 1/2 KVM: add request KVM_REQ_OUT_OF_MEMORY

This request will be used in rare cases when emulation can't continue due to being out of memory, to kill the current VM.

Currently only implemented for x86.

---
 arch/x86/kvm/x86.c       | 7 +++++++
 include/linux/kvm_host.h | 1 +
 2 files changed, 8 insertions(+)
arch/x86/kvm/x86.c +7 -0
index 17f4995e80a7e..ea1834dd807f9 100644
@@ -8471,6 +8471,13 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
 			kvm_vcpu_update_apicv(vcpu);
 		if (kvm_check_request(KVM_REQ_APF_READY, vcpu))
 			kvm_check_async_pf_completion(vcpu);
+
+		if (kvm_check_request(KVM_REQ_OUT_OF_MEMORY, vcpu)) {
+			vcpu->run->exit_reason = KVM_EXIT_INTERNAL_ERROR;
+			vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION;
+			r = 0;
+			goto out;
+		}
 	}
 
 	if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win) {
include/linux/kvm_host.h +1 -0
index 05e3c2fb3ef78..487c8ed2e3f88 100644
@@ -146,6 +146,7 @@ static inline bool is_error_page(struct page *page)
 #define KVM_REQ_MMU_RELOAD        (1 | KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
 #define KVM_REQ_PENDING_TIMER     2
 #define KVM_REQ_UNHALT            3
+#define KVM_REQ_OUT_OF_MEMORY     4
 #define KVM_REQUEST_ARCH_BASE     8
 
 #define KVM_ARCH_REQ_FLAGS(nr, flags) ({ \
Signature · 1 line
2.26.2
#2 lore raw

PATCH v4 2/2 KVM: nSVM: implement ondemand allocation of the nested state

In reply to Maxim Levitsky

This way we don't waste memory on VMs which don't use nesting virtualization even if it is available to them.

If allocation of nested state fails (which should happen, only when host is about to OOM anyway), use new KVM_REQ_OUT_OF_MEMORY request to shut down the guest

---
 arch/x86/kvm/svm/nested.c | 42 ++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c    | 54 ++++++++++++++++++++++-----------------
 arch/x86/kvm/svm/svm.h    |  7 +++++
 3 files changed, 79 insertions(+), 24 deletions(-)
arch/x86/kvm/svm/nested.c +42 -0
index 09417f5197410..fe119da2ef836 100644
@@ -467,6 +467,9 @@ int nested_svm_vmrun(struct vcpu_svm *svm)
 
 	vmcb12 = map.hva;
 
+	if (WARN_ON(!svm->nested.initialized))
+		return 1;
+
 	if (!nested_vmcb_checks(svm, vmcb12)) {
 		vmcb12->control.exit_code    = SVM_EXIT_ERR;
 		vmcb12->control.exit_code_hi = 0;
@@ -684,6 +687,45 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
 	return 0;
 }
 
+int svm_allocate_nested(struct vcpu_svm *svm)
+{
+	struct page *hsave_page;
+
+	if (svm->nested.initialized)
+		return 0;
+
+	hsave_page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!hsave_page)
+		goto error;
+
+	svm->nested.hsave = page_address(hsave_page);
+
+	svm->nested.msrpm = svm_vcpu_init_msrpm();
+	if (!svm->nested.msrpm)
+		goto err_free_hsave;
+
+	svm->nested.initialized = true;
+	return 0;
+err_free_hsave:
+	__free_page(hsave_page);
+error:
+	return 1;
+}
+
+void svm_free_nested(struct vcpu_svm *svm)
+{
+	if (!svm->nested.initialized)
+		return;
+
+	svm_vcpu_free_msrpm(svm->nested.msrpm);
+	svm->nested.msrpm = NULL;
+
+	__free_page(virt_to_page(svm->nested.hsave));
+	svm->nested.hsave = NULL;
+
+	svm->nested.initialized = false;
+}
+
 /*
  * Forcibly leave nested mode in order to be able to reset the VCPU later on.
  */
arch/x86/kvm/svm/svm.c +30 -24

120 lines not on this page · raw message

arch/x86/kvm/svm/svm.h +7 -0
index 45496775f0db2..3bee16e1f2e73 100644
@@ -96,6 +96,8 @@ struct svm_nested_state {
 
 	/* cache for control fields of the guest */
 	struct vmcb_control_area ctl;
+
+	bool initialized;
 };
 
 struct vcpu_svm {
@@ -338,6 +340,9 @@ static inline bool gif_set(struct vcpu_svm *svm)
 #define MSR_INVALID				0xffffffffU
 
 u32 svm_msrpm_offset(u32 msr);
+u32 *svm_vcpu_init_msrpm(void);
+void svm_vcpu_free_msrpm(u32 *msrpm);
+
 void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer);
 void svm_set_cr0(struct kvm_vcpu *vcpu, unsigned long cr0);
 int svm_set_cr4(struct kvm_vcpu *vcpu, unsigned long cr4);
@@ -379,6 +384,8 @@ static inline bool nested_exit_on_nmi(struct vcpu_svm *svm)
 int enter_svm_guest_mode(struct vcpu_svm *svm, u64 vmcb_gpa,
 			 struct vmcb *nested_vmcb);
 void svm_leave_nested(struct vcpu_svm *svm);
+void svm_free_nested(struct vcpu_svm *svm);
+int svm_allocate_nested(struct vcpu_svm *svm);
 int nested_svm_vmrun(struct vcpu_svm *svm);
 void nested_svm_vmloadsave(struct vmcb *from_vmcb, struct vmcb *to_vmcb);
 int nested_svm_vmexit(struct vcpu_svm *svm);
Signature · 1 line
2.26.2
#3 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

Quoted 32 lines · Maxim Levitsky

This way we don't waste memory on VMs which don't use nesting virtualization even if it is available to them.

If allocation of nested state fails (which should happen, only when host is about to OOM anyway), use new KVM_REQ_OUT_OF_MEMORY request to shut down the guest

---
 arch/x86/kvm/svm/nested.c | 42 ++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c    | 54 ++++++++++++++++++++++-----------------
 arch/x86/kvm/svm/svm.h    |  7 +++++
 3 files changed, 79 insertions(+), 24 deletions(-)
arch/x86/kvm/svm/nested.c +3 -0
index 09417f5197410..fe119da2ef836 100644
@@ -467,6 +467,9 @@ int nested_svm_vmrun(struct vcpu_svm *svm)
 
 	vmcb12 = map.hva;
 
+	if (WARN_ON(!svm->nested.initialized))
+		return 1;
+
 	if (!nested_vmcb_checks(svm, vmcb12)) {
 		vmcb12->control.exit_code    = SVM_EXIT_ERR;
 		vmcb12->control.exit_code_hi = 0;
@@ -684,6 +687,45 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
 	return 0;
 }
 

arch/x86/kvm/svm/nested.c int nested_svm_vmexit(struct vcpu_svm *svm) · line 699

Quoted 10 lines
arch/x86/kvm/svm/nested.c +10 -0
+int svm_allocate_nested(struct vcpu_svm *svm)
+{
+	struct page *hsave_page;
+
+	if (svm->nested.initialized)
+		return 0;
+
+	hsave_page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!hsave_page)
+		goto error;

goto is unnecessary, just do

		return -ENOMEM;

arch/x86/kvm/svm/nested.c int nested_svm_vmexit(struct vcpu_svm *svm) · line 712

Quoted 13 lines
arch/x86/kvm/svm/nested.c +13 -0
+
+	svm->nested.hsave = page_address(hsave_page);
+
+	svm->nested.msrpm = svm_vcpu_init_msrpm();
+	if (!svm->nested.msrpm)
+		goto err_free_hsave;
+
+	svm->nested.initialized = true;
+	return 0;
+err_free_hsave:
+	__free_page(hsave_page);
+error:
+	return 1;

As above, -ENOMEM would be preferable.

Quoted 45 lines
arch/x86/kvm/svm/nested.c +16 -0
+}
+
+void svm_free_nested(struct vcpu_svm *svm)
+{
+	if (!svm->nested.initialized)
+		return;
+
+	svm_vcpu_free_msrpm(svm->nested.msrpm);
+	svm->nested.msrpm = NULL;
+
+	__free_page(virt_to_page(svm->nested.hsave));
+	svm->nested.hsave = NULL;
+
+	svm->nested.initialized = false;
+}
+
 /*
  * Forcibly leave nested mode in order to be able to reset the VCPU later on.
  */
arch/x86/kvm/svm/svm.c +8 -3
index 3da5b2f1b4a19..57ea4407dcf09 100644
@@ -266,6 +266,7 @@ static int get_max_npt_level(void)
 void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
+	u64 old_efer = vcpu->arch.efer;
 	vcpu->arch.efer = efer;
 
 	if (!npt_enabled) {
@@ -276,9 +277,26 @@ void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer)
 			efer &= ~EFER_LME;
 	}
 
-	if (!(efer & EFER_SVME)) {
-		svm_leave_nested(svm);
-		svm_set_gif(svm, true);
+	if ((old_efer & EFER_SVME) != (efer & EFER_SVME)) {
+		if (!(efer & EFER_SVME)) {
+			svm_leave_nested(svm);
+			svm_set_gif(svm, true);
+
+			/*
+			 * Free the nested state unless we are in SMM, in which

arch/x86/kvm/svm/svm.c void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) · line 296

Quoted 10 lines
arch/x86/kvm/svm/svm.c +10 -0
+			 * case the exit from SVM mode is only for duration of the SMI
+			 * handler
+			 */
+			if (!is_smm(&svm->vcpu))
+				svm_free_nested(svm);
+
+		} else {
+			if (svm_allocate_nested(svm)) {
+				vcpu->arch.efer = old_efer;
+				kvm_make_request(KVM_REQ_OUT_OF_MEMORY, vcpu);

I really dislike KVM_REQ_OUT_OF_MEMORY. It's redundant with -ENOMEM and creates a huge discrepancy with respect to existing code, e.g. nVMX returns -ENOMEM in a similar situation.

The deferred error handling creates other issues, e.g. vcpu->arch.efer is unwound but the guest's RIP is not.

One thought for handling this without opening a can of worms would be to do:

	r = kvm_x86_ops.set_efer(vcpu, efer);
	if (r) {
		WARN_ON(r > 0);
		return r;
	}

I.e. go with the original approach, but only for returning errors that will go all the way out to userspace.

Quoted 6 lines
arch/x86/kvm/svm/svm.c +3 -0
+				return;
+			}
+		}
 	}
 
 	svm->vmcb->save.efer = efer | EFER_SVME;
maintainer
#4 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

Quoted 5 lines · Sean Christopherson

arch/x86/kvm/svm/svm.c void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) · line 296

Quoted 2 lines
arch/x86/kvm/svm/svm.c +2 -0
+				vcpu->arch.efer = old_efer;
+				kvm_make_request(KVM_REQ_OUT_OF_MEMORY, vcpu);

I really dislike KVM_REQ_OUT_OF_MEMORY. It's redundant with -ENOMEM and creates a huge discrepancy with respect to existing code, e.g. nVMX returns -ENOMEM in a similar situation.

Maxim, your previous version was adding some error handling to kvm_x86_ops.set_efer. I don't remember what was the issue; did you have any problems propagating all the errors up to KVM_SET_SREGS (easy), kvm_set_msr (harder) etc.?

Paolo

#5 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

Quoted 11 lines · Paolo Bonzini
Quoted 5 lines · Sean Christopherson

arch/x86/kvm/svm/svm.c void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) · line 296

Quoted 2 lines
arch/x86/kvm/svm/svm.c +2 -0
+				vcpu->arch.efer = old_efer;
+				kvm_make_request(KVM_REQ_OUT_OF_MEMORY, vcpu);

I really dislike KVM_REQ_OUT_OF_MEMORY. It's redundant with -ENOMEM and creates a huge discrepancy with respect to existing code, e.g. nVMX returns -ENOMEM in a similar situation.

Maxim, your previous version was adding some error handling to kvm_x86_ops.set_efer. I don't remember what was the issue; did you have any problems propagating all the errors up to KVM_SET_SREGS (easy), kvm_set_msr (harder) etc.?

I objected to letting .set_efer() return a fault. A relatively minor issue is the code in vmx_set_efer() that handles lack of EFER because technically KVM can emulate EFER.SCE+SYSCALL without supporting EFER in hardware. Returning success/'0' would avoid that particular issue. My primary concern is that I'd prefer not to add another case where KVM can potentially ignore a fault indicated by a helper, a la vmx_set_cr4().

To that end, I'd be ok with adding error handling to .set_efer() if KVM enforces, via WARN in one of the .set_efer() call sites, that SVM/VMX can only return negative error codes, i.e. let SVM handle the -ENOMEM case but disallow fault injection. It doesn't actually change anything, but it'd give me a warm fuzzy feeling.

maintainer
#6 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

Quoted 5 lines · Sean Christopherson
Quoted 4 lines

Maxim, your previous version was adding some error handling to kvm_x86_ops.set_efer. I don't remember what was the issue; did you have any problems propagating all the errors up to KVM_SET_SREGS (easy), kvm_set_msr (harder) etc.?

I objected to letting .set_efer() return a fault.

So did I, and that's why we get KVM_REQ_OUT_OF_MEMORY. But it was more of an "it's ugly and it ought not to fail" thing than something I could pinpoint.

It looks like we agree, but still we have to choose the lesser evil?

Paolo

Quoted 6 lines

A relatively minor issue is
the code in vmx_set_efer() that handles lack of EFER because technically KVM can emulate EFER.SCE+SYSCALL without supporting EFER in hardware. Returning success/'0' would avoid that particular issue. My primary concern is that I'd prefer not to add another case where KVM can potentially ignore a fault indicated by a helper, a la vmx_set_cr4().

#7 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

Quoted 7 lines · Paolo Bonzini
Quoted 5 lines · Sean Christopherson
Quoted 4 lines

Maxim, your previous version was adding some error handling to kvm_x86_ops.set_efer. I don't remember what was the issue; did you have any problems propagating all the errors up to KVM_SET_SREGS (easy), kvm_set_msr (harder) etc.?

I objected to letting .set_efer() return a fault.

Quoted 14 lines

So did I, and that's why we get KVM_REQ_OUT_OF_MEMORY. But it was more of an "it's ugly and it ought not to fail" thing than something I could pinpoint.

It looks like we agree, but still we have to choose the lesser evil?

Paolo

Quoted 6 lines

A relatively minor issue is
the code in vmx_set_efer() that handles lack of EFER because technically KVM can emulate EFER.SCE+SYSCALL without supporting EFER in hardware. Returning success/'0' would avoid that particular issue. My primary concern is that I'd prefer not to add another case where KVM can potentially ignore a fault indicated by a helper, a la vmx_set_cr4().

The thing is that kvm_emulate_wrmsr injects #GP when kvm_set_msr returns any non zero value, and returns 1 which means keep on going if I understand correctly (0 is userspace exit, negative value would be a return to userspace with an error)

So the question is if we have other wrmsr handlers which return negative value, and would be affected by changing kvm_emulate_wrmsr to pass through the error value. I am checking the code now.

I do agree now that this is the *correct* solution to this problem.

Best regards,
Maxim Levitsky

#8 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

Quoted 27 lines · Maxim Levitsky
Quoted 7 lines · Paolo Bonzini
Quoted 5 lines · Sean Christopherson
Quoted 4 lines

Maxim, your previous version was adding some error handling to kvm_x86_ops.set_efer. I don't remember what was the issue; did you have any problems propagating all the errors up to KVM_SET_SREGS (easy), kvm_set_msr (harder) etc.?

I objected to letting .set_efer() return a fault.

Quoted 14 lines

So did I, and that's why we get KVM_REQ_OUT_OF_MEMORY. But it was more of an "it's ugly and it ought not to fail" thing than something I could pinpoint.

It looks like we agree, but still we have to choose the lesser evil?

Paolo

Quoted 6 lines

A relatively minor issue is
the code in vmx_set_efer() that handles lack of EFER because technically KVM can emulate EFER.SCE+SYSCALL without supporting EFER in hardware. Returning success/'0' would avoid that particular issue. My primary concern is that I'd prefer not to add another case where KVM can potentially ignore a fault indicated by a helper, a la vmx_set_cr4().

The thing is that kvm_emulate_wrmsr injects #GP when kvm_set_msr returns any non zero value, and returns 1 which means keep on going if I understand correctly (0 is userspace exit, negative value would be a return to userspace with an error)

Quoted 8 lines

So the question is if we have other wrmsr handlers which return negative value, and would be affected by changing kvm_emulate_wrmsr to pass through the error value. I am checking the code now.

I do agree now that this is the *correct* solution to this problem.

Best regards,
Maxim Levitsky

So those are results of my analysis:

WRMSR called functions that return negative value (I could have missed something, but I double checked the wrmsr code in both SVM and VMX, and in the common x86 code):

vmx_set_vmx_msr - this is only called from userspace (msr_info->host_initiated == true), so this can be left as is

xen_hvm_config - this code should probably return 1 in some cases, but in one case, it legit does memory allocation like I do, and failure should probably kill the guest as well (but I can keep it as is if we are afraid that new behavier will not be backward compatible)

What do you think about this (only compile tested since I don't have any xen setups):

arch/x86/kvm/x86.c +9 -14
index 36e963dc1da61..66a57c5b14dfd 100644
@@ -2695,24 +2695,19 @@ static int xen_hvm_config(struct kvm_vcpu *vcpu, u64 data)
        u32 page_num = data & ~PAGE_MASK;
        u64 page_addr = data & PAGE_MASK;
        u8 *page;
-       int r;
 
-       r = -E2BIG;
        if (page_num >= blob_size)
-               goto out;
-       r = -ENOMEM;
+               return 1;
+
        page = memdup_user(blob_addr + (page_num * PAGE_SIZE), PAGE_SIZE);
-       if (IS_ERR(page)) {
-               r = PTR_ERR(page);
-               goto out;
+       if (IS_ERR(page))
+               return PTR_ERR(page);
+
+       if (kvm_vcpu_write_guest(vcpu, page_addr, page, PAGE_SIZE)) {
+               kfree(page);
+               return 1;
        }
-       if (kvm_vcpu_write_guest(vcpu, page_addr, page, PAGE_SIZE))
-               goto out_free;
-       r = 0;
-out_free:
-       kfree(page);
-out:
-       return r;
+       return 0;
 }
 
 

The msr write itself can be reached from the guest through two functions, from kvm_emulate_wrmsr which is called in wrmsr interception from both VMX and SVM, and from em_wrmsr which is called in unlikely case the emulator needs to emulate a wrmsr.

Both should be changed to inject #GP only on positive return value and pass the error otherwise.

Sounds reasonable? If you agree I'll post the patches implementing this.

Best regards,
Maxim Levitsky

#9 lore raw

Re: [PATCH v4 2/2] KVM: nSVM: implement ondemand allocation of the nested state

In reply to Sean Christopherson
Quoted 46 lines · Sean Christopherson
Quoted 32 lines · Maxim Levitsky

This way we don't waste memory on VMs which don't use nesting virtualization even if it is available to them.

If allocation of nested state fails (which should happen, only when host is about to OOM anyway), use new KVM_REQ_OUT_OF_MEMORY request to shut down the guest

---
 arch/x86/kvm/svm/nested.c | 42 ++++++++++++++++++++++++++++++
 arch/x86/kvm/svm/svm.c    | 54 ++++++++++++++++++++++-----------------
 arch/x86/kvm/svm/svm.h    |  7 +++++
 3 files changed, 79 insertions(+), 24 deletions(-)
arch/x86/kvm/svm/nested.c +3 -0
index 09417f5197410..fe119da2ef836 100644
@@ -467,6 +467,9 @@ int nested_svm_vmrun(struct vcpu_svm *svm)
 
 	vmcb12 = map.hva;
 
+	if (WARN_ON(!svm->nested.initialized))
+		return 1;
+
 	if (!nested_vmcb_checks(svm, vmcb12)) {
 		vmcb12->control.exit_code    = SVM_EXIT_ERR;
 		vmcb12->control.exit_code_hi = 0;
@@ -684,6 +687,45 @@ int nested_svm_vmexit(struct vcpu_svm *svm)
 	return 0;
 }
 

arch/x86/kvm/svm/nested.c int nested_svm_vmexit(struct vcpu_svm *svm) · line 699

Quoted 10 lines
arch/x86/kvm/svm/nested.c +10 -0
+int svm_allocate_nested(struct vcpu_svm *svm)
+{
+	struct page *hsave_page;
+
+	if (svm->nested.initialized)
+		return 0;
+
+	hsave_page = alloc_page(GFP_KERNEL_ACCOUNT | __GFP_ZERO);
+	if (!hsave_page)
+		goto error;

goto is unnecessary, just do

Quoted 1 line
		return -ENOMEM;

To be honest this is a philosophical question, what way is better, but I don't mind to change this.

Quoted 16 lines
Quoted 4 lines
arch/x86/kvm/svm/nested.c +3 -0
+
+	svm->nested.hsave = page_address(hsave_page);
+

arch/x86/kvm/svm/nested.c int nested_svm_vmexit(struct vcpu_svm *svm) · line 712

Quoted 10 lines
arch/x86/kvm/svm/nested.c +10 -0
+	svm->nested.msrpm = svm_vcpu_init_msrpm();
+	if (!svm->nested.msrpm)
+		goto err_free_hsave;
+
+	svm->nested.initialized = true;
+	return 0;
+err_free_hsave:
+	__free_page(hsave_page);
+error:
+	return 1;

As above, -ENOMEM would be preferable.

After the changes to return negative values from msr writes, this indeed makes sense and is done now.

Quoted 66 lines
Quoted 46 lines
arch/x86/kvm/svm/nested.c +16 -0
+}
+
+void svm_free_nested(struct vcpu_svm *svm)
+{
+	if (!svm->nested.initialized)
+		return;
+
+	svm_vcpu_free_msrpm(svm->nested.msrpm);
+	svm->nested.msrpm = NULL;
+
+	__free_page(virt_to_page(svm->nested.hsave));
+	svm->nested.hsave = NULL;
+
+	svm->nested.initialized = false;
+}
+
 /*
  * Forcibly leave nested mode in order to be able to reset the VCPU later on.
  */
arch/x86/kvm/svm/svm.c +8 -3
index 3da5b2f1b4a19..57ea4407dcf09 100644
@@ -266,6 +266,7 @@ static int get_max_npt_level(void)
 void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer)
 {
 	struct vcpu_svm *svm = to_svm(vcpu);
+	u64 old_efer = vcpu->arch.efer;
 	vcpu->arch.efer = efer;
 
 	if (!npt_enabled) {
@@ -276,9 +277,26 @@ void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer)
 			efer &= ~EFER_LME;
 	}
 
-	if (!(efer & EFER_SVME)) {
-		svm_leave_nested(svm);
-		svm_set_gif(svm, true);
+	if ((old_efer & EFER_SVME) != (efer & EFER_SVME)) {
+		if (!(efer & EFER_SVME)) {
+			svm_leave_nested(svm);
+			svm_set_gif(svm, true);
+
+			/*
+			 * Free the nested state unless we are in SMM, in which

arch/x86/kvm/svm/svm.c void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) · line 296

Quoted 10 lines
arch/x86/kvm/svm/svm.c +10 -0
+			 * case the exit from SVM mode is only for duration of the SMI
+			 * handler
+			 */
+			if (!is_smm(&svm->vcpu))
+				svm_free_nested(svm);
+
+		} else {
+			if (svm_allocate_nested(svm)) {
+				vcpu->arch.efer = old_efer;
+				kvm_make_request(KVM_REQ_OUT_OF_MEMORY, vcpu);

I really dislike KVM_REQ_OUT_OF_MEMORY. It's redundant with -ENOMEM and creates a huge discrepancy with respect to existing code, e.g. nVMX returns -ENOMEM in a similar situation.

The deferred error handling creates other issues, e.g. vcpu->arch.efer is unwound but the guest's RIP is not.

One thought for handling this without opening a can of worms would be to do:

Quoted 8 lines
	r = kvm_x86_ops.set_efer(vcpu, efer);
	if (r) {
		WARN_ON(r > 0);
		return r;
	}

I.e. go with the original approach, but only for returning errors that will go all the way out to userspace.

Done as explained in the other reply.

Quoted 7 lines

arch/x86/kvm/svm/svm.c void svm_set_efer(struct kvm_vcpu *vcpu, u64 efer) · line 302

Quoted 7 lines
arch/x86/kvm/svm/svm.c +3 -0
+				return;
+			}
+		}
 	}
 
 	svm->vmcb->save.efer = efer | EFER_SVME;

Thanks for the review,
Best regards,
Maxim Levitsky