KVM: x86: handle idiv overflow at kvm_write_tsc
authorMarcelo Tosatti <mtosatti@redhat.com>
Wed, 12 Jun 2013 02:31:12 +0000 (23:31 -0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sun, 5 Oct 2014 21:54:11 +0000 (14:54 -0700)
commit 8915aa27d5efbb9185357175b0acf884325565f9 upstream.

Its possible that idivl overflows (due to large delta stored in usdiff,
valid scenario).

Create an exception handler to catch the overflow exception (division by zero
is protected by vcpu->arch.virtual_tsc_khz check), and interpret it accordingly
(delta is larger than USEC_PER_SEC).

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=969644

Signed-off-by: Marcelo Tosatti <mtosatti@redhat.com>
Signed-off-by: Gleb Natapov <gleb@redhat.com>
Signed-off-by: Philipp Hahn <hahn@univention.de>
Tested-by: Philipp Hahn <hahn@univention.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
arch/x86/kvm/x86.c

index 1be0a9e75d1fe7eb2931f45d70081724219d2f5f..e8753555f1446554b44462beb894579f52d4f1cc 100644 (file)
@@ -1196,20 +1196,37 @@ void kvm_write_tsc(struct kvm_vcpu *vcpu, struct msr_data *msr)
        elapsed = ns - kvm->arch.last_tsc_nsec;
 
        if (vcpu->arch.virtual_tsc_khz) {
+               int faulted = 0;
+
                /* n.b - signed multiplication and division required */
                usdiff = data - kvm->arch.last_tsc_write;
 #ifdef CONFIG_X86_64
                usdiff = (usdiff * 1000) / vcpu->arch.virtual_tsc_khz;
 #else
                /* do_div() only does unsigned */
-               asm("idivl %2; xor %%edx, %%edx"
-               : "=A"(usdiff)
-               : "A"(usdiff * 1000), "rm"(vcpu->arch.virtual_tsc_khz));
+               asm("1: idivl %[divisor]\n"
+                   "2: xor %%edx, %%edx\n"
+                   "   movl $0, %[faulted]\n"
+                   "3:\n"
+                   ".section .fixup,\"ax\"\n"
+                   "4: movl $1, %[faulted]\n"
+                   "   jmp  3b\n"
+                   ".previous\n"
+
+               _ASM_EXTABLE(1b, 4b)
+
+               : "=A"(usdiff), [faulted] "=r" (faulted)
+               : "A"(usdiff * 1000), [divisor] "rm"(vcpu->arch.virtual_tsc_khz));
+
 #endif
                do_div(elapsed, 1000);
                usdiff -= elapsed;
                if (usdiff < 0)
                        usdiff = -usdiff;
+
+               /* idivl overflow => difference is larger than USEC_PER_SEC */
+               if (faulted)
+                       usdiff = USEC_PER_SEC;
        } else
                usdiff = USEC_PER_SEC; /* disable TSC match window below */