* [PATCH v2 1/4] KVM: arm64: Clear the whole hugetlb folio when sanitising MTE tags
2026-10-21 11:06 [PATCH v2 0/4] KVM: arm64: Fix stale MTE tags reaching a guest Fuad Tabba
@ 2026-10-21 11:06 ` Fuad Tabba
2026-10-21 11:06 ` [PATCH v2 2/4] KVM: arm64: Clear the whole hugetlb folio when storing " Fuad Tabba
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Fuad Tabba @ 2026-10-21 11:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Steven Price,
Yang Shi, David Hildenbrand, Lorenzo Stoakes, Fuad Tabba,
linux-kernel
sanitise_mte_tags() clears the tags of the pages about to be mapped at
stage-2 before marking them tagged. For a hugetlb folio the flag covers
every page in the folio, but the loop only covers the mapping being
installed. Stage-2 maps a folio in pieces for the contiguous hugetlb
sizes, and for any size under dirty logging. The first fault then marks
the whole folio after clearing only its own piece, and the rest keeps
its previous owner's tags for the guest to read.
Clear the whole folio, as mte_sync_tags() does.
Fixes: 25c17c4b55def ("hugetlb: arm64: add mte support")
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/mmu.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9ba86450fe4af..a551b6bb13c21 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1512,10 +1512,10 @@ static void sanitise_mte_tags(struct kvm *kvm, kvm_pfn_t pfn,
}
if (folio_test_hugetlb(folio)) {
- /* Hugetlb has MTE flags set on head page only */
+ /* A hugetlb folio is marked tagged as a whole, so clear every page */
if (folio_try_hugetlb_mte_tagging(folio)) {
- for (i = 0; i < nr_pages; i++, page++)
- mte_clear_page_tags(page_address(page));
+ for (i = 0; i < folio_nr_pages(folio); i++)
+ mte_clear_page_tags(page_address(folio_page(folio, i)));
folio_set_hugetlb_mte_tagged(folio);
}
return;
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v2 2/4] KVM: arm64: Clear the whole hugetlb folio when storing MTE tags
2026-10-21 11:06 [PATCH v2 0/4] KVM: arm64: Fix stale MTE tags reaching a guest Fuad Tabba
2026-10-21 11:06 ` [PATCH v2 1/4] KVM: arm64: Clear the whole hugetlb folio when sanitising MTE tags Fuad Tabba
@ 2026-10-21 11:06 ` Fuad Tabba
2026-10-21 11:06 ` [PATCH v2 3/4] KVM: arm64: Sanitise MTE tags when a permission fault installs a mapping Fuad Tabba
2026-10-21 11:07 ` [PATCH v2 4/4] arm64: mte: Add mte_clear_folio_tags() Fuad Tabba
3 siblings, 0 replies; 6+ messages in thread
From: Fuad Tabba @ 2026-10-21 11:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Steven Price,
Yang Shi, David Hildenbrand, Lorenzo Stoakes, Fuad Tabba,
linux-kernel
When KVM_ARM_MTE_COPY_TAGS copies tags into a page, it marks the page as
tagged. For a hugetlb folio the flag covers every page in the folio. If
the folio wasn't tagged yet, its other pages are marked as tagged with
their previous owner's tags still in place. The stage-2 fault path then
maps them into the guest without clearing them. The ioctl's read side
also copies them out to the VMM.
Clear the whole folio on its first tagging, before copying in this
page's tags.
Fixes: 25c17c4b55def ("hugetlb: arm64: add mte support")
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/guest.c | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index b01d6622b8720..cda655fbff657 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -1012,6 +1012,7 @@ int kvm_vm_ioctl_mte_copy_tags(struct kvm *kvm,
void *maddr;
unsigned long num_tags;
struct folio *folio;
+ unsigned long i;
if (!page) {
ret = -EFAULT;
@@ -1042,12 +1043,14 @@ int kvm_vm_ioctl_mte_copy_tags(struct kvm *kvm,
/*
* Only locking to serialise with a concurrent
* __set_ptes() in the VMM but still overriding the
- * tags, hence ignoring the return value.
+ * tags. A hugetlb folio is marked tagged as a whole,
+ * so its first tagging clears every page.
*/
- if (folio_test_hugetlb(folio))
- folio_try_hugetlb_mte_tagging(folio);
- else
+ if (!folio_test_hugetlb(folio))
try_page_mte_tagging(page);
+ else if (folio_try_hugetlb_mte_tagging(folio))
+ for (i = 0; i < folio_nr_pages(folio); i++)
+ mte_clear_page_tags(page_address(folio_page(folio, i)));
num_tags = mte_copy_tags_from_user(maddr, tags,
MTE_GRANULES_PER_PAGE);
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v2 3/4] KVM: arm64: Sanitise MTE tags when a permission fault installs a mapping
2026-10-21 11:06 [PATCH v2 0/4] KVM: arm64: Fix stale MTE tags reaching a guest Fuad Tabba
2026-10-21 11:06 ` [PATCH v2 1/4] KVM: arm64: Clear the whole hugetlb folio when sanitising MTE tags Fuad Tabba
2026-10-21 11:06 ` [PATCH v2 2/4] KVM: arm64: Clear the whole hugetlb folio when storing " Fuad Tabba
@ 2026-10-21 11:06 ` Fuad Tabba
2026-10-21 11:07 ` [PATCH v2 4/4] arm64: mte: Add mte_clear_folio_tags() Fuad Tabba
3 siblings, 0 replies; 6+ messages in thread
From: Fuad Tabba @ 2026-10-21 11:06 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Steven Price,
Yang Shi, David Hildenbrand, Lorenzo Stoakes, Fuad Tabba,
linux-kernel
The stage-2 fault path sanitises MTE tags only on translation faults, as
a permission fault normally just relaxes an existing leaf. However, when
the page is backed by a THP, a permission fault at page granule installs
a block whose other pages may never have been mapped or sanitised. Once
dirty logging is off, a guest write to a page mapped read-only during
logging installs the block, and the guest can read the tags those pages'
previous owners left.
Sanitise whenever the fault path calls kvm_pgtable_stage2_map() rather
than relaxing permissions. If the VMM replaces the VMA after the guest
takes a permission fault but before vma_lookup(), that path would map
and sanitise memory from the new VMA without checking it. Run the
VM_MTE_ALLOWED check in kvm_s2_fault_compute_prot() on permission
faults too.
Fixes: ea7fc1bb1cd1b ("KVM: arm64: Introduce MTE VM feature")
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/kvm/mmu.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index a551b6bb13c21..6e10aa9913d71 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2004,7 +2004,7 @@ static int kvm_s2_fault_compute_prot(const struct kvm_s2_fault_desc *s2fd,
if (s2fd->nested)
*prot = adjust_nested_exec_perms(kvm, s2fd->nested, *prot);
- if (!kvm_s2_fault_is_perm(s2fd) && !s2vi->map_non_cacheable && kvm_has_mte(kvm)) {
+ if (!s2vi->map_non_cacheable && kvm_has_mte(kvm)) {
/* Check the VMM hasn't introduced a new disallowed VMA */
if (!s2vi->mte_allowed)
return -EFAULT;
@@ -2059,9 +2059,6 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
}
}
- if (!perm_fault_granule && !s2vi->map_non_cacheable && kvm_has_mte(kvm))
- sanitise_mte_tags(kvm, pfn, mapping_size);
-
/*
* Under the premise of getting a FSC_PERM fault, we just need to relax
* permissions only if mapping_size equals perm_fault_granule. Otherwise,
@@ -2076,6 +2073,9 @@ static int kvm_s2_fault_map(const struct kvm_s2_fault_desc *s2fd,
ret = KVM_PGT_FN(kvm_pgtable_stage2_relax_perms)(pgt, gfn_to_gpa(gfn),
prot, flags);
} else {
+ if (!s2vi->map_non_cacheable && kvm_has_mte(kvm))
+ sanitise_mte_tags(kvm, pfn, mapping_size);
+
ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, gfn_to_gpa(gfn), mapping_size,
__pfn_to_phys(pfn), prot,
memcache, flags);
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v2 4/4] arm64: mte: Add mte_clear_folio_tags()
2026-10-21 11:06 [PATCH v2 0/4] KVM: arm64: Fix stale MTE tags reaching a guest Fuad Tabba
` (2 preceding siblings ...)
2026-10-21 11:06 ` [PATCH v2 3/4] KVM: arm64: Sanitise MTE tags when a permission fault installs a mapping Fuad Tabba
@ 2026-10-21 11:07 ` Fuad Tabba
2026-10-21 12:28 ` Lorenzo Stoakes (ARM)
3 siblings, 1 reply; 6+ messages in thread
From: Fuad Tabba @ 2026-10-21 11:07 UTC (permalink / raw)
To: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel
Cc: Joey Gouly, Suzuki K Poulose, Zenghui Yu, Steffen Eiden,
Catalin Marinas, Will Deacon, Mark Rutland, Steven Price,
Yang Shi, David Hildenbrand, Lorenzo Stoakes, Fuad Tabba,
linux-kernel
mte_sync_tags(), sanitise_mte_tags() and kvm_vm_ioctl_mte_copy_tags()
each open-code the loop that clears the tags of every page in a hugetlb
folio. Add mte_clear_folio_tags() and use it in all three.
kvm_vm_ioctl_mte_copy_tags() then tests for hugetlb first, as it does
where it sets the tagged flag below.
No functional change intended.
Suggested-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
---
arch/arm64/include/asm/mte.h | 4 ++++
arch/arm64/kernel/mte.c | 15 ++++++++++-----
arch/arm64/kvm/guest.c | 10 +++++-----
arch/arm64/kvm/mmu.c | 3 +--
4 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/arch/arm64/include/asm/mte.h b/arch/arm64/include/asm/mte.h
index 7f7b97e099968..5a2d2814c0416 100644
--- a/arch/arm64/include/asm/mte.h
+++ b/arch/arm64/include/asm/mte.h
@@ -96,6 +96,7 @@ static inline bool try_page_mte_tagging(struct page *page)
}
void mte_zero_clear_page_tags(void *addr);
+void mte_clear_folio_tags(struct folio *folio);
void mte_sync_tags(pte_t pte, unsigned int nr_pages);
void mte_copy_page_tags(void *kto, const void *kfrom);
void mte_thread_init_user(void);
@@ -128,6 +129,9 @@ static inline bool try_page_mte_tagging(struct page *page)
static inline void mte_zero_clear_page_tags(void *addr)
{
}
+static inline void mte_clear_folio_tags(struct folio *folio)
+{
+}
static inline void mte_sync_tags(pte_t pte, unsigned int nr_pages)
{
}
diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c
index 1a9aad6ef22a0..fb2598f8c0ccd 100644
--- a/arch/arm64/kernel/mte.c
+++ b/arch/arm64/kernel/mte.c
@@ -36,6 +36,14 @@ DEFINE_STATIC_KEY_FALSE(mte_async_or_asymm_mode);
EXPORT_SYMBOL_GPL(mte_async_or_asymm_mode);
#endif
+void mte_clear_folio_tags(struct folio *folio)
+{
+ unsigned long i;
+
+ for (i = 0; i < folio_nr_pages(folio); i++)
+ mte_clear_page_tags(page_address(folio_page(folio, i)));
+}
+
void mte_sync_tags(pte_t pte, unsigned int nr_pages)
{
struct page *page = pte_page(pte);
@@ -43,12 +51,9 @@ void mte_sync_tags(pte_t pte, unsigned int nr_pages)
unsigned long i;
if (folio_test_hugetlb(folio)) {
- unsigned long nr = folio_nr_pages(folio);
-
- /* Hugetlb MTE flags are set for head page only */
+ /* A hugetlb folio is marked tagged as a whole, so clear every page */
if (folio_try_hugetlb_mte_tagging(folio)) {
- for (i = 0; i < nr; i++, page++)
- mte_clear_page_tags(page_address(page));
+ mte_clear_folio_tags(folio);
folio_set_hugetlb_mte_tagged(folio);
}
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index cda655fbff657..c75466384f8ac 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -1012,7 +1012,6 @@ int kvm_vm_ioctl_mte_copy_tags(struct kvm *kvm,
void *maddr;
unsigned long num_tags;
struct folio *folio;
- unsigned long i;
if (!page) {
ret = -EFAULT;
@@ -1046,11 +1045,12 @@ int kvm_vm_ioctl_mte_copy_tags(struct kvm *kvm,
* tags. A hugetlb folio is marked tagged as a whole,
* so its first tagging clears every page.
*/
- if (!folio_test_hugetlb(folio))
+ if (folio_test_hugetlb(folio)) {
+ if (folio_try_hugetlb_mte_tagging(folio))
+ mte_clear_folio_tags(folio);
+ } else {
try_page_mte_tagging(page);
- else if (folio_try_hugetlb_mte_tagging(folio))
- for (i = 0; i < folio_nr_pages(folio); i++)
- mte_clear_page_tags(page_address(folio_page(folio, i)));
+ }
num_tags = mte_copy_tags_from_user(maddr, tags,
MTE_GRANULES_PER_PAGE);
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 6e10aa9913d71..f8139c0a54deb 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1514,8 +1514,7 @@ static void sanitise_mte_tags(struct kvm *kvm, kvm_pfn_t pfn,
if (folio_test_hugetlb(folio)) {
/* A hugetlb folio is marked tagged as a whole, so clear every page */
if (folio_try_hugetlb_mte_tagging(folio)) {
- for (i = 0; i < folio_nr_pages(folio); i++)
- mte_clear_page_tags(page_address(folio_page(folio, i)));
+ mte_clear_folio_tags(folio);
folio_set_hugetlb_mte_tagged(folio);
}
return;
--
2.47.3
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v2 4/4] arm64: mte: Add mte_clear_folio_tags()
2026-10-21 11:07 ` [PATCH v2 4/4] arm64: mte: Add mte_clear_folio_tags() Fuad Tabba
@ 2026-10-21 12:28 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 6+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-21 12:28 UTC (permalink / raw)
To: Fuad Tabba
Cc: Marc Zyngier, Oliver Upton, kvmarm, linux-arm-kernel, Joey Gouly,
Suzuki K Poulose, Zenghui Yu, Steffen Eiden, Catalin Marinas,
Will Deacon, Mark Rutland, Steven Price, Yang Shi,
David Hildenbrand, Fuad Tabba, linux-kernel
On Wed, Oct 21, 2026 at 12:07:00PM +0100, Fuad Tabba wrote:
> mte_sync_tags(), sanitise_mte_tags() and kvm_vm_ioctl_mte_copy_tags()
> each open-code the loop that clears the tags of every page in a hugetlb
> folio. Add mte_clear_folio_tags() and use it in all three.
> kvm_vm_ioctl_mte_copy_tags() then tests for hugetlb first, as it does
> where it sets the tagged flag below.
>
> No functional change intended.
>
> Suggested-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Great LGTM so:
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> arch/arm64/include/asm/mte.h | 4 ++++
> arch/arm64/kernel/mte.c | 15 ++++++++++-----
> arch/arm64/kvm/guest.c | 10 +++++-----
> arch/arm64/kvm/mmu.c | 3 +--
> 4 files changed, 20 insertions(+), 12 deletions(-)
>
> diff --git a/arch/arm64/include/asm/mte.h b/arch/arm64/include/asm/mte.h
> index 7f7b97e099968..5a2d2814c0416 100644
> --- a/arch/arm64/include/asm/mte.h
> +++ b/arch/arm64/include/asm/mte.h
> @@ -96,6 +96,7 @@ static inline bool try_page_mte_tagging(struct page *page)
> }
>
> void mte_zero_clear_page_tags(void *addr);
> +void mte_clear_folio_tags(struct folio *folio);
> void mte_sync_tags(pte_t pte, unsigned int nr_pages);
> void mte_copy_page_tags(void *kto, const void *kfrom);
> void mte_thread_init_user(void);
> @@ -128,6 +129,9 @@ static inline bool try_page_mte_tagging(struct page *page)
> static inline void mte_zero_clear_page_tags(void *addr)
> {
> }
> +static inline void mte_clear_folio_tags(struct folio *folio)
> +{
> +}
> static inline void mte_sync_tags(pte_t pte, unsigned int nr_pages)
> {
> }
> diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c
> index 1a9aad6ef22a0..fb2598f8c0ccd 100644
> --- a/arch/arm64/kernel/mte.c
> +++ b/arch/arm64/kernel/mte.c
> @@ -36,6 +36,14 @@ DEFINE_STATIC_KEY_FALSE(mte_async_or_asymm_mode);
> EXPORT_SYMBOL_GPL(mte_async_or_asymm_mode);
> #endif
>
> +void mte_clear_folio_tags(struct folio *folio)
> +{
> + unsigned long i;
> +
> + for (i = 0; i < folio_nr_pages(folio); i++)
> + mte_clear_page_tags(page_address(folio_page(folio, i)));
> +}
> +
> void mte_sync_tags(pte_t pte, unsigned int nr_pages)
> {
> struct page *page = pte_page(pte);
> @@ -43,12 +51,9 @@ void mte_sync_tags(pte_t pte, unsigned int nr_pages)
> unsigned long i;
>
> if (folio_test_hugetlb(folio)) {
> - unsigned long nr = folio_nr_pages(folio);
> -
> - /* Hugetlb MTE flags are set for head page only */
> + /* A hugetlb folio is marked tagged as a whole, so clear every page */
> if (folio_try_hugetlb_mte_tagging(folio)) {
> - for (i = 0; i < nr; i++, page++)
> - mte_clear_page_tags(page_address(page));
> + mte_clear_folio_tags(folio);
> folio_set_hugetlb_mte_tagged(folio);
> }
>
> diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
> index cda655fbff657..c75466384f8ac 100644
> --- a/arch/arm64/kvm/guest.c
> +++ b/arch/arm64/kvm/guest.c
> @@ -1012,7 +1012,6 @@ int kvm_vm_ioctl_mte_copy_tags(struct kvm *kvm,
> void *maddr;
> unsigned long num_tags;
> struct folio *folio;
> - unsigned long i;
>
> if (!page) {
> ret = -EFAULT;
> @@ -1046,11 +1045,12 @@ int kvm_vm_ioctl_mte_copy_tags(struct kvm *kvm,
> * tags. A hugetlb folio is marked tagged as a whole,
> * so its first tagging clears every page.
> */
> - if (!folio_test_hugetlb(folio))
> + if (folio_test_hugetlb(folio)) {
> + if (folio_try_hugetlb_mte_tagging(folio))
> + mte_clear_folio_tags(folio);
> + } else {
> try_page_mte_tagging(page);
> - else if (folio_try_hugetlb_mte_tagging(folio))
> - for (i = 0; i < folio_nr_pages(folio); i++)
> - mte_clear_page_tags(page_address(folio_page(folio, i)));
> + }
> num_tags = mte_copy_tags_from_user(maddr, tags,
> MTE_GRANULES_PER_PAGE);
>
> diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
> index 6e10aa9913d71..f8139c0a54deb 100644
> --- a/arch/arm64/kvm/mmu.c
> +++ b/arch/arm64/kvm/mmu.c
> @@ -1514,8 +1514,7 @@ static void sanitise_mte_tags(struct kvm *kvm, kvm_pfn_t pfn,
> if (folio_test_hugetlb(folio)) {
> /* A hugetlb folio is marked tagged as a whole, so clear every page */
> if (folio_try_hugetlb_mte_tagging(folio)) {
> - for (i = 0; i < folio_nr_pages(folio); i++)
> - mte_clear_page_tags(page_address(folio_page(folio, i)));
> + mte_clear_folio_tags(folio);
> folio_set_hugetlb_mte_tagged(folio);
> }
> return;
> --
> 2.47.3
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 6+ messages in thread