[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-changelog] [xen stable-4.9] x86/mm: Set old_guest_table when destroying vcpu pagetables



commit 248f22e0b67f4bd22d8175f770d02f52ca780a64
Author:     George Dunlap <george.dunlap@xxxxxxxxxx>
AuthorDate: Wed Dec 11 15:52:55 2019 +0100
Commit:     Jan Beulich <jbeulich@xxxxxxxx>
CommitDate: Wed Dec 11 15:52:55 2019 +0100

    x86/mm: Set old_guest_table when destroying vcpu pagetables
    
    Changeset 6c4efc1eba ("x86/mm: Don't drop a type ref unless you held a
    ref to begin with"), part of XSA-299, changed the calling discipline
    of put_page_type() such that if put_page_type() returned -ERESTART
    (indicating a partially de-validated page), subsequent calls to
    put_page_type() must be called with PTF_partial_set.  If called on a
    partially de-validated page but without PTF_partial_set, Xen will
    BUG(), because to do otherwise would risk opening up the kind of
    privilege escalation bug described in XSA-299.
    
    One place this was missed was in vcpu_destroy_pagetables().
    put_page_and_type_preemptible() is called, but on -ERESTART, the
    entire operation is simply restarted, causing put_page_type() to be
    called on a partially de-validated page without PTF_partial_set.  The
    result was that if such an operation were interrupted, Xen would hit a
    BUG().
    
    Fix this by having vcpu_destroy_pagetables() consistently pass off
    interrupted de-validations to put_old_page_type():
    - Unconditionally clear references to the page, even if
      put_page_and_type failed
    - Set old_guest_table and old_guest_table_partial appropriately
    
    While here, do some refactoring:
    
     - Move clearing of arch.cr3 to the top of the function
    
     - Now that clearing is unconditional, move the unmap to the same
       conditional as the l4tab mapping.  This also allows us to reduce
       the scope of the l4tab variable.
    
     - Avoid code duplication by looping to drop references on
       guest_table_user
    
    This is part of XSA-310.
    
    Reported-by: Sarah Newman <srn@xxxxxxxxx>
    Signed-off-by: George Dunlap <george.dunlap@xxxxxxxxxx>
    Reviewed-by: Jan Beulich <jbeulich@xxxxxxxx>
    master commit: ececa12b2c4c8e4433e4f9be83f5c668ae36fe08
    master date: 2019-12-11 14:54:13 +0100
---
 xen/arch/x86/mm.c | 75 ++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 47 insertions(+), 28 deletions(-)

diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
index 531bca7a1d..cd5f0ef4f7 100644
--- a/xen/arch/x86/mm.c
+++ b/xen/arch/x86/mm.c
@@ -3470,40 +3470,36 @@ int put_old_guest_table(struct vcpu *v)
 int vcpu_destroy_pagetables(struct vcpu *v)
 {
     unsigned long mfn = pagetable_get_pfn(v->arch.guest_table);
-    struct page_info *page;
-    l4_pgentry_t *l4tab = NULL;
+    struct page_info *page = NULL;
     int rc = put_old_guest_table(v);
+    bool put_guest_table_user = false;
 
     if ( rc )
         return rc;
 
+    v->arch.cr3 = 0;
+
+    /*
+     * Get the top-level guest page; either the guest_table itself, for
+     * 64-bit, or the top-level l4 entry for 32-bit.  Either way, remove
+     * the reference to that page.
+     */
     if ( is_pv_32bit_vcpu(v) )
     {
-        l4tab = map_domain_page(_mfn(mfn));
-        mfn = l4e_get_pfn(*l4tab);
-    }
+        l4_pgentry_t *l4tab = map_domain_page(_mfn(mfn));
 
-    if ( mfn )
-    {
-        page = mfn_to_page(mfn);
-        if ( paging_mode_refcounts(v->domain) )
-            put_page(page);
-        else
-            rc = put_page_and_type_preemptible(page);
-    }
-
-    if ( l4tab )
-    {
-        if ( !rc )
-            l4e_write(l4tab, l4e_empty());
+        mfn = l4e_get_pfn(*l4tab);
+        l4e_write(l4tab, l4e_empty());
         unmap_domain_page(l4tab);
     }
-    else if ( !rc )
+    else
     {
         v->arch.guest_table = pagetable_null();
+        put_guest_table_user = true;
+    }
 
-        /* Drop ref to guest_table_user (from MMUEXT_NEW_USER_BASEPTR) */
-        mfn = pagetable_get_pfn(v->arch.guest_table_user);
+    /* Free that page if non-zero */
+    do {
         if ( mfn )
         {
             page = mfn_to_page(mfn);
@@ -3511,18 +3507,41 @@ int vcpu_destroy_pagetables(struct vcpu *v)
                 put_page(page);
             else
                 rc = put_page_and_type_preemptible(page);
+            mfn = 0;
         }
-        if ( !rc )
-            v->arch.guest_table_user = pagetable_null();
-    }
 
-    v->arch.cr3 = 0;
+        if ( !rc && put_guest_table_user )
+        {
+            /* Drop ref to guest_table_user (from MMUEXT_NEW_USER_BASEPTR) */
+            mfn = pagetable_get_pfn(v->arch.guest_table_user);
+            v->arch.guest_table_user = pagetable_null();
+            put_guest_table_user = false;
+        }
+    } while ( mfn );
 
     /*
-     * put_page_and_type_preemptible() is liable to return -EINTR. The
-     * callers of us expect -ERESTART so convert it over.
+     * If a "put" operation was interrupted, finish things off in
+     * put_old_guest_table() when the operation is restarted.
      */
-    return rc != -EINTR ? rc : -ERESTART;
+    switch ( rc )
+    {
+    case -EINTR:
+    case -ERESTART:
+        v->arch.old_guest_ptpg = NULL;
+        v->arch.old_guest_table = page;
+        v->arch.old_guest_table_partial = (rc == -ERESTART);
+        rc = -ERESTART;
+        break;
+    default:
+        /*
+         * Failure to 'put' a page may cause it to leak, but that's
+         * less bad than a crash.
+         */
+        ASSERT(rc == 0);
+        break;
+    }
+
+    return rc;
 }
 
 int new_guest_cr3(unsigned long mfn)
--
generated by git-patchbot for /home/xen/git/xen.git#stable-4.9

_______________________________________________
Xen-changelog mailing list
Xen-changelog@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/xen-changelog

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.