KVM: SVM: Implement check_nested_events for NMI
cover KVM: SVM: Implement check_nested_events for NMI
Moved nested NMI exit to new check_nested_events.
The second patch fixes the NMI pending race condition that now occurs.
Cathy Avery (2):
KVM: SVM: Implement check_nested_events for NMI
KVM: x86: check_nested_events if there is an injectable NMI
arch/x86/kvm/svm/nested.c | 21 +++++++++++++++++++++ arch/x86/kvm/svm/svm.c | 2 +- arch/x86/kvm/svm/svm.h | 15 --------------- arch/x86/kvm/x86.c | 15 +++++++++++---- 4 files changed, 33 insertions(+), 20 deletions(-)
Signature · 1 line
2.20.1
PATCH 1/2 KVM: SVM: Implement check_nested_events for NMI
Migrate nested guest NMI intercept processing
to new check_nested_events.
--- arch/x86/kvm/svm/nested.c | 21 +++++++++++++++++++++ arch/x86/kvm/svm/svm.c | 2 +- arch/x86/kvm/svm/svm.h | 15 --------------- 3 files changed, 22 insertions(+), 16 deletions(-)
arch/x86/kvm/svm/nested.c +21 -0
@@ -764,6 +764,20 @@ int nested_svm_check_exception(struct vcpu_svm *svm, unsigned nr,return vmexit; } +static bool nested_exit_on_nmi (struct vcpu_svm *svm) +{ +return (svm->nested.intercept & (1ULL <<INTERCEPT_NMI )); +} + +static void nested_svm_nmi (struct vcpu_svm *svm) +{ + svm->vmcb->control.exit_code =SVM_EXIT_NMI ; + svm->vmcb->control.exit_info_1 =0 ; + svm->vmcb->control.exit_info_2 =0 ; + + svm->nested.exit_required = true; +} +static void nested_svm_intr (struct vcpu_svm *svm) { svm->vmcb->control.exit_code =SVM_EXIT_INTR ;
@@ -793,6 +807,13 @@ int svm_check_nested_events(struct kvm_vcpu *vcpu)return 0 ; } +if (vcpu->arch.nmi_pending &&nested_exit_on_nmi (svm)) { +if (block_nested_events) +return -EBUSY ; +nested_svm_nmi (svm); +return 0 ; + } +return 0 ; }
arch/x86/kvm/svm/svm.c +1 -1
@@ -3053,7 +3053,7 @@ static int svm_nmi_allowed(struct kvm_vcpu *vcpu)int ret; ret = !(vmcb->control.int_state &SVM_INTERRUPT_SHADOW_MASK ) && !(svm->vcpu.arch.hflags &HF_NMI_MASK ); - ret = ret &&gif_set (svm) &&nested_svm_nmi (svm); + ret = ret &&gif_set (svm);return ret; }
arch/x86/kvm/svm/svm.h +0 -15
@@ -369,21 +369,6 @@ void disable_nmi_singlestep(struct vcpu_svm *svm);#define NESTED_EXIT_DONE 1/* Exit caused nested vmexit */ #define NESTED_EXIT_CONTINUE 2/* Further checks needed */ -/* This function returns true if it is save to enable the nmi window */ -static inline bool nested_svm_nmi (struct vcpu_svm *svm) -{ -if (!is_guest_mode (&svm->vcpu)) -return true; - -if (!(svm->nested.intercept & (1ULL <<INTERCEPT_NMI ))) -return true; - - svm->vmcb->control.exit_code =SVM_EXIT_NMI ; - svm->nested.exit_required = true; - -return false; -} -static inline bool svm_nested_virtualize_tpr (struct kvm_vcpu *vcpu) {return is_guest_mode (vcpu) && (vcpu->arch.hflags &HF_VINTR_MASK );
Signature · 1 line
2.20.1
PATCH 2/2 KVM: x86: check_nested_events if there is an injectable NMI
In reply to Cathy AveryWith NMI intercept moved to check_nested_events there is a race condition where vcpu->arch.nmi_pending is set late causing the execution of check_nested_events to not setup correctly for nested.exit_required. A second call to check_nested_events allows the injectable nmi to be detected in time in order to require immediate exit from L2 to L1.
--- arch/x86/kvm/x86.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
arch/x86/kvm/x86.c +11 -4
@@ -7734,10 +7734,17 @@ static int inject_pending_event(struct kvm_vcpu *vcpu) vcpu->arch.smi_pending = false; ++vcpu->arch.smi_count;enter_smm (vcpu); - }else if (vcpu->arch.nmi_pending && kvm_x86_ops.nmi_allowed (vcpu)) { - --vcpu->arch.nmi_pending; - vcpu->arch.nmi_injected = true; - kvm_x86_ops.set_nmi (vcpu); + }else if (vcpu->arch.nmi_pending) { +if (is_guest_mode (vcpu) && kvm_x86_ops.check_nested_events) { + r = kvm_x86_ops.check_nested_events (vcpu); +if (r !=0 ) +return r; + } +if (kvm_x86_ops.nmi_allowed (vcpu)) { + --vcpu->arch.nmi_pending; + vcpu->arch.nmi_injected = true; + kvm_x86_ops.set_nmi (vcpu); + } }else if (kvm_cpu_has_injectable_intr (vcpu)) {/* * Because interrupts can be injected asynchronously, we are
Signature · 1 line
2.20.1
Re: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 2 lines · Cathy Avery
With NMI intercept moved to
check_nested_eventsthere is a race condition wherevcpu->arch.nmi_pendingis set late causing
How is nmi_pending set late? The KVM_{G,S}ET_VCPU_EVENTS paths can't set it because the current KVM_RUN thread holds the mutex, and the only other call to process_nmi() is in the request path of vcpu_enter_guest, which has already executed.
Quoted 39 lines
the execution of
check_nested_eventsto not setup correctly for nested.exit_required. A second call tocheck_nested_eventsallows the injectable nmi to be detected in time in order to require immediate exit from L2 to L1.Signed-off-by Cathy Avery <[email protected]>--- arch/x86/kvm/x86.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)arch/x86/kvm/x86.c +11
-4@@ -7734,10 +7734,17 @@ static int inject_pending_event(struct kvm_vcpu *vcpu) vcpu->arch.smi_pending = false; ++vcpu->arch.smi_count;enter_smm (vcpu); - }else if (vcpu->arch.nmi_pending && kvm_x86_ops.nmi_allowed (vcpu)) { - --vcpu->arch.nmi_pending; - vcpu->arch.nmi_injected = true; - kvm_x86_ops.set_nmi (vcpu); + }else if (vcpu->arch.nmi_pending) { +if (is_guest_mode (vcpu) && kvm_x86_ops.check_nested_events) { + r = kvm_x86_ops.check_nested_events (vcpu); +if (r !=0 ) +return r; + } +if (kvm_x86_ops.nmi_allowed (vcpu)) { + --vcpu->arch.nmi_pending; + vcpu->arch.nmi_injected = true; + kvm_x86_ops.set_nmi (vcpu); + } }else if (kvm_cpu_has_injectable_intr (vcpu)) {/* * Because interrupts can be injected asynchronously, we are Signature · 1 line
2.20.1
Paolo Bonzini
maintainerRe: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 8 lines · Sean Christopherson
Quoted 2 lines · Cathy Avery
With NMI intercept moved to
check_nested_eventsthere is a race condition wherevcpu->arch.nmi_pendingis set late causingHow is
nmi_pendingset late? The KVM_{G,S}ET_VCPU_EVENTS paths can't set it because the currentKVM_RUNthread holds the mutex, and the only other call toprocess_nmi()is in the request path ofvcpu_enter_guest, which has already executed.
I think the actual cause is priority inversion between NMI and interrupts, because NMI is added last in patch 1.
Paolo
Re: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 4 lines · Paolo Bonzini
Quoted 3 lines · Sean Christopherson
Quoted 2 lines · Cathy Avery
With NMI intercept moved to
check_nested_eventsthere is a race condition wherevcpu->arch.nmi_pendingis set late causing
Quoted 8 lines
Quoted 5 lines
How is
nmi_pendingset late? The KVM_{G,S}ET_VCPU_EVENTS paths can't set it because the currentKVM_RUNthread holds the mutex, and the only other call toprocess_nmi()is in the request path ofvcpu_enter_guest, which has already executed.I think the actual cause is priority inversion between NMI and interrupts, because NMI is added last in patch 1.
Ah, that makes more sense. I stared/glared at this exact code for a long while and came to the conclusion that the "late" behavior was exclusive to interrupts, would have been a shame if all that glaring was for naught.
Paolo Bonzini
maintainerRe: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 10 lines · Sean Christopherson
Quoted 9 lines · Paolo Bonzini
Quoted 8 lines · Sean Christopherson
Quoted 2 lines · Cathy Avery
With NMI intercept moved to
check_nested_eventsthere is a race condition wherevcpu->arch.nmi_pendingis set late causingHow is
nmi_pendingset late? The KVM_{G,S}ET_VCPU_EVENTS paths can't set it because the currentKVM_RUNthread holds the mutex, and the only other call toprocess_nmi()is in the request path ofvcpu_enter_guest, which has already executed.
Quoted 6 lines
Quoted 2 lines
I think the actual cause is priority inversion between NMI and interrupts, because NMI is added last in patch 1.
Ah, that makes more sense. I stared/glared at this exact code for a long while and came to the conclusion that the "late" behavior was exclusive to interrupts, would have been a shame if all that glaring was for naught.
Ah no, it's a bug in Cathy's patch and it's a weird one.
The problem is that on AMD you exit guest mode with the NMI latched and GIF=0. So check_nested_events should enable the NMI window in addition to causing a vmexit.
So why does it work? Because on AMD we don't have (yet) nested_run_pending, so we just check if we already have a vmexit scheduled and if so return -EBUSY. The second call causes inject_pending_event to return -EBUSY and thus go through KVM_REQ_EVENT again, which enables the NMI window.
Paolo
Paolo Bonzini
maintainerRe: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 7 lines · Paolo Bonzini
Quoted 1 line
Ah no, it's a bug in Cathy's patch and it's a weird one.
The problem is that on AMD you exit guest mode with the NMI latched and GIF=0. So
check_nested_eventsshould enable the NMI window in addition to causing a vmexit.
Quoted 5 lines
So why does it work? Because on AMD we don't have (yet)
nested_run_pending, so we just check if we already have a vmexit scheduled and if so return-EBUSY. The second call causesinject_pending_eventto return-EBUSYand thus go throughKVM_REQ_EVENTagain, which enables the NMI window.
... and this means that suddenly your event handling series has become twice as large so I'm taking it over.
Paolo
Re: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
In reply to Sean ChristophersonQuoted 7 lines · Sean Christopherson
Quoted 2 lines · Cathy Avery
With NMI intercept moved to
check_nested_eventsthere is a race condition wherevcpu->arch.nmi_pendingis set late causingHow is
nmi_pendingset late? The KVM_{G,S}ET_VCPU_EVENTS paths can't set it because the currentKVM_RUNthread holds the mutex, and the only other call toprocess_nmi()is in the request path ofvcpu_enter_guest, which has already executed.
You will have to forgive me as I am new to KVM and any help would be most appreciated. This is what I noticed when an NMI intercept is processed when it was implemented in check_nested_events.
When check_nested_events is called from inject_pending_event ... check_nested_events needs to have already been called (kvm_vcpu_running with vcpu->arch.nmi_pending = 1) to set up the NMI intercept and set svm->nested.exit_required. Otherwise we do not exit from the second checked_nested_events call ( code below ) with a return of -EBUSY which allows us to immediately vmexit.
/*
* Call check_nested_events() even if we reinjected a previous event
* in order for caller to determine if it should require immediate-exit
* from L2 to L1 due to pending L1 events which require exit
* from L2 to L1.
*/
if (is_guest_mode(vcpu) && kvm_x86_ops.check_nested_events) { r = kvm_x86_ops.check_nested_events(vcpu); if (r != 0)
return r;
}
Unfortunately when kvm_vcpu_running is called vcpu->arch.nmi_pending is not yet set.
Here is the trace snippet ( with some debug ) without the second call to check_nested_events.
Thanks,
Cathy
qemu-system-x86-2029 [040] 232.168269: kvm_entry: vcpu 0
qemu-system-x86-2029 [040] 232.168271: kvm_exit: reason EXIT_MSR rip 0x405371 info 1 0 qemu-system-x86-2029 [040] 232.168272: kvm_nested_vmexit: rip 405371 reason EXIT_MSR info1 1 info2 0 int_info 0 int_info_err 0 qemu-system-x86-2029 [040] 232.168273: kvm_apic: apic_write APIC_ICR2 = 0x0
qemu-system-x86-2029 [040] 232.168274: kvm_apic: apic_write APIC_ICR = 0x44400
qemu-system-x86-2029 [040] 232.168275: kvm_apic_ipi: dst 0 vec 0 (NMI|physical|assert|edge|self) qemu-system-x86-2029 [040] 232.168277: kvm_apic_accept_irq: apicid 0 vec 0 (NMI|edge) qemu-system-x86-2029 [040] 232.168278: kvm_msr: msr_write 830 = 0x44400
qemu-system-x86-2029 [040] 232.168279: bprint: svm_check_nested_events: svm_check_nested_events reinj = 0, exit_req = 0 qemu-system-x86-2029 [040] 232.168279: bprint: svm_check_nested_events: svm_check_nested_events nmi pending = 0 qemu-system-x86-2029 [040] 232.168279: bputs: vcpu_enter_guest: inject_pending_event 1 qemu-system-x86-2029 [040] 232.168279: bprint: svm_check_nested_events: svm_check_nested_events reinj = 0, exit_req = 0 qemu-system-x86-2029 [040] 232.168279: bprint: svm_check_nested_events: svm_check_nested_events nmi pending = 1 qemu-system-x86-2029 [040] 232.168280: bprint: svm_nmi_allowed: svm_nmi_allowed ret 1 qemu-system-x86-2029 [040] 232.168280: bputs: svm_inject_nmi: svm_inject_nmi
qemu-system-x86-2029 [040] 232.168280: bprint: vcpu_enter_guest: nmi_pending 0
qemu-system-x86-2029 [040] 232.168281: kvm_entry: vcpu 0
qemu-system-x86-2029 [040] 232.168282: kvm_exit: reason EXIT_NMI rip 0x405373 info 1 0 qemu-system-x86-2029 [040] 232.168284: kvm_nested_vmexit_inject: reason EXIT_NMI info1 1 info2 0 int_info 0 int_info_err 0 qemu-system-x86-2029 [040] 232.168285: kvm_entry: vcpu 0
Quoted 39 lines
Quoted 39 lines
the execution of
check_nested_eventsto not setup correctly for nested.exit_required. A second call tocheck_nested_eventsallows the injectable nmi to be detected in time in order to require immediate exit from L2 to L1.Signed-off-by Cathy Avery <[email protected]>--- arch/x86/kvm/x86.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)arch/x86/kvm/x86.c +11
-4@@ -7734,10 +7734,17 @@ static int inject_pending_event(struct kvm_vcpu *vcpu) vcpu->arch.smi_pending = false; ++vcpu->arch.smi_count;enter_smm (vcpu); - }else if (vcpu->arch.nmi_pending && kvm_x86_ops.nmi_allowed (vcpu)) { - --vcpu->arch.nmi_pending; - vcpu->arch.nmi_injected = true; - kvm_x86_ops.set_nmi (vcpu); + }else if (vcpu->arch.nmi_pending) { +if (is_guest_mode (vcpu) && kvm_x86_ops.check_nested_events) { + r = kvm_x86_ops.check_nested_events (vcpu); +if (r !=0 ) +return r; + } +if (kvm_x86_ops.nmi_allowed (vcpu)) { + --vcpu->arch.nmi_pending; + vcpu->arch.nmi_injected = true; + kvm_x86_ops.set_nmi (vcpu); + } }else if (kvm_cpu_has_injectable_intr (vcpu)) {/* * Because interrupts can be injected asynchronously, we are Signature · 1 line
2.20.1
Paolo Bonzini
maintainerRe: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 3 lines · Cathy Avery
You will have to forgive me as I am new to KVM and any help would be most appreciated.
No problem---this is a _really_ hairy part. At least every time we make some changes it suddenly starts making more sense (both hacks and bugs decrease over time).
Paolo
Re: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 8 lines · Paolo Bonzini
Quoted 3 lines · Cathy Avery
You will have to forgive me as I am new to KVM and any help would be most appreciated.
No problem---this is a _really_ hairy part. At least every time we make some changes it suddenly starts making more sense (both hacks and bugs decrease over time).
LOL, no kidding, what sadist gave Cathy nested NMI as a ramp task? :-D
Paolo Bonzini
maintainerRe: [PATCH 2/2] KVM: x86: check_nested_events if there is an injectable NMI
Quoted 11 lines · Sean Christopherson
Quoted 8 lines · Paolo Bonzini
Quoted 3 lines · Cathy Avery
You will have to forgive me as I am new to KVM and any help would be most appreciated.
No problem---this is a _really_ hairy part. At least every time we make some changes it suddenly starts making more sense (both hacks and bugs decrease over time).
LOL, no kidding, what sadist gave Cathy nested NMI as a ramp task? :-D
The suggestion was to write more tests, she decided to extend that to fixing them!!
Paolo
Quoted 7 lines · Cathy Avery
Moved nested NMI exit to new
check_nested_events.
The second patch fixes the NMI pending race condition that now occurs.Cathy Avery (2):
KVM: SVM: Implementcheck_nested_eventsfor NMI
KVM: x86:check_nested_eventsif there is an injectable NMI
Not directly related to this series but I just noticed that we have the following comment in inject_pending_event():
/* try to inject new event if pending */
if (vcpu->arch.exception.pending) {
...
if (vcpu->arch.exception.nr == DB_VECTOR) {
/*
* This code assumes that nSVM doesn't use
* check_nested_events(). If it does, the
* DR6/DR7 changes should happen before L1
* gets a #VMEXIT for an intercepted #DB in
* L2. (Under VMX, on the other hand, the
* DR6/DR7 changes should not happen in the
* event of a VM-exit to L1 for an intercepted
* #DB in L2.)
*/
kvm_deliver_exception_payload(vcpu);
if (vcpu->arch.dr7 & DR7_GD) {
vcpu->arch.dr7 &= ~DR7_GD;
kvm_update_dr7(vcpu);
}
}
kvm_x86_ops.queue_exception(vcpu);
}As we already implement check_nested_events() on SVM, do we need to do anything here? CC: Jim who added the guardian (f10c729ff9652).
Quoted 5 lines
arch/x86/kvm/svm/nested.c | 21 +++++++++++++++++++++ arch/x86/kvm/svm/svm.c | 2 +- arch/x86/kvm/svm/svm.h | 15 --------------- arch/x86/kvm/x86.c | 15 +++++++++++---- 4 files changed, 33 insertions(+), 20 deletions(-)
Signature · 1 line
Vitaly
Paolo Bonzini
maintainerQuoted 27 lines · Vitaly Kuznetsov
Not directly related to this series but I just noticed that we have the following comment in
inject_pending_event():/* try to inject new event if pending */ if (vcpu->arch.exception.pending) { ... if (vcpu->arch.exception.nr == DB_VECTOR) { /* * This code assumes that nSVM doesn't use * check_nested_events(). If it does, the * DR6/DR7 changes should happen before L1 * gets a #VMEXIT for an intercepted #DB in * L2. (Under VMX, on the other hand, the * DR6/DR7 changes should not happen in the * event of a VM-exit to L1 for an intercepted * #DB in L2.) */ kvm_deliver_exception_payload(vcpu); if (vcpu->arch.dr7 & DR7_GD) { vcpu->arch.dr7 &= ~DR7_GD; kvm_update_dr7(vcpu); } } kvm_x86_ops.queue_exception(vcpu); }
Quoted 2 lines
As we already implement
check_nested_events()on SVM, do we need to do anything here? CC: Jim who added the guardian (f10c729ff9652).
It's (still) okay because we don't use check_nested_events() for exceptions.
Regarding this series, I think we should implement the NMI injection test for VMX and see if it requires the same change as patch 2.
Paolo
Paolo Bonzini
maintainerQuoted 7 lines · Cathy Avery
Moved nested NMI exit to new
check_nested_events.
The second patch fixes the NMI pending race condition that now occurs.Cathy Avery (2):
KVM: SVM: Implementcheck_nested_eventsfor NMI
KVM: x86:check_nested_eventsif there is an injectable NMI
Quoted 6 lines
arch/x86/kvm/svm/nested.c | 21 +++++++++++++++++++++ arch/x86/kvm/svm/svm.c | 2 +- arch/x86/kvm/svm/svm.h | 15 --------------- arch/x86/kvm/x86.c | 15 +++++++++++---- 4 files changed, 33 insertions(+), 20 deletions(-)
Thanks, I've integrated this in Sean's event reinjection series and will post soon my own take on both.
Paolo