Subject: gnttab: don't blindly free status pages upon version change
From: Jan Beulich <[email-masked]>

There may still be active mappings, which would trigger the respective
BUG_ON(). Split the loop into one dealing with the page attributes and
the second (when the first fully passed) freeing the pages. Return an
error if any pages still have pending references.

This is part of XSA-255.

Signed-off-by: Jan Beulich <[email-masked]>
Reviewed-by: Stefano Stabellini <[email-masked]>
Reviewed-by: Andrew Cooper <[email-masked]>
---
v4: Add gprintk(XENLOG_ERR, ...) to domain_crash() invocations.
v3: Call guest_physmap_remove_page() from gnttab_map_frame(), making the
    code unconditional at the same time. Re-base over changes to first
    patch.
v2: Also deal with translated guests.

--- a/xen/common/grant_table.c
+++ b/xen/common/grant_table.c
@@ -1636,23 +1636,74 @@ status_alloc_failed:
     return -ENOMEM;
 }
 
-static void
+static int
 gnttab_unpopulate_status_frames(struct domain *d, struct grant_table *gt)
 {
-    int i;
+    unsigned int i;
 
     for ( i = 0; i < nr_status_frames(gt); i++ )
     {
         struct page_info *pg = virt_to_page(gt->status[i]);
+        gfn_t gfn = gnttab_get_frame_gfn(gt, true, i);
+
+        /*
+         * For translated domains, recovering from failure after partial
+         * changes were made is more complicated than it seems worth
+         * implementing at this time. Hence respective error paths below
+         * crash the domain in such a case.
+         */
+        if ( paging_mode_translate(d) )
+        {
+            int rc = gfn_eq(gfn, INVALID_GFN)
+                     ? 0
+                     : guest_physmap_remove_page(d, gfn,
+                                                 _mfn(page_to_mfn(pg)), 0);
+
+            if ( rc )
+            {
+                gprintk(XENLOG_ERR,
+                        "Could not remove status frame %u (GFN %#lx) from P2M\n",
+                        i, gfn_x(gfn));
+                domain_crash(d);
+                return rc;
+            }
+            gnttab_set_frame_gfn(gt, true, i, INVALID_GFN);
+        }
 
         BUG_ON(page_get_owner(pg) != d);
         if ( test_and_clear_bit(_PGC_allocated, &pg->count_info) )
             put_page(pg);
-        BUG_ON(pg->count_info & ~PGC_xen_heap);
+
+        if ( pg->count_info & ~PGC_xen_heap )
+        {
+            if ( paging_mode_translate(d) )
+            {
+                gprintk(XENLOG_ERR,
+                        "Wrong page state %#lx of status frame %u (GFN %#lx)\n",
+                        pg->count_info, i, gfn_x(gfn));
+                domain_crash(d);
+            }
+            else
+            {
+                if ( get_page(pg, d) )
+                    set_bit(_PGC_allocated, &pg->count_info);
+                while ( i-- )
+                    gnttab_create_status_page(d, gt, i);
+            }
+            return -EBUSY;
+        }
+
+        page_set_owner(pg, NULL);
+    }
+
+    for ( i = 0; i < nr_status_frames(gt); i++ )
+    {
         free_xenheap_page(gt->status[i]);
         gt->status[i] = NULL;
     }
     gt->nr_status_frames = 0;
+
+    return 0;
 }
 
 /*
@@ -2962,8 +3013,9 @@ gnttab_set_version(XEN_GUEST_HANDLE_PARA
         break;
     }
 
-    if ( op.version < 2 && gt->gt_version == 2 )
-        gnttab_unpopulate_status_frames(currd, gt);
+    if ( op.version < 2 && gt->gt_version == 2 &&
+         (res = gnttab_unpopulate_status_frames(currd, gt)) != 0 )
+        goto out_unlock;
 
     /* Make sure there's no crud left over from the old version. */
     for ( i = 0; i < nr_grant_frames(gt); i++ )
@@ -3803,6 +3855,11 @@ int gnttab_map_frame(struct domain *d, u
             rc = -EINVAL;
     }
 
+    if ( !rc && paging_mode_translate(d) &&
+         !gfn_eq(gnttab_get_frame_gfn(gt, status, idx), INVALID_GFN) )
+        rc = guest_physmap_remove_page(d, gnttab_get_frame_gfn(gt, status, idx),
+                                       *mfn, 0);
+
     if ( !rc )
         gnttab_set_frame_gfn(gt, status, idx, gfn);
 
--- a/xen/include/asm-arm/grant_table.h
+++ b/xen/include/asm-arm/grant_table.h
@@ -73,6 +73,11 @@ static inline unsigned int gnttab_dom0_m
             (gfn);                                                       \
     } while ( 0 )
 
+#define gnttab_get_frame_gfn(gt, st, idx) ({                             \
+   _gfn((st) ? gnttab_status_gmfn(NULL, gt, idx)                         \
+             : gnttab_shared_gmfn(NULL, gt, idx));                       \
+})
+
 #define gnttab_create_shared_page(d, t, i)                               \
     do {                                                                 \
         share_xen_page_with_guest(                                       \
--- a/xen/include/asm-x86/grant_table.h
+++ b/xen/include/asm-x86/grant_table.h
@@ -47,6 +47,12 @@ static inline unsigned int gnttab_dom0_m
 #define gnttab_init_arch(gt) 0
 #define gnttab_destroy_arch(gt) do {} while ( 0 )
 #define gnttab_set_frame_gfn(gt, st, idx, gfn) do {} while ( 0 )
+#define gnttab_get_frame_gfn(gt, st, idx) ({                             \
+    unsigned long mfn_ = (st) ? gnttab_status_mfn(gt, idx)               \
+                              : gnttab_shared_mfn(gt, idx);              \
+    unsigned long gpfn_ = get_gpfn_from_mfn(mfn_);                       \
+    VALID_M2P(gpfn_) ? _gfn(gpfn_) : INVALID_GFN;                        \
+})
 
 #define gnttab_create_shared_page(d, t, i)                               \
     do {                                                                 \
@@ -63,11 +69,11 @@ static inline unsigned int gnttab_dom0_m
     } while ( 0 )

-#define gnttab_shared_mfn(d, t, i)                      \
+#define gnttab_shared_mfn(t, i)                         \
     ((virt_to_maddr((t)->shared_raw[i]) >> PAGE_SHIFT))
 
 #define gnttab_shared_gmfn(d, t, i)                     \
-    (mfn_to_gmfn(d, gnttab_shared_mfn(d, t, i)))
+    (mfn_to_gmfn(d, gnttab_shared_mfn(t, i)))

 #define gnttab_status_mfn(t, i)                         \