Description: x86: make new_guest_cr3() preemptible
 ... as it may take significant amounts of time.
From: Jan Beulich <jbeulich@suse.com>
Origin: upstream
Id: CVE-2013-1918 XSA-45
---
--- a/xen/arch/x86/mm.c
+++ b/xen/arch/x86/mm.c
@@ -2806,44 +2806,69 @@ int new_guest_cr3(unsigned long mfn)
 {
     struct vcpu *curr = current;
     struct domain *d = curr->domain;
-    int okay;
+    int rc;
     unsigned long old_base_mfn;
 
 #ifdef __x86_64__
     if ( is_pv_32on64_domain(d) )
     {
-        okay = paging_mode_refcounts(d)
-            ? 0 /* Old code was broken, but what should it be? */
-            : mod_l4_entry(
+        rc = paging_mode_refcounts(d)
+             ? -EINVAL /* Old code was broken, but what should it be? */
+             : mod_l4_entry(
                     __va(pagetable_get_paddr(curr->arch.guest_table)),
                     l4e_from_pfn(
                         mfn,
                         (_PAGE_PRESENT|_PAGE_RW|_PAGE_USER|_PAGE_ACCESSED)),
-                    pagetable_get_pfn(curr->arch.guest_table), 0, 0, curr) == 0;
-        if ( unlikely(!okay) )
+                    pagetable_get_pfn(curr->arch.guest_table), 0, 1, curr);
+        switch ( rc )
         {
+        case 0:
+            break;
+        case -EINTR:
+        case -EAGAIN:
+            return -EAGAIN;
+        default:
             MEM_LOG("Error while installing new compat baseptr %lx", mfn);
-            return 0;
+            return rc;
         }
 
         invalidate_shadow_ldt(curr, 0);
         write_ptbase(curr);
 
-        return 1;
+        return 0;
     }
 #endif
-    okay = paging_mode_refcounts(d)
-        ? get_page_from_pagenr(mfn, d)
-        : !get_page_and_type_from_pagenr(mfn, PGT_root_page_table, d, 0, 0);
-    if ( unlikely(!okay) )
+    rc = put_old_guest_table(curr);
+    if ( unlikely(rc) )
+        return rc;
+
+    old_base_mfn = pagetable_get_pfn(curr->arch.guest_table);
+    /*
+     * This is particularly important when getting restarted after the
+     * previous attempt got preempted in the put-old-MFN phase.
+     */
+    if ( old_base_mfn == mfn )
     {
-        MEM_LOG("Error while installing new baseptr %lx", mfn);
+        write_ptbase(curr);
         return 0;
     }
 
-    invalidate_shadow_ldt(curr, 0);
+    rc = paging_mode_refcounts(d)
+         ? (get_page_from_pagenr(mfn, d) ? 0 : -EINVAL)
+         : get_page_and_type_from_pagenr(mfn, PGT_root_page_table, d, 0, 1);
+    switch ( rc )
+    {
+    case 0:
+        break;
+    case -EINTR:
+    case -EAGAIN:
+        return -EAGAIN;
+    default:
+        MEM_LOG("Error while installing new baseptr %lx", mfn);
+        return rc;
+    }
 
-    old_base_mfn = pagetable_get_pfn(curr->arch.guest_table);
+    invalidate_shadow_ldt(curr, 0);
 
     curr->arch.guest_table = pagetable_from_pfn(mfn);
     update_cr3(curr);
@@ -2852,13 +2877,25 @@ int new_guest_cr3(unsigned long mfn)
 
     if ( likely(old_base_mfn != 0) )
     {
+        struct page_info *page = mfn_to_page(old_base_mfn);
+
         if ( paging_mode_refcounts(d) )
-            put_page(mfn_to_page(old_base_mfn));
+            put_page(page);
         else
-            put_page_and_type(mfn_to_page(old_base_mfn));
+            switch ( rc = put_page_and_type_preemptible(page, 1) )
+            {
+            case -EINTR:
+                rc = -EAGAIN;
+            case -EAGAIN:
+                curr->arch.old_guest_table = page;
+                break;
+            default:
+                BUG_ON(rc);
+                break;
+            }
     }
 
-    return 1;
+    return rc;
 }
 
 static struct domain *get_pg_owner(domid_t domid)
@@ -3154,7 +3191,8 @@ long do_mmuext_op(
         }
 
         case MMUEXT_NEW_BASEPTR:
-            okay = new_guest_cr3(gmfn_to_mfn(d, op.arg1.mfn));
+            rc = new_guest_cr3(gmfn_to_mfn(d, op.arg1.mfn));
+            okay = !rc;
             break;
         
 #ifdef __x86_64__
--- a/xen/arch/x86/traps.c
+++ b/xen/arch/x86/traps.c
@@ -2317,8 +2317,15 @@ static int emulate_privileged_op(struct 
                 rc = new_guest_cr3(gmfn_to_mfn(v->domain, compat_cr3_to_pfn(*reg)));
 #endif
             domain_unlock(v->domain);
-            if ( rc == 0 ) /* not okay */
+            switch ( rc )
+            {
+            case 0:
+                break;
+            case -EAGAIN: /* retry after preemption */
+                goto skip;
+            default:      /* not okay */
                 goto fail;
+            }
             break;
 
         case 4: /* Write CR4 */
