diff mbox

KVM: x86: fix #UD address of failed Hyper-V hypercalls

Message ID 20180524155056.710-1-rkrcmar@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Radim Krčmář May 24, 2018, 3:50 p.m. UTC
If the hypercall was called from userspace or real mode, KVM injects #UD
and then advances RIP, so it looks like #UD was caused by the following
instruction.  This probably won't cause more than confusion, but could
give an unexpected access to guest OS' instruction emulator.

Also, refactor the code to count hv hypercalls that were handled by the
virt userspace.

Fixes: 6356ee0c9602 ("x86: Delay skip of emulated hypercall instruction")
Signed-off-by: Radim Krčmář <rkrcmar@redhat.com>
---
 arch/x86/kvm/hyperv.c | 19 +++++++++++--------
 arch/x86/kvm/x86.c    | 12 ++++--------
 2 files changed, 15 insertions(+), 16 deletions(-)

Comments

Konrad Rzeszutek Wilk May 24, 2018, 4:08 p.m. UTC | #1
On Thu, May 24, 2018 at 05:50:56PM +0200, Radim Krčmář wrote:
> If the hypercall was called from userspace or real mode, KVM injects #UD
> and then advances RIP, so it looks like #UD was caused by the following
> instruction.  This probably won't cause more than confusion, but could
> give an unexpected access to guest OS' instruction emulator.

What does the SDM say about this? Or does the HyperV specification give
a firm statement that this is the expectation ?

> 
> Also, refactor the code to count hv hypercalls that were handled by the
> virt userspace.
> 
> Fixes: 6356ee0c9602 ("x86: Delay skip of emulated hypercall instruction")
> Signed-off-by: Radim Krčmář <rkrcmar@redhat.com>
> ---
>  arch/x86/kvm/hyperv.c | 19 +++++++++++--------
>  arch/x86/kvm/x86.c    | 12 ++++--------
>  2 files changed, 15 insertions(+), 16 deletions(-)
> 
> diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
> index 5708e951a5c6..46ff64da44ca 100644
> --- a/arch/x86/kvm/hyperv.c
> +++ b/arch/x86/kvm/hyperv.c
> @@ -1260,12 +1260,16 @@ static void kvm_hv_hypercall_set_result(struct kvm_vcpu *vcpu, u64 result)
>  	}
>  }
>  
> +static int kvm_hv_hypercall_complete(struct kvm_vcpu *vcpu, u64 result)
> +{
> +	kvm_hv_hypercall_set_result(vcpu, result);
> +	++vcpu->stat.hypercalls;
> +	return kvm_skip_emulated_instruction(vcpu);
> +}
> +
>  static int kvm_hv_hypercall_complete_userspace(struct kvm_vcpu *vcpu)
>  {
> -	struct kvm_run *run = vcpu->run;
> -
> -	kvm_hv_hypercall_set_result(vcpu, run->hyperv.u.hcall.result);
> -	return kvm_skip_emulated_instruction(vcpu);
> +	return kvm_hv_hypercall_complete(vcpu, vcpu->run->hyperv.u.hcall.result);
>  }
>  
>  static u16 kvm_hvcall_signal_event(struct kvm_vcpu *vcpu, bool fast, u64 param)
> @@ -1350,7 +1354,7 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
>  	/* Hypercall continuation is not supported yet */
>  	if (rep_cnt || rep_idx) {
>  		ret = HV_STATUS_INVALID_HYPERCALL_CODE;
> -		goto set_result;
> +		goto out;
>  	}
>  
>  	switch (code) {
> @@ -1381,9 +1385,8 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
>  		break;
>  	}
>  
> -set_result:
> -	kvm_hv_hypercall_set_result(vcpu, ret);
> -	return 1;
> +out:
> +	return kvm_hv_hypercall_complete(vcpu, ret);
>  }
>  
>  void kvm_hv_init_vm(struct kvm *kvm)
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 37dd9a9d050a..35a20fcfb661 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -6676,11 +6676,8 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
>  	unsigned long nr, a0, a1, a2, a3, ret;
>  	int op_64_bit;
>  
> -	if (kvm_hv_hypercall_enabled(vcpu->kvm)) {
> -		if (!kvm_hv_hypercall(vcpu))
> -			return 0;
> -		goto out;
> -	}
> +	if (kvm_hv_hypercall_enabled(vcpu->kvm))
> +		return kvm_hv_hypercall(vcpu);
>  
>  	nr = kvm_register_read(vcpu, VCPU_REGS_RAX);
>  	a0 = kvm_register_read(vcpu, VCPU_REGS_RBX);
> @@ -6701,7 +6698,7 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
>  
>  	if (kvm_x86_ops->get_cpl(vcpu) != 0) {
>  		ret = -KVM_EPERM;
> -		goto out_error;
> +		goto out;
>  	}
>  
>  	switch (nr) {
> @@ -6721,12 +6718,11 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
>  		ret = -KVM_ENOSYS;
>  		break;
>  	}
> -out_error:
> +out:
>  	if (!op_64_bit)
>  		ret = (u32)ret;
>  	kvm_register_write(vcpu, VCPU_REGS_RAX, ret);
>  
> -out:
>  	++vcpu->stat.hypercalls;
>  	return kvm_skip_emulated_instruction(vcpu);
>  }
> -- 
> 2.17.0
>
Paolo Bonzini May 24, 2018, 4:22 p.m. UTC | #2
On 24/05/2018 18:08, Konrad Rzeszutek Wilk wrote:
> On Thu, May 24, 2018 at 05:50:56PM +0200, Radim Krčmář wrote:
>> If the hypercall was called from userspace or real mode, KVM injects #UD
>> and then advances RIP, so it looks like #UD was caused by the following
>> instruction.  This probably won't cause more than confusion, but could
>> give an unexpected access to guest OS' instruction emulator.
>
> What does the SDM say about this? Or does the HyperV specification give
> a firm statement that this is the expectation ?

#UD is a fault and its RIP always points to the instruction that caused
the exception.  But you of course know this already, so I'm not sure I
understand the question.

Paolo
Paolo Bonzini May 24, 2018, 4:22 p.m. UTC | #3
On 24/05/2018 17:50, Radim Krčmář wrote:
> If the hypercall was called from userspace or real mode, KVM injects #UD
> and then advances RIP, so it looks like #UD was caused by the following
> instruction.  This probably won't cause more than confusion, but could
> give an unexpected access to guest OS' instruction emulator.
> 
> Also, refactor the code to count hv hypercalls that were handled by the
> virt userspace.
> 
> Fixes: 6356ee0c9602 ("x86: Delay skip of emulated hypercall instruction")
> Signed-off-by: Radim Krčmář <rkrcmar@redhat.com>
> ---
>  arch/x86/kvm/hyperv.c | 19 +++++++++++--------
>  arch/x86/kvm/x86.c    | 12 ++++--------
>  2 files changed, 15 insertions(+), 16 deletions(-)
> 
> diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
> index 5708e951a5c6..46ff64da44ca 100644
> --- a/arch/x86/kvm/hyperv.c
> +++ b/arch/x86/kvm/hyperv.c
> @@ -1260,12 +1260,16 @@ static void kvm_hv_hypercall_set_result(struct kvm_vcpu *vcpu, u64 result)
>  	}
>  }
>  
> +static int kvm_hv_hypercall_complete(struct kvm_vcpu *vcpu, u64 result)
> +{
> +	kvm_hv_hypercall_set_result(vcpu, result);
> +	++vcpu->stat.hypercalls;
> +	return kvm_skip_emulated_instruction(vcpu);
> +}
> +
>  static int kvm_hv_hypercall_complete_userspace(struct kvm_vcpu *vcpu)
>  {
> -	struct kvm_run *run = vcpu->run;
> -
> -	kvm_hv_hypercall_set_result(vcpu, run->hyperv.u.hcall.result);
> -	return kvm_skip_emulated_instruction(vcpu);
> +	return kvm_hv_hypercall_complete(vcpu, vcpu->run->hyperv.u.hcall.result);
>  }
>  
>  static u16 kvm_hvcall_signal_event(struct kvm_vcpu *vcpu, bool fast, u64 param)
> @@ -1350,7 +1354,7 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
>  	/* Hypercall continuation is not supported yet */
>  	if (rep_cnt || rep_idx) {
>  		ret = HV_STATUS_INVALID_HYPERCALL_CODE;
> -		goto set_result;
> +		goto out;
>  	}
>  
>  	switch (code) {
> @@ -1381,9 +1385,8 @@ int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
>  		break;
>  	}
>  
> -set_result:
> -	kvm_hv_hypercall_set_result(vcpu, ret);
> -	return 1;
> +out:
> +	return kvm_hv_hypercall_complete(vcpu, ret);
>  }
>  
>  void kvm_hv_init_vm(struct kvm *kvm)
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 37dd9a9d050a..35a20fcfb661 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -6676,11 +6676,8 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
>  	unsigned long nr, a0, a1, a2, a3, ret;
>  	int op_64_bit;
>  
> -	if (kvm_hv_hypercall_enabled(vcpu->kvm)) {
> -		if (!kvm_hv_hypercall(vcpu))
> -			return 0;
> -		goto out;
> -	}
> +	if (kvm_hv_hypercall_enabled(vcpu->kvm))
> +		return kvm_hv_hypercall(vcpu);
>  
>  	nr = kvm_register_read(vcpu, VCPU_REGS_RAX);
>  	a0 = kvm_register_read(vcpu, VCPU_REGS_RBX);
> @@ -6701,7 +6698,7 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
>  
>  	if (kvm_x86_ops->get_cpl(vcpu) != 0) {
>  		ret = -KVM_EPERM;
> -		goto out_error;
> +		goto out;
>  	}
>  
>  	switch (nr) {
> @@ -6721,12 +6718,11 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
>  		ret = -KVM_ENOSYS;
>  		break;
>  	}
> -out_error:
> +out:
>  	if (!op_64_bit)
>  		ret = (u32)ret;
>  	kvm_register_write(vcpu, VCPU_REGS_RAX, ret);
>  
> -out:
>  	++vcpu->stat.hypercalls;
>  	return kvm_skip_emulated_instruction(vcpu);
>  }
> 


Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Radim Krčmář May 24, 2018, 5:01 p.m. UTC | #4
2018-05-24 18:22+0200, Paolo Bonzini:
> On 24/05/2018 18:08, Konrad Rzeszutek Wilk wrote:
> > On Thu, May 24, 2018 at 05:50:56PM +0200, Radim Krčmář wrote:
> >> If the hypercall was called from userspace or real mode, KVM injects #UD
> >> and then advances RIP, so it looks like #UD was caused by the following
> >> instruction.  This probably won't cause more than confusion, but could
> >> give an unexpected access to guest OS' instruction emulator.
> >
> > What does the SDM say about this? Or does the HyperV specification give
> > a firm statement that this is the expectation ?
> 
> #UD is a fault and its RIP always points to the instruction that caused
> the exception.  But you of course know this already, so I'm not sure I
> understand the question.

That was my reasoning.  And for completeness, TLFS only says this:

  Hypercalls can be invoked only from the most privileged guest processor
  mode. In the case of x64, this means protected mode with a current
  privilege level (CPL) of zero.  Although real-mode code runs with an
  effective CPL of zero, hypercalls are not allowed in real mode. An
  attempt to invoke a hypercall within an illegal processor mode will
  generate a #UD (undefined operation) exception.
Konrad Rzeszutek Wilk May 24, 2018, 5:32 p.m. UTC | #5
On Thu, May 24, 2018 at 07:01:04PM +0200, Radim Krčmář wrote:
> 2018-05-24 18:22+0200, Paolo Bonzini:
> > On 24/05/2018 18:08, Konrad Rzeszutek Wilk wrote:
> > > On Thu, May 24, 2018 at 05:50:56PM +0200, Radim Krčmář wrote:
> > >> If the hypercall was called from userspace or real mode, KVM injects #UD
> > >> and then advances RIP, so it looks like #UD was caused by the following
> > >> instruction.  This probably won't cause more than confusion, but could
> > >> give an unexpected access to guest OS' instruction emulator.
> > >
> > > What does the SDM say about this? Or does the HyperV specification give
> > > a firm statement that this is the expectation ?
> > 
> > #UD is a fault and its RIP always points to the instruction that caused
> > the exception.  But you of course know this already, so I'm not sure I
> > understand the question.
> 
> That was my reasoning.  And for completeness, TLFS only says this:
> 
>   Hypercalls can be invoked only from the most privileged guest processor
>   mode. In the case of x64, this means protected mode with a current
>   privilege level (CPL) of zero.  Although real-mode code runs with an
>   effective CPL of zero, hypercalls are not allowed in real mode. An
>   attempt to invoke a hypercall within an illegal processor mode will
>   generate a #UD (undefined operation) exception.

