From 741ee5ab7303200098322ff5e8732d409f356981 Mon Sep 17 00:00:00 2001 From: Roger Pau Monne 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é Reviewed-by: Jan Beulich --- 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 7a00bf95ddcf..54d2d60fcc5f 100644 --- a/xen/common/memory.c +++ b/xen/common/memory.c @@ -163,8 +163,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) ) @@ -176,15 +174,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 @@ -284,13 +273,6 @@ static void populate_physmap(struct memop_args *a) goto out; } - 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); } @@ -305,9 +287,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 0a0ebc15981b..2682fff9ccbc 100644 --- a/xen/common/page_alloc.c +++ b/xen/common/page_alloc.c @@ -1068,15 +1068,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)) ) { @@ -1101,9 +1103,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. @@ -1357,6 +1356,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]); /* * We can modify count_info without holding heap @@ -2040,7 +2046,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; @@ -2069,13 +2075,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 ) @@ -2084,9 +2093,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)); @@ -2999,9 +3005,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 16f733281af3..d128541e2cf8 100644 --- a/xen/include/xen/mm.h +++ b/xen/include/xen/mm.h @@ -202,8 +202,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