From 60ad3397f7ce432fa45f48e3014e1bdb74790d87 Mon Sep 17 00:00:00 2001
From: Roger Pau Monne <roger@xenproject.org>
Date: Tue, 4 Aug 2026 12:23:19 +0200
Subject: [PATCH] xen/page_alloc: ensure TLB flush is done ahead of page
 scrubbing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The current way in which idle TLB flush and TLB flushing when allocating a
page are done allows for the scrubbing to be done ahead of the TLB flush.
A PV domain can still have a TLB entry for the page after scrubbing, and
hence it may be able to modify it.  Such unintended page accessing allows
domains to possibly exchange information even when `xsm=silo scrub-domheap`
are in effect.

Remove the MEMF_no_tlbflush memory allocation flag, and reorder the
flushing so it's always done ahead of the scrubbing in
alloc_{,color_}heap_pages().  The sole user of MEMF_no_tlbflush is
populate_physmap(), and given the constrains above it's no longer safe
to defer the flush, hence the flag removal and the folding of the flush in
the allocator function itself.

This is XSA-511 / CVE-2026-79603.

Fixes: 24f1a58d1954 ("mm: option to _always_ scrub freed domheap pages")
Signed-off-by: Roger Pau Monné <roger@xenproject.org>
Reviewed-by: Jan Beulich <jbeulich@suse.com>
---
 xen/common/memory.c     | 21 ---------------------
 xen/common/page_alloc.c | 34 +++++++++++++++++++---------------
 xen/include/xen/mm.h    |  2 --
 3 files changed, 19 insertions(+), 38 deletions(-)

diff --git a/xen/common/memory.c b/xen/common/memory.c
index e245b160d467..ff2ed61fe2f2 100644
--- a/xen/common/memory.c
+++ b/xen/common/memory.c
@@ -232,8 +232,6 @@ static void populate_physmap(struct memop_args *a)
     unsigned int i, j;
     xen_pfn_t gpfn;
     struct domain *d = a->domain, *curr_d = current->domain;
-    bool need_tlbflush = false;
-    uint32_t tlbflush_timestamp = 0;
 
     if ( !guest_handle_subrange_okay(a->extent_list, a->nr_done,
                                      a->nr_extents-1) )
@@ -245,15 +243,6 @@ static void populate_physmap(struct memop_args *a)
 
     if ( unlikely(!d->creation_finished) )
     {
-        /*
-         * With MEMF_no_tlbflush set, alloc_heap_pages() will ignore
-         * TLB-flushes. After VM creation, this is a security issue (it can
-         * make pages accessible to guest B, when guest A may still have a
-         * cached mapping to them). So we do this only during domain creation,
-         * when the domain itself has not yet been unpaused for the first
-         * time.
-         */
-        a->memflags |= MEMF_no_tlbflush;
         /*
          * With MEMF_no_icache_flush, alloc_heap_pages() will skip
          * performing icache flushes. We do it only before domain
@@ -396,13 +385,6 @@ static void populate_physmap(struct memop_args *a)
                     }
                 }
 
-                if ( unlikely(a->memflags & MEMF_no_tlbflush) )
-                {
-                    for ( j = 0; j < (1U << a->extent_order); j++ )
-                        accumulate_tlbflush(&need_tlbflush, &page[j],
-                                            &tlbflush_timestamp);
-                }
-
                 mfn = page_to_mfn(page);
             }
 
@@ -417,9 +399,6 @@ static void populate_physmap(struct memop_args *a)
     }
 
 out:
-    if ( need_tlbflush )
-        filtered_flush_tlb_mask(tlbflush_timestamp);
-
     if ( a->memflags & MEMF_no_icache_flush )
         invalidate_icache();
 
diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c
index 40fdb5fb98c2..2ee9d730f0c2 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -1099,15 +1099,17 @@ static struct page_info *alloc_heap_pages(
         /* Preserve PGC_need_scrub so we can check it after lock is dropped. */
         pg[i].count_info = PGC_state_inuse | (pg[i].count_info & PGC_need_scrub);
 
-        if ( !(memflags & MEMF_no_tlbflush) )
-            accumulate_tlbflush(&need_tlbflush, &pg[i],
-                                &tlbflush_timestamp);
+        accumulate_tlbflush(&need_tlbflush, &pg[i], &tlbflush_timestamp);
 
         init_free_page_fields(&pg[i]);
     }
 
     spin_unlock(&heap_lock);
 
