Description: x86: make arch_set_info_guest() preemptible
 .. as the root page table validation (and the dropping of an eventual
 old one) can require meaningful amounts of time.
From: Jan Beulich <jbeulich@suse.com>
Origin: upstream
Id: CVE-2013-1918 XSA-45
---
--- a/xen/arch/x86/domain.c
+++ b/xen/arch/x86/domain.c
@@ -676,6 +676,7 @@ int arch_set_info_guest(
 {
     struct domain *d = v->domain;
     unsigned long cr3_pfn = INVALID_MFN;
+    struct page_info *cr3_page;
     unsigned long flags, cr4;
     int i, rc = 0, compat;
 
@@ -815,72 +816,103 @@ int arch_set_info_guest(
     if ( rc != 0 )
         return rc;
 
+    set_bit(_VPF_in_reset, &v->pause_flags);
+
     if ( !compat )
-    {
         cr3_pfn = gmfn_to_mfn(d, xen_cr3_to_pfn(c.nat->ctrlreg[3]));
+#ifdef __x86_64__
+    else
+        cr3_pfn = gmfn_to_mfn(d, compat_cr3_to_pfn(c.cmp->ctrlreg[3]));
+#endif
+    cr3_page = mfn_to_page(cr3_pfn);
 
-        if ( !mfn_valid(cr3_pfn) ||
-             (paging_mode_refcounts(d)
-              ? !get_page(mfn_to_page(cr3_pfn), d)
-              : !get_page_and_type(mfn_to_page(cr3_pfn), d,
-                                   PGT_base_page_table)) )
-        {
-            destroy_gdt(v);
-            return -EINVAL;
-        }
+    if ( !mfn_valid(cr3_pfn) || !get_page(cr3_page, d) )
+    {
+        cr3_page = NULL;
+        rc = -EINVAL;
+    }
+    else if ( paging_mode_refcounts(d) )
+        /* nothing */;
+    else if ( cr3_page == v->arch.old_guest_table )
+    {
+        v->arch.old_guest_table = NULL;
+        put_page(cr3_page);
+    }
+    else
+    {
+        /*
+         * Since v->arch.guest_table{,_user} are both NULL, this effectively
+         * is just a call to put_old_guest_table().
+         */
+        if ( !compat )
+            rc = vcpu_destroy_pagetables(v);
+        if ( !rc )
+            rc = get_page_type_preemptible(cr3_page,
+                                           !compat ? PGT_root_page_table
+                                                   : PGT_l3_page_table);
+        if ( rc == -EINTR )
+            rc = -EAGAIN;
+    }
 
+    if ( rc )
+        /* handled below */;
+    else if ( !compat )
+    {
         v->arch.guest_table = pagetable_from_pfn(cr3_pfn);
 
 #ifdef __x86_64__
         if ( c.nat->ctrlreg[1] )
         {
             cr3_pfn = gmfn_to_mfn(d, xen_cr3_to_pfn(c.nat->ctrlreg[1]));
+            cr3_page = mfn_to_page(cr3_pfn);
 
-            if ( !mfn_valid(cr3_pfn) ||
-                 (paging_mode_refcounts(d)
-                  ? !get_page(mfn_to_page(cr3_pfn), d)
-                  : !get_page_and_type(mfn_to_page(cr3_pfn), d,
-                                       PGT_base_page_table)) )
+            if ( !mfn_valid(cr3_pfn) || !get_page(cr3_page, d) )
             {
-                cr3_pfn = pagetable_get_pfn(v->arch.guest_table);
-                v->arch.guest_table = pagetable_null();
-                if ( paging_mode_refcounts(d) )
-                    put_page(mfn_to_page(cr3_pfn));
-                else
-                    put_page_and_type(mfn_to_page(cr3_pfn));
-                destroy_gdt(v);
-                return -EINVAL;
+                cr3_page = NULL;
+                rc = -EINVAL;
+            }
+            else if ( !paging_mode_refcounts(d) )
+            {
+                rc = get_page_type_preemptible(cr3_page, PGT_root_page_table);
+                switch ( rc )
+                {
+                case -EINTR:
+                    rc = -EAGAIN;
+                case -EAGAIN:
+                    v->arch.old_guest_table =
+                        pagetable_get_page(v->arch.guest_table);
+                    v->arch.guest_table = pagetable_null();
+                    break;
+                }
             }
 
-            v->arch.guest_table_user = pagetable_from_pfn(cr3_pfn);
+            if ( !rc )
+                v->arch.guest_table_user = pagetable_from_pfn(cr3_pfn);
         }
         else if ( !(flags & VGCF_in_kernel) )
         {
-            destroy_gdt(v);
-            return -EINVAL;
+            cr3_page = NULL;
+            rc = -EINVAL;
         }
     }
     else
     {
         l4_pgentry_t *l4tab;
 
-        cr3_pfn = gmfn_to_mfn(d, compat_cr3_to_pfn(c.cmp->ctrlreg[3]));
-
-        if ( !mfn_valid(cr3_pfn) ||
-             (paging_mode_refcounts(d)
-              ? !get_page(mfn_to_page(cr3_pfn), d)
-              : !get_page_and_type(mfn_to_page(cr3_pfn), d,
-                                   PGT_l3_page_table)) )
-        {
-            destroy_gdt(v);
-            return -EINVAL;
-        }
-
         l4tab = __va(pagetable_get_paddr(v->arch.guest_table));
         *l4tab = l4e_from_pfn(
             cr3_pfn, _PAGE_PRESENT|_PAGE_RW|_PAGE_USER|_PAGE_ACCESSED);
 #endif
     }
+    if ( rc )
+    {
+        if ( cr3_page )
+            put_page(cr3_page);
+        destroy_gdt(v);
+        return rc;
+    }
+
+    clear_bit(_VPF_in_reset, &v->pause_flags);
 
     if ( v->vcpu_id == 0 )
         update_domain_wallclock_time(d);
--- a/xen/common/compat/domain.c
+++ b/xen/common/compat/domain.c
@@ -52,6 +52,10 @@ int compat_vcpu_op(int cmd, int vcpuid, 
             rc = boot_vcpu(d, vcpuid, cmp_ctxt);
         domain_unlock(d);
 
+        if ( rc == -EAGAIN )
+            rc = hypercall_create_continuation(__HYPERVISOR_vcpu_op, "iih",
+                                               cmd, vcpuid, arg);
+
         xfree(cmp_ctxt);
         break;
     }
--- a/xen/common/domain.c
+++ b/xen/common/domain.c
@@ -842,6 +842,11 @@ long do_vcpu_op(int cmd, int vcpuid, XEN
         domain_unlock(d);
 
         xfree(ctxt);
+
+        if ( rc == -EAGAIN )
+            rc = hypercall_create_continuation(__HYPERVISOR_vcpu_op, "iih",
+                                               cmd, vcpuid, arg);
+
         break;
 
     case VCPUOP_up:
--- a/xen/common/domctl.c
+++ b/xen/common/domctl.c
@@ -318,6 +318,10 @@ long do_domctl(XEN_GUEST_HANDLE(xen_domc
             domain_pause(d);
             ret = arch_set_info_guest(v, c);
             domain_unpause(d);
+
+            if ( ret == -EAGAIN )
+                ret = hypercall_create_continuation(
+                          __HYPERVISOR_domctl, "h", u_domctl);
         }
 
     svc_out:
