From: Andrew Cooper <andrew.cooper3@citrix.com>
Date: Mon, 19 Oct 2020 15:51:22 +0100
Subject: [PATCH 2/2] x86/pv: Flush TLB in response to paging structure changes

With MMU_UPDATE, a PV guest can make changes to higher level pagetables.  This
is safe from Xen's point of view (as the update only affects guest mappings),
and the guest is required to flush (if necessary) after making updates.

However, Xen's use of linear pagetables (UPDATE_VA_MAPPING, GNTTABOP_map,
writeable pagetables, etc.) is an implementation detail outside of the
API/ABI.

Changes in the paging structure require invalidations in the linear pagetable
range for subsequent accesses into the linear pagetables to access non-stale
mappings.  Xen must provide suitable flushing to prevent intermixed guest
actions from accidentally accessing/modifying the wrong pagetable.

For all L2 and higher modifications, flush the TLB.  PV guests cannot create
L2 or higher entries with the Global bit set, so no mappings established in
the linear range can be global.  (This could in principle be an order 39 flush
starting at LINEAR_PT_VIRT_START, but no such mechanism exists in practice.)

Express the necessary flushes as a set of booleans which accumulate across the
operation.  Comment the flushing logic extensively.

This is XSA-286.

Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
Reviewed-by: Jan Beulich <jbeulich@suse.com>

--- xen-4.0.3-testing.orig/xen/arch/x86/mm.c
+++ xen-4.0.3-testing/xen/arch/x86/mm.c
@@ -3911,7 +3911,7 @@ int do_mmu_update(
     struct vcpu *curr = current, *v = curr;
     struct domain *d = current->domain, *pt_owner = d, *pg_owner;
     struct domain_mmap_cache mapcache;
-    bool_t sync_guest = 0;
+    bool_t flush_linear_pt = 0;
     int rc = put_old_guest_table(curr);
 
     if ( unlikely(rc) )
@@ -4111,6 +4111,8 @@ int do_mmu_update(
 
                     rc = mod_l2_entry(va, l2e, mfn,
                                       cmd == MMU_PT_UPDATE_PRESERVE_AD, v);
+                    if ( !rc )
+                        flush_linear_pt = 1;
                 }
                 break;
                 case PGT_l3_page_table:
@@ -4139,6 +4141,8 @@ int do_mmu_update(
 
                     rc = mod_l3_entry(va, l3e, mfn,
                                       cmd == MMU_PT_UPDATE_PRESERVE_AD, v);
+                    if ( !rc )
+                        flush_linear_pt = 1;
                 }
                 break;
 #if CONFIG_PAGING_LEVELS >= 4
@@ -4168,18 +4172,8 @@ int do_mmu_update(
 
                     rc = mod_l4_entry(va, l4e, mfn,
                                       cmd == MMU_PT_UPDATE_PRESERVE_AD, v);
-                    /*
-                     * No need to sync if all uses of the page can be accounted
-                     * to the page lock we hold, its pinned status, and uses on
-                     * this (v)CPU.
-                     */
-                    if ( !rc && this_cpu(root_pgt) &&
-                         ((page->u.inuse.type_info & PGT_count_mask) >
-                          (1 + !!(page->u.inuse.type_info & PGT_pinned) +
-                           (pagetable_get_pfn(curr->arch.guest_table) == mfn) +
-                           (pagetable_get_pfn(curr->arch.guest_table_user) ==
-                            mfn))) )
-                        sync_guest = 1;
+                    if ( !rc )
+                        flush_linear_pt = 1;
                 }
                 break;
 #endif
@@ -4271,16 +4265,20 @@ int do_mmu_update(
 
     domain_mmap_cache_destroy(&mapcache);
 
-    if ( sync_guest )
-    {
-        /*
-         * Force other vCPU-s of the affected guest to pick up L4 entry
-         * changes (if any). Issue a flush IPI with empty operation mask to
-         * facilitate this (including ourselves waiting for the IPI to
-         * actually have arrived).
-         */
-        flush_mask(&pt_owner->domain_dirty_cpumask, 0);
-    }
+    /*
+     * Perform required TLB maintenance.
+     *
+     * pt_owner may not be current->domain.  This may occur during
+     * construction of 32bit PV guests, or debugging of PV guests.  The
+     * behaviour cannot be correct with domain unpaused.  We therefore expect
+     * pt_owner->dirty_cpumask to be empty, but it is a waste of effort to
+     * explicitly check for, and exclude, this corner case.
+     *
+     * flush_linear_pt requires a FLUSH_TLB to all dirty CPUs.  The flush must
+     * be performed now to maintain correct behaviour across a multicall.
+     */
+    if ( flush_linear_pt )
+        flush_mask(&pt_owner->domain_dirty_cpumask, FLUSH_TLB);
 
     perfc_add(num_page_updates, i);
 
