]> xenbits.xensource.com Git - xen.git/commitdiff
x86: make page table unpinning preemptible
authorJan Beulich <jbeulich@suse.com>
Thu, 2 May 2013 15:26:13 +0000 (17:26 +0200)
committerJan Beulich <jbeulich@suse.com>
Thu, 2 May 2013 15:26:13 +0000 (17:26 +0200)
... as it may take significant amounts of time.

Since we can't re-invoke the operation in a second attempt, the
continuation logic must be slightly tweaked so that we make sure
do_mmuext_op() gets run one more time even when the preempted unpin
operation was the last one in a batch.

This is part of CVE-2013-1918 / XSA-45.

Signed-off-by: Jan Beulich <jbeulich@suse.com>
Acked-by: Tim Deegan <tim@xen.org>
master commit: a3e049f8e86fe18e3b87f18dc0c7be665026fd9f
master date: 2013-05-02 16:39:06 +0200

xen/arch/x86/mm.c
xen/arch/x86/x86_64/compat/mm.c

index 2567c5e78e7cb7eb00744b473f769bf020dbf820..9c3d251ae5c0ca77ed44da1a5bf2fe91ddf2b4de 100644 (file)
@@ -3040,6 +3040,14 @@ long do_mmuext_op(
         return rc;
     }
 
+    if ( unlikely(count == MMU_UPDATE_PREEMPTED) &&
+         likely(guest_handle_is_null(uops)) )
+    {
+        /* See the curr->arch.old_guest_table related
+         * hypercall_create_continuation() below. */
+        return (int)foreigndom;
+    }
+
     if ( unlikely(count & MMU_UPDATE_PREEMPTED) )
     {
         count &= ~MMU_UPDATE_PREEMPTED;
@@ -3063,7 +3071,7 @@ long do_mmuext_op(
 
     for ( i = 0; i < count; i++ )
     {
-        if ( hypercall_preempt_check() )
+        if ( curr->arch.old_guest_table || hypercall_preempt_check() )
         {
             rc = -EAGAIN;
             break;
@@ -3181,7 +3189,17 @@ long do_mmuext_op(
                 break;
             }
 
-            put_page_and_type(page);
+            switch ( rc = put_page_and_type_preemptible(page, 1) )
+            {
+            case -EINTR:
+            case -EAGAIN:
+                curr->arch.old_guest_table = page;
+                rc = 0;
+                break;
+            default:
+                BUG_ON(rc);
+                break;
+            }
             put_page(page);
 
             /* A page is dirtied when its pin status is cleared. */
@@ -3487,9 +3505,27 @@ long do_mmuext_op(
     }
 
     if ( rc == -EAGAIN )
+    {
+        ASSERT(i < count);
         rc = hypercall_create_continuation(
             __HYPERVISOR_mmuext_op, "hihi",
             uops, (count - i) | MMU_UPDATE_PREEMPTED, pdone, foreigndom);
+    }
+    else if ( curr->arch.old_guest_table )
+    {
+        XEN_GUEST_HANDLE(void) null;
+
+        ASSERT(rc || i == count);
+        set_xen_guest_handle(null, NULL);
+        /*
+         * In order to have a way to communicate the final return value to
+         * our continuation, we pass this in place of "foreigndom", building
+         * on the fact that this argument isn't needed anymore.
+         */
+        rc = hypercall_create_continuation(
+                __HYPERVISOR_mmuext_op, "hihi", null,
+                MMU_UPDATE_PREEMPTED, null, rc);
+    }
 
     put_pg_owner(pg_owner);
 
index 7b6ad6dc78f392a599d513147a47e1b1caa76a75..6ad41d4fd8bcde57e722e07f410c2affac07efb5 100644 (file)
@@ -222,6 +222,13 @@ int compat_mmuext_op(XEN_GUEST_HANDLE(mmuext_op_compat_t) cmp_uops,
     int rc = 0;
     XEN_GUEST_HANDLE(mmuext_op_t) nat_ops;
 
+    if ( unlikely(count == MMU_UPDATE_PREEMPTED) &&
+         likely(guest_handle_is_null(cmp_uops)) )
+    {
+        set_xen_guest_handle(nat_ops, NULL);
+        return do_mmuext_op(nat_ops, count, pdone, foreigndom);
+    }
+
     preempt_mask = count & MMU_UPDATE_PREEMPTED;
     count ^= preempt_mask;
 
@@ -324,12 +331,18 @@ int compat_mmuext_op(XEN_GUEST_HANDLE(mmuext_op_compat_t) cmp_uops,
                 guest_handle_add_offset(nat_ops, i - left);
                 guest_handle_subtract_offset(cmp_uops, left);
                 left = 1;
-                BUG_ON(!hypercall_xlat_continuation(&left, 0x01, nat_ops, cmp_uops));
-                BUG_ON(left != arg1);
-                if (!test_bit(_MCSF_in_multicall, &mcs->flags))
-                    regs->_ecx += count - i;
+                if ( arg1 != MMU_UPDATE_PREEMPTED )
+                {
+                    BUG_ON(!hypercall_xlat_continuation(&left, 0x01, nat_ops,
+                                                        cmp_uops));
+                    if ( !test_bit(_MCSF_in_multicall, &mcs->flags) )
+                        regs->_ecx += count - i;
+                    else
+                        mcs->compat_call.args[1] += count - i;
+                }
                 else
-                    mcs->compat_call.args[1] += count - i;
+                    BUG_ON(hypercall_xlat_continuation(&left, 0));
+                BUG_ON(left != arg1);
             }
             else
                 BUG_ON(err > 0);