+    /* Flush ahead of scrubbing: ensure no PV domain has a stale TLB entry. */
+    if ( need_tlbflush )
+        filtered_flush_tlb_mask(tlbflush_timestamp);
+
     if ( first_dirty != INVALID_DIRTY_IDX ||
          (scrub_debug && !(memflags & MEMF_no_scrub)) )
     {
@@ -1143,9 +1145,6 @@ static struct page_info *alloc_heap_pages(
         }
     }
 
-    if ( need_tlbflush )
-        filtered_flush_tlb_mask(tlbflush_timestamp);
-
     /*
      * Ensure cache and RAM are consistent for platforms where the guest
      * can control its own visibility of/through the cache.
@@ -1405,6 +1404,13 @@ bool scrub_free_pages(void)
                 {
                     if ( test_bit(_PGC_need_scrub, &pg[i].count_info) )
                     {
+                        bool need_tlbflush = false;
+                        uint32_t tlbflush_ts = 0;
+
+                        accumulate_tlbflush(&need_tlbflush, &pg[i], &tlbflush_ts);
+                        if ( need_tlbflush )
+                            filtered_flush_tlb_mask(tlbflush_ts);
+
                         scrub_one_page(&pg[i], true);
                         /*
                          * We can modify count_info without holding heap
@@ -2072,7 +2078,7 @@ static struct page_info *alloc_color_heap_page(unsigned int memflags,
     uint32_t tlbflush_timestamp = 0;
     bool need_scrub;
 
-    if ( memflags & ~(MEMF_no_refcount | MEMF_no_owner | MEMF_no_tlbflush |
+    if ( memflags & ~(MEMF_no_refcount | MEMF_no_owner |
                       MEMF_no_icache_flush | MEMF_no_scrub) )
         return NULL;
 
@@ -2101,13 +2107,16 @@ static struct page_info *alloc_color_heap_page(unsigned int memflags,
     free_colored_pages[color]--;
     page_list_del(pg, color_heap(color));
 
-    if ( !(memflags & MEMF_no_tlbflush) )
-        accumulate_tlbflush(&need_tlbflush, pg, &tlbflush_timestamp);
+    accumulate_tlbflush(&need_tlbflush, pg, &tlbflush_timestamp);
 
     init_free_page_fields(pg);
 
     spin_unlock(&heap_lock);
 
+    /* Flush ahead of scrubbing: ensure no PV domain has a stale TLB entry. */
+    if ( need_tlbflush )
+        filtered_flush_tlb_mask(tlbflush_timestamp);
+
     if ( !(memflags & MEMF_no_scrub) )
     {
         if ( need_scrub )
@@ -2116,9 +2125,6 @@ static struct page_info *alloc_color_heap_page(unsigned int memflags,
             check_one_page(pg);
     }
 
-    if ( need_tlbflush )
-        filtered_flush_tlb_mask(tlbflush_timestamp);
-
     flush_page_to_ram(mfn_x(page_to_mfn(pg)),
                       !(memflags & MEMF_no_icache_flush));
 
@@ -3033,9 +3039,7 @@ static bool prepare_staticmem_pages(struct page_info *pg, unsigned long nr_mfns,
             goto out_err;
         }
 
-        if ( !(memflags & MEMF_no_tlbflush) )
-            accumulate_tlbflush(&need_tlbflush, &pg[i],
-                                &tlbflush_timestamp);
+        accumulate_tlbflush(&need_tlbflush, &pg[i], &tlbflush_timestamp);
 
         /*
          * Preserve flag PGC_static and change page state
diff --git a/xen/include/xen/mm.h b/xen/include/xen/mm.h
index b80bec00c124..379a8e4cbe56 100644
--- a/xen/include/xen/mm.h
+++ b/xen/include/xen/mm.h
@@ -222,8 +222,6 @@ struct npfec {
 #define  MEMF_exact_node  (1U<<_MEMF_exact_node)
 #define _MEMF_no_owner    5
 #define  MEMF_no_owner    (1U<<_MEMF_no_owner)
-#define _MEMF_no_tlbflush 6
-#define  MEMF_no_tlbflush (1U<<_MEMF_no_tlbflush)
 #define _MEMF_no_icache_flush 7
 #define  MEMF_no_icache_flush (1U<<_MEMF_no_icache_flush)
 #define _MEMF_no_scrub    8
-- 
2.53.0

