Skip to contentSkip to conversation

KVM: VMX: Fix locking order in handle_invalid_guest_state

PATCH In review Jan Kiszka 4 participants 21 messages Jul 22, 2009 – Jul 30, 2009

Subsystems (3)
lore
Share & tools
lore

Jan Kiszka

author
#0 lore raw

PATCH 1/1 KVM: VMX: Fix locking order in handle_invalid_guest_state

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

---

 arch/x86/kvm/vmx.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx.c +2 -2
index d75c271..4f914c3 100644
@@ -3324,8 +3324,8 @@ static void handle_invalid_guest_state(struct kvm_vcpu *vcpu,
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 	enum emulation_result err = EMULATE_DONE;
 
-	preempt_enable();
 	local_irq_enable();
+	preempt_enable();
 
 	while (!guest_state_valid(vcpu)) {
 		err = emulate_instruction(vcpu, kvm_run, 0, 0, 0);
@@ -3344,8 +3344,8 @@ static void handle_invalid_guest_state(struct kvm_vcpu *vcpu,
 			schedule();
 	}
 
-	local_irq_disable();
 	preempt_disable();
+	local_irq_disable();
 
 	vmx->invalid_state_emulation_result = err;
 }
#1 lore raw
Quoted 2 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Maybe add a new member to x86_ops?

Quoted 32 lines

---

 arch/x86/kvm/vmx.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx.c +2 -2
index d75c271..4f914c3 100644
@@ -3324,8 +3324,8 @@ static void handle_invalid_guest_state(struct kvm_vcpu *vcpu,
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 	enum emulation_result err = EMULATE_DONE;
 
-	preempt_enable();
 	local_irq_enable();
+	preempt_enable();
 
 	while (!guest_state_valid(vcpu)) {
 		err = emulate_instruction(vcpu, kvm_run, 0, 0, 0);
@@ -3344,8 +3344,8 @@ static void handle_invalid_guest_state(struct kvm_vcpu *vcpu,
 			schedule();
 	}
 
-	local_irq_disable();
 	preempt_disable();
+	local_irq_disable();
 
 	vmx->invalid_state_emulation_result = err;
 }
author
#2 lore raw
Quoted 6 lines · Marcelo Tosatti
Quoted 2 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Maybe, maybe not. handle_invalid_guest_state is an alternative way of "executing" guest code, and it currently shares the setup and tear-down with vmx_vcpu_run. If it has to share parts that actually require preemption and IRQ lock, then moving makes not much sense. Can anyone comment on what the requirements for handle_invalid_guest_state are?

I would suggest to merge this fix first and then decide about and potentially merge a refactoring patch.

Jan

Quoted 36 lines

Maybe add a new member to x86_ops?

Quoted 31 lines

---

 arch/x86/kvm/vmx.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
arch/x86/kvm/vmx.c +2 -2
index d75c271..4f914c3 100644
@@ -3324,8 +3324,8 @@ static void handle_invalid_guest_state(struct kvm_vcpu *vcpu,
 	struct vcpu_vmx *vmx = to_vmx(vcpu);
 	enum emulation_result err = EMULATE_DONE;
 
-	preempt_enable();
 	local_irq_enable();
+	preempt_enable();
 
 	while (!guest_state_valid(vcpu)) {
 		err = emulate_instruction(vcpu, kvm_run, 0, 0, 0);
@@ -3344,8 +3344,8 @@ static void handle_invalid_guest_state(struct kvm_vcpu *vcpu,
 			schedule();
 	}
 
-	local_irq_disable();
 	preempt_disable();
+	local_irq_disable();
 
 	vmx->invalid_state_emulation_result = err;
 }
#3 lore raw
Quoted 11 lines · Jan Kiszka
Quoted 8 lines · Marcelo Tosatti
Quoted 3 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Quoted 6 lines

Maybe, maybe not. handle_invalid_guest_state is an alternative way of "executing" guest code, and it currently shares the setup and tear-down with vmx_vcpu_run. If it has to share parts that actually require preemption and IRQ lock, then moving makes not much sense. Can anyone comment on what the requirements for handle_invalid_guest_state are?

Like you said, it's an alternative to vmx entry/exit, so it shares the same requirements. It must run with interrupts and preemption enabled, but any code that normally runs in the entry critical section (like interrupt injection) must continue to run in a critical section.

Quoted 3 lines

I would suggest to merge this fix first and then decide about and potentially merge a refactoring patch.

btw, what does it fix? a debug warning?

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#4 lore raw
Quoted 25 lines · Avi Kivity
Quoted 11 lines · Jan Kiszka
Quoted 8 lines · Marcelo Tosatti
Quoted 3 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Quoted 6 lines

Maybe, maybe not. handle_invalid_guest_state is an alternative way of "executing" guest code, and it currently shares the setup and tear-down with vmx_vcpu_run. If it has to share parts that actually require preemption and IRQ lock, then moving makes not much sense. Can anyone comment on what the requirements for handle_invalid_guest_state are?

Like you said, it's an alternative to vmx entry/exit, so it shares the same requirements. It must run with interrupts and preemption enabled, but any code that normally runs in the entry critical section (like interrupt injection) must continue to run in a critical section.

Quoted 6 lines
Quoted 3 lines

I would suggest to merge this fix first and then decide about and potentially merge a refactoring patch.

btw, what does it fix? a debug warning?

I haven't seen anything in the wild, and I don't think it would raise a warning. All it should cause is a potential delay of some pending reschedule as preempt_enable will not fire under local_irq_disable.

Jan

#5 lore raw
Quoted 8 lines · Jan Kiszka
Quoted 3 lines

btw, what does it fix? a debug warning?

I haven't seen anything in the wild, and I don't think it would raise a warning. All it should cause is a potential delay of some pending reschedule as preempt_enable will not fire under local_irq_disable.

Ah, okay, then it is a real fix. Preempt-correctness is important.

(but won't local_irq_enable() reschedule?)

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#6 lore raw
Quoted 5 lines · Avi Kivity
Quoted 4 lines · Jan Kiszka
Quoted 3 lines

btw, what does it fix? a debug warning?

Quoted 8 lines
Quoted 4 lines

I haven't seen anything in the wild, and I don't think it would raise a warning. All it should cause is a potential delay of some pending reschedule as preempt_enable will not fire under local_irq_disable.

Ah, okay, then it is a real fix. Preempt-correctness is important.

(but won't local_irq_enable() reschedule?)

The last time I checked it was essentially a plain 'sti'.

Jan

#7 lore raw
Quoted 13 lines · Jan Kiszka
Quoted 11 lines · Avi Kivity
Quoted 9 lines · Jan Kiszka
Quoted 4 lines

btw, what does it fix? a debug warning?

I haven't seen anything in the wild, and I don't think it would raise a warning. All it should cause is a potential delay of some pending reschedule as preempt_enable will not fire under local_irq_disable.

Quoted 8 lines
Quoted 4 lines

Ah, okay, then it is a real fix. Preempt-correctness is important.

(but won't local_irq_enable() reschedule?)

The last time I checked it was essentially a plain 'sti'.

Presumably there's a reschedule interrupt queued; I think if you set the reschedule bit you have to IPI the cpu running the task.

Signature · 1 line
error compiling committee.c: too many arguments to function
author
#8 lore raw
Quoted 19 lines · Avi Kivity
Quoted 18 lines · Jan Kiszka
Quoted 15 lines · Avi Kivity
Quoted 9 lines · Jan Kiszka
Quoted 4 lines

btw, what does it fix? a debug warning?

I haven't seen anything in the wild, and I don't think it would raise a warning. All it should cause is a potential delay of some pending reschedule as preempt_enable will not fire under local_irq_disable.

Ah, okay, then it is a real fix. Preempt-correctness is important.

(but won't local_irq_enable() reschedule?)

Quoted 7 lines
Quoted 3 lines

The last time I checked it was essentially a plain 'sti'.

Presumably there's a reschedule interrupt queued; I think if you set the reschedule bit you have to IPI the cpu running the task.

Yeah. But as we preempt_disable first, that one might have been processed already.

Jan

#9 lore raw
Quoted 7 lines · Jan Kiszka
Quoted 3 lines

Presumably there's a reschedule interrupt queued; I think if you set the reschedule bit you have to IPI the cpu running the task.

Yeah. But as we preempt_disable first, that one might have been processed already.

Ah, yes. Thanks.

Signature · 1 line
error compiling committee.c: too many arguments to function
#10 lore raw
In reply to Marcelo Tosatti
Quoted 8 lines · Marcelo Tosatti
Quoted 2 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Maybe add a new member to x86_ops?

Why don't do something like this ?

arch/x86/include/asm/kvm_host.h +1 -0
Index: kvm-requests/arch/x86/include/asm/kvm_host.h
===================================================================
@@ -529,6 +529,7 @@ struct kvm_x86_ops {
 	int (*set_tss_addr)(struct kvm *kvm, unsigned int addr);
 	int (*get_tdp_level)(void);
 	u64 (*get_mt_mask)(struct kvm_vcpu *vcpu, gfn_t gfn, bool is_mmio);
+	int (*emulate_guest_state)(struct kvm_vcpu *vcpu, struct kvm_run *run);
 	const struct trace_print_flags *exit_reasons_str;
 };
 
arch/x86/kvm/vmx.c +11 -31

115 lines not on this page · raw message

arch/x86/kvm/x86.c +5 -0
Index: kvm-requests/arch/x86/kvm/x86.c
===================================================================
@@ -3556,6 +3556,11 @@ static int vcpu_enter_guest(struct kvm_v
 			kvm_mmu_sync_roots(vcpu);
 		if (test_and_clear_bit(KVM_REQ_TLB_FLUSH, &vcpu->requests))
 			kvm_x86_ops->tlb_flush(vcpu);
+		if (test_and_clear_bit(KVM_REQ_EMULATE, &vcpu->requests)) {
+			r = kvm_x86_ops->emulate_guest_state(vcpu, kvm_run);
+			goto out;
+		}
+
 		if (test_and_clear_bit(KVM_REQ_REPORT_TPR_ACCESS,
 				       &vcpu->requests)) {
 			kvm_run->exit_reason = KVM_EXIT_TPR_ACCESS;
include/linux/kvm_host.h +1 -0
Index: kvm-requests/include/linux/kvm_host.h
===================================================================
@@ -39,6 +39,7 @@
 #define KVM_REQ_MMU_SYNC           7
 #define KVM_REQ_KVMCLOCK_UPDATE    8
 #define KVM_REQ_KICK               9
+#define KVM_REQ_EMULATE		   10
 
 #define KVM_USERSPACE_IRQ_SOURCE_ID	0
 
#11 lore raw
Quoted 7 lines · Marcelo Tosatti
Quoted 5 lines · Marcelo Tosatti
Quoted 3 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

Quoted 8 lines
Quoted 5 lines

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Maybe add a new member to x86_ops?

Why don't do something like this ?

The downside is that we're moving a vmx specific hack to common code.

I think this could be simplified if interrupt injection happened outside the critical section. This is needed anyway because emulated interrupt injection needs to access guest memory (IVT and the stack).

Something else I noticed, handle_invalid_guest_state() doesn't check vcpu->requests; normal execution will exit due to the interrupt while emulated execution will not.

Signature · 1 line
error compiling committee.c: too many arguments to function
#12 lore raw
Quoted 14 lines · Avi Kivity
Quoted 13 lines · Marcelo Tosatti
Quoted 10 lines · Marcelo Tosatti
Quoted 3 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Maybe add a new member to x86_ops?

Quoted 8 lines
Quoted 2 lines

Why don't do something like this ?

The downside is that we're moving a vmx specific hack to common code.

I think this could be simplified if interrupt injection happened outside the critical section. This is needed anyway because emulated interrupt injection needs to access guest memory (IVT and the stack).

Why can't it happen now (outside of the critical section), other than the kvm_vcpu_kick thing?

Quoted 7 lines

Something else I noticed, handle_invalid_guest_state() doesn't check vcpu->requests; normal execution will exit due to the interrupt while emulated execution will not.

Signature · 1 line
error compiling committee.c: too many arguments to function
#13 lore raw
Quoted 18 lines · Marcelo Tosatti
Quoted 17 lines · Avi Kivity
Quoted 15 lines · Marcelo Tosatti
Quoted 10 lines · Marcelo Tosatti
Quoted 3 lines · Jan Kiszka

Release and re-acquire preemption and IRQ lock in the same order as vcpu_enter_guest does.

This should happen in vcpu_enter_guest, before it decides to disable preemption/irqs (so you consolidate the control there).

Maybe add a new member to x86_ops?

Why don't do something like this ?

Quoted 9 lines
Quoted 5 lines

The downside is that we're moving a vmx specific hack to common code.

I think this could be simplified if interrupt injection happened outside the critical section. This is needed anyway because emulated interrupt injection needs to access guest memory (IVT and the stack).

Why can't it happen now (outside of the critical section), other than the kvm_vcpu_kick thing?

Depend what part of irq injection we want out of critical section. I guess inject_pending_event() can be called outside of it now. Need to think about others.

--
Gleb.

#14 lore raw
In reply to Marcelo Tosatti
Quoted 10 lines · Marcelo Tosatti
Quoted 6 lines

The downside is that we're moving a vmx specific hack to common code.

I think this could be simplified if interrupt injection happened outside the critical section. This is needed anyway because emulated interrupt injection needs to access guest memory (IVT and the stack).

Why can't it happen now (outside of the critical section), other than the kvm_vcpu_kick thing?

I think there's little reason now. One thing we need to do is make it possible to call the injection code twice without entering the guest. I think right now it assumes nothing has been injected.

We need either to cancel a previous injection (a variant of vmx_complete_interrupts()), or avoid reinjecting if we already did.

Signature · 1 line
error compiling committee.c: too many arguments to function
#15 lore raw
Quoted 8 lines · Avi Kivity
Quoted 7 lines · Marcelo Tosatti
Quoted 6 lines

The downside is that we're moving a vmx specific hack to common code.

I think this could be simplified if interrupt injection happened outside the critical section. This is needed anyway because emulated interrupt injection needs to access guest memory (IVT and the stack).

Quoted 8 lines
Quoted 3 lines

Why can't it happen now (outside of the critical section), other than the kvm_vcpu_kick thing?

I think there's little reason now. One thing we need to do is make it possible to call the injection code twice without entering the guest. I think right now it assumes nothing has been injected.

I Looked at this and it seems the current code handle this case. Injection puts an event on a queue and if we haven't entered a guest after this point on the next entry event is injected from the queue, just like if injection failed due to IDT access.

What may happen is that at the time of irq injection there will be irq with higher irr pending in APIC, But it looks like this already may happen if injection failed on IDT access, but this is rare.

Quoted 5 lines

We need either to cancel a previous injection (a variant of vmx_complete_interrupts()), or avoid reinjecting if we already did.

Signature · 1 line
error compiling committee.c: too many arguments to function

--
Gleb.

#16 lore raw
Quoted 11 lines · Gleb Natapov
Quoted 5 lines

I think there's little reason now. One thing we need to do is make it possible to call the injection code twice without entering the guest. I think right now it assumes nothing has been injected.

I Looked at this and it seems the current code handle this case. Injection puts an event on a queue and if we haven't entered a guest after this point on the next entry event is injected from the queue, just like if injection failed due to IDT access.

Good (it was one of the goals of the original interrupt rework, ~2 years ago)

Quoted 4 lines

What may happen is that at the time of irq injection there will be irq with higher irr pending in APIC, But it looks like this already may happen if injection failed on IDT access, but this is rare.

Right, we don't guarantee the precise time the APIC selects the vector to inject, as long as it's after the last vmexit.

Signature · 1 line
error compiling committee.c: too many arguments to function
#17 lore raw
Quoted 13 lines · Avi Kivity
Quoted 11 lines · Gleb Natapov
Quoted 5 lines

I think there's little reason now. One thing we need to do is make it possible to call the injection code twice without entering the guest. I think right now it assumes nothing has been injected.

I Looked at this and it seems the current code handle this case. Injection puts an event on a queue and if we haven't entered a guest after this point on the next entry event is injected from the queue, just like if injection failed due to IDT access.

Quoted 3 lines

Good (it was one of the goals of the original interrupt rework, ~2 years ago)

But if we emulate an injection by playing with guest memory and registers we have to be sure we do it only once.

--
Gleb.

#18 lore raw
Quoted 7 lines · Gleb Natapov
Quoted 4 lines

Good (it was one of the goals of the original interrupt rework, ~2 years ago)

But if we emulate an injection by playing with guest memory and registers we have to be sure we do it only once.

Once we've successfully updated the stack, inject is complete and we can remove the interrupt from the queue.

Signature · 1 line
error compiling committee.c: too many arguments to function
#19 lore raw
Quoted 5 lines · Avi Kivity
Quoted 4 lines · Gleb Natapov
Quoted 4 lines

Good (it was one of the goals of the original interrupt rework, ~2 years ago)

Quoted 7 lines
Quoted 3 lines

But if we emulate an injection by playing with guest memory and registers we have to be sure we do it only once.

Once we've successfully updated the stack, inject is complete and we can remove the interrupt from the queue.

Then, it we skip actual guest entry for some reason, we will not be able to tell if we can inject next irq or not.

--
Gleb.

#20 lore raw
Quoted 10 lines · Gleb Natapov
Quoted 9 lines · Avi Kivity
Quoted 7 lines · Gleb Natapov
Quoted 4 lines

Good (it was one of the goals of the original interrupt rework, ~2 years ago)

But if we emulate an injection by playing with guest memory and registers we have to be sure we do it only once.

Quoted 6 lines
Quoted 3 lines

Once we've successfully updated the stack, inject is complete and we can remove the interrupt from the queue.

Then, it we skip actual guest entry for some reason, we will not be able to tell if we can inject next irq or not.

Actually thinking about it when first irq is injected we should clear IF flag, so the second one will not happen.

--
Gleb.