xen/common/memory.c | 21 --------------------- xen/common/page_alloc.c | 34 +++++++++++++++++++--------------- xen/include/xen/mm.h | 2 -- 3 files changed, 19 insertions(+), 38 deletions(-)
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory CVE-2026-79603 / XSA-511
version 3
Unconditionally do TLB flushing ahead of page scrubbing
UPDATES IN VERSION 3
====================
Public release.
ISSUE DESCRIPTION
=================
x86 PV guests can free memory pages while still keeping a stale TLB entry
pointing to them. A TLB flush is only issued by Xen (if needed) when the
page is re-used. Since it's possible for the page to be scrubbed ahead of
the TLB flush, there's a window where a PV guest can modify an already
scrubbed page.
IMPACT
======
Deployments using `xsm=silo scrub-domheap` with the aim of not allowing the
exchange of information amongst guests are not effective in the presence of
PV guests.
VULNERABLE SYSTEMS
==================
All Xen versions from 4.13 onwards are vulnerable. Xen versions 4.12 and
earlier are not vulnerable as they lack the `scrub-domheap` command line
option.
Only x86 PV guests can exploit the vulnerability.
MITIGATION
==========
There is no known mitigation.
CREDITS
=======
This issue was discovered by Roger Pau Monné of AMD.
RESOLUTION
==========
Applying the appropriate attached patch resolves this issue.
Note that patches for released versions are generally prepared to
apply to the stable branches, and may not apply cleanly to the most
recent release tarball. Downstreams are encouraged to update to the
tip of the stable branch before applying these patches.
xsa511.patch xen-unstable - Xen 4.22.x
xsa511-4.21.patch Xen 4.21.x
xsa511-4.20.patch Xen 4.20.x
xsa511-4.19.patch Xen 4.19.x
xsa511-4.18.patch Xen 4.18.x - Xen 4.17.x
$ sha256sum xsa511*
ba3731960983ef88836f96655f917abb25447eab69fda1d9cf7f4e8203138403 xsa511.patch
c05a2d9fb391739a9ed39aa9247be264eb039bdac74f2db3b51e20199e6234cd xsa511-4.18.patch
9653110b3e82ea5c28185d436b22231f39d6e875712a7c9c6882d8cb1bd0d406 xsa511-4.19.patch
0a475b8622d867210346612c5f97a9c3b7b638de2704d51fbd86317b6b11a840 xsa511-4.20.patch
61aa358aef962a1e4dda3dd45cac7436e395362e9b5f9314ad3c60d39231cb97 xsa511-4.21.patch
$
DEPLOYMENT DURING EMBARGO
=========================
Deployment of the patches and/or mitigations described above (or
others which are substantially similar) is permitted during the
embargo, even on public-facing systems with untrusted guest users and
administrators.
But: Distribution of updated software is prohibited (except to other
members of the predisclosure list).
Predisclosure list members who wish to deploy significantly different
patches and/or mitigations, please contact the Xen Project Security
Team.
(Note: this during-embargo deployment notice is retained in
post-embargo publicly released Xen Project advisories, even though it
is then no longer applicable. This is to enable the community to have
oversight of the Xen Project Security Team's decisionmaking.)
For more information about permissible uses of embargoed information,
consult the Xen Project community's agreed Security Policy:
http://www.xenproject.org/security-policy.html
-----BEGIN PGP SIGNATURE-----
iQFABAEBCAAqFiEEI+MiLBRfRHX6gGCng/4UyVfoK9kFAmqf98YMHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZf6EH/3BoQ+95hSDLUYJzmWNdjdqwYpyrWe1RaMcXWfuM
DuvbkEd6SIrtkhEmO8ZHSiBm2g5v9/SyXrm0L4NZ2+LcZWbOAx0PK9D3DOjpIZk2
LpQJg75GPWLkBZ62vgZlCzcXa0opVNSrmnJvYimoHvdplMpFQOhd7Ve3988XCx1G
Mb7tKeQ7IdgAW0P/gMTGpunGL9dF58N2d8H5qbp5695tneszzW1UtAVB+4BxlEuh
WenQ1hJVtEWznRTYvaEJ7v6CYBoY7TmeYkpZviGhTj8cuTWguMNrKuSitPkRNJ+P
OLIQ/pz3E6PGhJxXWw8BpjkDzHf9ETACu1g+sa+18z6/+1o=
=/FMW
-----END PGP SIGNATURE-----
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
From 8c021a1df9e01f0a673bc828035b39e26590ff6f 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 | 22 +++++++++++++---------
xen/include/xen/mm.h | 2 --
3 files changed, 13 insertions(+), 32 deletions(-)
diff --git a/xen/common/memory.c b/xen/common/memory.c
index 5251ab343778..640f3a70ab54 100644
--- a/xen/common/memory.c
+++ b/xen/common/memory.c
@@ -161,8 +161,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) )
@@ -174,15 +172,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
@@ -282,13 +271,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);
}
@@ -303,9 +285,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 9b5df74fddab..5e88249f5099 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -1030,9 +1030,7 @@ 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);
/* Initialise fields which have other uses for free pages. */
pg[i].u.inuse.type_info = PGT_TYPE_INFO_INITIALIZER;
@@ -1042,6 +1040,10 @@ static struct page_info *alloc_heap_pages(
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)) )
{
@@ -1066,9 +1068,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.
@@ -1315,6 +1314,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
@@ -2791,9 +2797,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 8bc5f4249d1b..1fef542f6fba 100644
--- a/xen/include/xen/mm.h
+++ b/xen/include/xen/mm.h
@@ -197,8 +197,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
From de94c1e82d579d617b66d343fe7a5ea87577aa62 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 | 22 +++++++++++++---------
xen/include/xen/mm.h | 2 --
3 files changed, 13 insertions(+), 32 deletions(-)
diff --git a/xen/common/memory.c b/xen/common/memory.c
index 723ab3c0da8f..e65d3ce3b046 100644
--- a/xen/common/memory.c
+++ b/xen/common/memory.c
@@ -161,8 +161,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) )
@@ -174,15 +172,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
@@ -282,13 +271,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);
}
@@ -303,9 +285,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 bbb857845965..7bd565cd27be 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -1038,15 +1038,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)) )
{
@@ -1071,9 +1073,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.
@@ -1320,6 +1319,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
@@ -2796,9 +2802,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 7561297a7553..cc7852662a48 100644
--- a/xen/include/xen/mm.h
+++ b/xen/include/xen/mm.h
@@ -200,8 +200,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
From 741ee5ab7303200098322ff5e8732d409f356981 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 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
From 70e281422ad1ee5e8f9cb2dbaad418db08dc5bda 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 76a1bf1b1159..6c1ea6aae273 100644
--- a/xen/common/memory.c
+++ b/xen/common/memory.c
@@ -165,8 +165,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) )
@@ -178,15 +176,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
@@ -286,13 +275,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);
}
@@ -307,9 +289,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 a7b3b04a4d01..c4a8f8bc767e 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -1096,15 +1096,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)) )
{
@@ -1131,9 +1133,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.
@@ -1387,6 +1386,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
@@ -2054,7 +2060,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;
@@ -2083,13 +2089,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 )
@@ -2098,9 +2107,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));
@@ -3006,9 +3012,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 b968f47b87e0..58410a367a39 100644
--- a/xen/include/xen/mm.h
+++ b/xen/include/xen/mm.h
@@ -204,8 +204,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
© 2016 - 2026 Red Hat, Inc.