Which is weird b/c I remember seeing an bug where the opposite was requested -
that is VMCALL was allowed from ring 3 in Windows .. or maybe a similar bug
where ring3 did a vmcall and something was not handled correctly.

Either way the patch seems right.
Radim Krčmář May 26, 2018, 2:21 p.m. UTC | #6
2018-05-24 18:22+0200, Paolo Bonzini:
> Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>

Thanks, applied.
diff mbox

Patch

diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 5708e951a5c6..46ff64da44ca 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -1260,12 +1260,16 @@  static void kvm_hv_hypercall_set_result(struct kvm_vcpu *vcpu, u64 result)
 	}
 }
 
+static int kvm_hv_hypercall_complete(struct kvm_vcpu *vcpu, u64 result)
+{
+	kvm_hv_hypercall_set_result(vcpu, result);
+	++vcpu->stat.hypercalls;
+	return kvm_skip_emulated_instruction(vcpu);
+}
+
 static int kvm_hv_hypercall_complete_userspace(struct kvm_vcpu *vcpu)
 {
-	struct kvm_run *run = vcpu->run;
-
-	kvm_hv_hypercall_set_result(vcpu, run->hyperv.u.hcall.result);
-	return kvm_skip_emulated_instruction(vcpu);
+	return kvm_hv_hypercall_complete(vcpu, vcpu->run->hyperv.u.hcall.result);
 }
 
 static u16 kvm_hvcall_signal_event(struct kvm_vcpu *vcpu, bool fast, u64 param)
@@ -1350,7 +1354,7 @@  int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
 	/* Hypercall continuation is not supported yet */
 	if (rep_cnt || rep_idx) {
 		ret = HV_STATUS_INVALID_HYPERCALL_CODE;
-		goto set_result;
+		goto out;
 	}
 
 	switch (code) {
@@ -1381,9 +1385,8 @@  int kvm_hv_hypercall(struct kvm_vcpu *vcpu)
 		break;
 	}
 
-set_result:
-	kvm_hv_hypercall_set_result(vcpu, ret);
-	return 1;
+out:
+	return kvm_hv_hypercall_complete(vcpu, ret);
 }
 
 void kvm_hv_init_vm(struct kvm *kvm)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 37dd9a9d050a..35a20fcfb661 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6676,11 +6676,8 @@  int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 	unsigned long nr, a0, a1, a2, a3, ret;
 	int op_64_bit;
 
-	if (kvm_hv_hypercall_enabled(vcpu->kvm)) {
-		if (!kvm_hv_hypercall(vcpu))
-			return 0;
-		goto out;
-	}
+	if (kvm_hv_hypercall_enabled(vcpu->kvm))
+		return kvm_hv_hypercall(vcpu);
 
 	nr = kvm_register_read(vcpu, VCPU_REGS_RAX);
 	a0 = kvm_register_read(vcpu, VCPU_REGS_RBX);
@@ -6701,7 +6698,7 @@  int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 
 	if (kvm_x86_ops->get_cpl(vcpu) != 0) {
 		ret = -KVM_EPERM;
-		goto out_error;
+		goto out;
 	}
 
 	switch (nr) {
@@ -6721,12 +6718,11 @@  int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
 		ret = -KVM_ENOSYS;
 		break;
 	}
-out_error:
+out:
 	if (!op_64_bit)
 		ret = (u32)ret;
 	kvm_register_write(vcpu, VCPU_REGS_RAX, ret);
 
-out:
 	++vcpu->stat.hypercalls;
 	return kvm_skip_emulated_instruction(vcpu);
 }