Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v3 0/3] make persistent huge zero folio read-only
@ 2026-07-06 13:04 Xueyuan Chen
  2026-07-06 13:04 ` [RFC PATCH v3 1/3] mm: " Xueyuan Chen
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Xueyuan Chen @ 2026-07-06 13:04 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Catalin Marinas, Will Deacon,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-mm, linux-arm-kernel, linux-kernel
  Cc: Lance Yang, Jann Horn, Yang Shi, Mike Rapoport, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, H . Peter Anvin, Andy Lutomirski,
	Peter Zijlstra

The persistent huge zero folio is shared globally and should stay zero
after initialization. As Jann Horn pointed out[1], kernel bugs have ended
up writing to pages that were meant to be read-only, including in
security-sensitive cases. Making the persistent huge zero folio read-only
in the direct map turns such writes into faults instead of silent zero-page
corruption.

This series adds set_direct_map_ro_noflush() so mm code can make a
direct-map range read-only, then uses it for persistent huge zero folio.
The helper is direct-map specific, takes an address-based range as
discussed for set_direct_map* helpers[2], and does not flush TLBs.

Persistent huge zero folio setup happens during early boot, so no explicit
TLB flush is needed here.

Inspired by Jann Horn's read-only zero page work[1] and follow-up
discussion[3] with Yang Shi.

Patches 2 and 3 add arm64 and x86 implementations.

[1] https://lore.kernel.org/linux-mm/20260508-ro-zeropage-v1-1-9808abc20b49@google.com/
[2] https://lore.kernel.org/linux-mm/0e5b23a6-4895-454a-9dfa-6dc21adc2991@kernel.org/
[3] https://lore.kernel.org/linux-mm/CAHbLzkrXXe7r3n3jXgDKtwZhRqj=jDx9E6dLOULohnhBguvi9A@mail.gmail.com/

RFC v2 -> RFC v3:
- Patch #01: Replace arch_make_pages_readonly() with
  set_direct_map_ro_noflush() in the existing set_direct_map* family
  (per Mike and David, thanks!).
- Patch #01: Use a direct-map address and number of pages, and document the
  direct-map-only and no-TLB-flush semantics (per David, thanks!).
- Patch #02 and #03: Update the arm64 and x86 implementations for
  set_direct_map_ro_noflush().
- https://lore.kernel.org/linux-mm/20260609143801.7917-1-xueyuan.chen21@gmail.com/

RFC v1 -> RFC v2:
- Patch #01: Drop the READONLY_HUGE_ZERO_FOLIO Kconfig option
  (per Dave, thanks!).
- Patch #01: Replace the huge-zero-folio-specific hook with a generic
  page-range hook (per David, thanks!)
- Patch #02 and #03: Update the arm64 and x86 implementations for the new
  hook.
- https://lore.kernel.org/linux-mm/20260527035607.14919-1-xueyuan.chen21@gmail.com/

Xueyuan Chen (3):
  mm: make persistent huge zero folio read-only
  arm64/mm: add set_direct_map_ro_noflush()
  x86/mm: add set_direct_map_ro_noflush()

 arch/arm64/include/asm/set_memory.h |  2 ++
 arch/arm64/mm/pageattr.c            | 10 ++++++++++
 arch/x86/include/asm/set_memory.h   |  2 ++
 arch/x86/mm/pat/set_memory.c        | 15 +++++++++++++++
 include/linux/set_memory.h          | 26 ++++++++++++++++++++++++++
 mm/huge_memory.c                    |  9 ++++++++-
 6 files changed, 63 insertions(+), 1 deletion(-)

-- 
2.49.0



^ permalink raw reply	[flat|nested] 5+ messages in thread

* [RFC PATCH v3 1/3] mm: make persistent huge zero folio read-only
  2026-07-06 13:04 [RFC PATCH v3 0/3] make persistent huge zero folio read-only Xueyuan Chen
@ 2026-07-06 13:04 ` Xueyuan Chen
  2026-07-07 13:17   ` Usama Arif
  2026-07-06 13:04 ` [RFC PATCH v3 2/3] arm64/mm: add set_direct_map_ro_noflush() Xueyuan Chen
  2026-07-06 13:04 ` [RFC PATCH v3 3/3] x86/mm: " Xueyuan Chen
  2 siblings, 1 reply; 5+ messages in thread
From: Xueyuan Chen @ 2026-07-06 13:04 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Catalin Marinas, Will Deacon,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-mm, linux-arm-kernel, linux-kernel
  Cc: Lance Yang, Jann Horn, Yang Shi, Mike Rapoport, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, H . Peter Anvin, Andy Lutomirski,
	Peter Zijlstra, Xueyuan Chen

From: Xueyuan Chen <xueyuan.chen21@gmail.com>

The persistent huge zero folio is shared globally and should stay zero
after initialization. As Jann Horn pointed out[1], kernel bugs have ended
up writing to pages that were meant to be read-only, including in
security-sensitive cases. Making the persistent huge zero folio read-only
in the direct map turns such writes into faults instead of silent zero-page
corruption.

Add set_direct_map_ro_noflush() so mm code can make a direct-map range
read-only. Use an address-based signature to match ongoing direct-map
helper work[2], where existing page-based helpers may move the same way.
The helper is direct-map specific and does not flush TLBs. Architectures
without direct-map permission support keep existing behavior through the
generic stub.

Persistent huge zero folio setup happens during early boot, so no explicit
TLB flush is needed.

Inspired by Jann Horn's read-only zero page work[1] and follow-up
discussion[3] with Yang Shi.

[1] https://lore.kernel.org/linux-mm/20260508-ro-zeropage-v1-1-9808abc20b49@google.com/
[2] https://lore.kernel.org/linux-mm/0e5b23a6-4895-454a-9dfa-6dc21adc2991@kernel.org/
[3] https://lore.kernel.org/linux-mm/CAHbLzkrXXe7r3n3jXgDKtwZhRqj=jDx9E6dLOULohnhBguvi9A@mail.gmail.com/

Suggested-by: David Hildenbrand <david@kernel.org>
Co-developed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
---
 include/linux/set_memory.h | 26 ++++++++++++++++++++++++++
 mm/huge_memory.c           |  9 ++++++++-
 2 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
index 3030d9245f5a..a905074fb21d 100644
--- a/include/linux/set_memory.h
+++ b/include/linux/set_memory.h
@@ -40,6 +40,22 @@ static inline int set_direct_map_valid_noflush(struct page *page,
 	return 0;
 }
 
+/**
+ * set_direct_map_ro_noflush - make direct-map mappings read-only
+ * @addr: start address in the direct map
+ * @nr_pages: number of pages starting at @addr
+ *
+ * Make the direct-map mappings for @nr_pages pages starting at @addr
+ * read-only, without flushing TLBs.
+ *
+ * Return: 0 on success or when unsupported, negative error code on failure.
+ */
+static inline int set_direct_map_ro_noflush(const void *addr,
+					    unsigned long nr_pages)
+{
+	return 0;
+}
+
 static inline bool kernel_page_present(struct page *page)
 {
 	return true;
@@ -56,6 +72,16 @@ static inline bool can_set_direct_map(void)
 }
 #define can_set_direct_map can_set_direct_map
 #endif
+
+#ifndef set_direct_map_ro_noflush
+static inline int set_direct_map_ro_noflush(const void *addr,
+					    unsigned long nr_pages)
+{
+	return 0;
+}
+
+#define set_direct_map_ro_noflush set_direct_map_ro_noflush
+#endif
 #endif /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
 
 #ifdef CONFIG_X86_64
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index bdd8635922f9..6633217b10dc 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -41,6 +41,7 @@
 #include <linux/pgalloc.h>
 #include <linux/pgalloc_tag.h>
 #include <linux/pagewalk.h>
+#include <linux/set_memory.h>
 
 #include <asm/tlb.h>
 #include "internal.h"
@@ -981,8 +982,14 @@ static int __init thp_shrinker_init(void)
 		 * that get_huge_zero_folio() will most likely not fail as
 		 * thp_shrinker_init() is invoked early on during boot.
 		 */
-		if (!get_huge_zero_folio())
+		if (!get_huge_zero_folio()) {
 			pr_warn("Allocating persistent huge zero folio failed\n");
+			return 0;
+		}
+
+		/* Set up during early boot; no explicit TLB flush is needed here. */
+		set_direct_map_ro_noflush(folio_address(huge_zero_folio),
+					  HPAGE_PMD_NR);
 		return 0;
 	}
 
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH v3 2/3] arm64/mm: add set_direct_map_ro_noflush()
  2026-07-06 13:04 [RFC PATCH v3 0/3] make persistent huge zero folio read-only Xueyuan Chen
  2026-07-06 13:04 ` [RFC PATCH v3 1/3] mm: " Xueyuan Chen
@ 2026-07-06 13:04 ` Xueyuan Chen
  2026-07-06 13:04 ` [RFC PATCH v3 3/3] x86/mm: " Xueyuan Chen
  2 siblings, 0 replies; 5+ messages in thread
From: Xueyuan Chen @ 2026-07-06 13:04 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Catalin Marinas, Will Deacon,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-mm, linux-arm-kernel, linux-kernel
  Cc: Lance Yang, Jann Horn, Yang Shi, Mike Rapoport, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, H . Peter Anvin, Andy Lutomirski,
	Peter Zijlstra, Xueyuan Chen

From: Xueyuan Chen <xueyuan.chen21@gmail.com>

Implement set_direct_map_ro_noflush() for arm64 with update_range_prot() on
the linear map, setting PTE_RDONLY and clearing PTE_WRITE. Keep the
existing can_set_direct_map() guard and leave TLB invalidation to the
caller.

Co-developed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
---
 arch/arm64/include/asm/set_memory.h |  2 ++
 arch/arm64/mm/pageattr.c            | 10 ++++++++++
 2 files changed, 12 insertions(+)

diff --git a/arch/arm64/include/asm/set_memory.h b/arch/arm64/include/asm/set_memory.h
index 90f61b17275e..7083260303c3 100644
--- a/arch/arm64/include/asm/set_memory.h
+++ b/arch/arm64/include/asm/set_memory.h
@@ -14,6 +14,8 @@ int set_memory_valid(unsigned long addr, int numpages, int enable);
 int set_direct_map_invalid_noflush(struct page *page);
 int set_direct_map_default_noflush(struct page *page);
 int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);
+int set_direct_map_ro_noflush(const void *addr, unsigned long nr_pages);
+#define set_direct_map_ro_noflush set_direct_map_ro_noflush
 bool kernel_page_present(struct page *page);
 
 int set_memory_encrypted(unsigned long addr, int numpages);
diff --git a/arch/arm64/mm/pageattr.c b/arch/arm64/mm/pageattr.c
index bbe98ac9ad8c..1a6769073558 100644
--- a/arch/arm64/mm/pageattr.c
+++ b/arch/arm64/mm/pageattr.c
@@ -365,6 +365,16 @@ int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid)
 	return set_memory_valid(addr, nr, valid);
 }
 
+int set_direct_map_ro_noflush(const void *addr, unsigned long nr_pages)
+{
+	if (!can_set_direct_map())
+		return 0;
+
+	return update_range_prot((unsigned long)addr, PAGE_SIZE * nr_pages,
+				 __pgprot(PTE_RDONLY),
+				 __pgprot(PTE_WRITE));
+}
+
 #ifdef CONFIG_DEBUG_PAGEALLOC
 /*
  * This is - apart from the return value - doing the same
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [RFC PATCH v3 3/3] x86/mm: add set_direct_map_ro_noflush()
  2026-07-06 13:04 [RFC PATCH v3 0/3] make persistent huge zero folio read-only Xueyuan Chen
  2026-07-06 13:04 ` [RFC PATCH v3 1/3] mm: " Xueyuan Chen
  2026-07-06 13:04 ` [RFC PATCH v3 2/3] arm64/mm: add set_direct_map_ro_noflush() Xueyuan Chen
@ 2026-07-06 13:04 ` Xueyuan Chen
  2 siblings, 0 replies; 5+ messages in thread
From: Xueyuan Chen @ 2026-07-06 13:04 UTC (permalink / raw)
  To: Andrew Morton, David Hildenbrand, Catalin Marinas, Will Deacon,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
	linux-mm, linux-arm-kernel, linux-kernel
  Cc: Lance Yang, Jann Horn, Yang Shi, Mike Rapoport, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, H . Peter Anvin, Andy Lutomirski,
	Peter Zijlstra, Xueyuan Chen

From: Xueyuan Chen <xueyuan.chen21@gmail.com>

Implement set_direct_map_ro_noflush() for x86 using CPA directly on the
passed direct-map address. Clear _PAGE_RW and _PAGE_DIRTY, keep alias
checks disabled like the existing direct-map _noflush helpers, and leave
TLB invalidation to the caller.

Co-developed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
---
 arch/x86/include/asm/set_memory.h |  2 ++
 arch/x86/mm/pat/set_memory.c      | 15 +++++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/arch/x86/include/asm/set_memory.h b/arch/x86/include/asm/set_memory.h
index 4362c26aa992..bd3817e06052 100644
--- a/arch/x86/include/asm/set_memory.h
+++ b/arch/x86/include/asm/set_memory.h
@@ -89,6 +89,8 @@ int set_pages_rw(struct page *page, int numpages);
 int set_direct_map_invalid_noflush(struct page *page);
 int set_direct_map_default_noflush(struct page *page);
 int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid);
+int set_direct_map_ro_noflush(const void *addr, unsigned long nr_pages);
+#define set_direct_map_ro_noflush set_direct_map_ro_noflush
 bool kernel_page_present(struct page *page);
 
 extern int kernel_set_to_readonly;
diff --git a/arch/x86/mm/pat/set_memory.c b/arch/x86/mm/pat/set_memory.c
index d023a40a1e03..5987f4c84f6f 100644
--- a/arch/x86/mm/pat/set_memory.c
+++ b/arch/x86/mm/pat/set_memory.c
@@ -2662,6 +2662,21 @@ int set_direct_map_valid_noflush(struct page *page, unsigned nr, bool valid)
 	return __set_pages_np(page, nr);
 }
 
+int set_direct_map_ro_noflush(const void *addr, unsigned long nr_pages)
+{
+	unsigned long tempaddr = (unsigned long)addr;
+	struct cpa_data cpa = {
+		.vaddr = &tempaddr,
+		.pgd = NULL,
+		.numpages = nr_pages,
+		.mask_set = __pgprot(0),
+		.mask_clr = __pgprot(_PAGE_RW | _PAGE_DIRTY),
+		.flags = CPA_NO_CHECK_ALIAS,
+	};
+
+	return __change_page_attr_set_clr(&cpa, 1);
+}
+
 #ifdef CONFIG_DEBUG_PAGEALLOC
 void __kernel_map_pages(struct page *page, int numpages, int enable)
 {
-- 
2.49.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [RFC PATCH v3 1/3] mm: make persistent huge zero folio read-only
  2026-07-06 13:04 ` [RFC PATCH v3 1/3] mm: " Xueyuan Chen
@ 2026-07-07 13:17   ` Usama Arif
  0 siblings, 0 replies; 5+ messages in thread
From: Usama Arif @ 2026-07-07 13:17 UTC (permalink / raw)
  To: Xueyuan Chen
  Cc: Usama Arif, Andrew Morton, David Hildenbrand, Catalin Marinas,
	Will Deacon, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, x86, linux-mm, linux-arm-kernel, linux-kernel,
	Lance Yang, Jann Horn, Yang Shi, Mike Rapoport, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R . Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Barry Song, H . Peter Anvin, Andy Lutomirski,
	Peter Zijlstra

On Mon,  6 Jul 2026 21:04:38 +0800 Xueyuan Chen <xueyuan.chen21@gmail.com> wrote:

> From: Xueyuan Chen <xueyuan.chen21@gmail.com>
> 
> The persistent huge zero folio is shared globally and should stay zero
> after initialization. As Jann Horn pointed out[1], kernel bugs have ended
> up writing to pages that were meant to be read-only, including in
> security-sensitive cases. Making the persistent huge zero folio read-only
> in the direct map turns such writes into faults instead of silent zero-page
> corruption.
> 
> Add set_direct_map_ro_noflush() so mm code can make a direct-map range
> read-only. Use an address-based signature to match ongoing direct-map
> helper work[2], where existing page-based helpers may move the same way.
> The helper is direct-map specific and does not flush TLBs. Architectures
> without direct-map permission support keep existing behavior through the
> generic stub.
> 
> Persistent huge zero folio setup happens during early boot, so no explicit
> TLB flush is needed.
> 
> Inspired by Jann Horn's read-only zero page work[1] and follow-up
> discussion[3] with Yang Shi.
> 
> [1] https://lore.kernel.org/linux-mm/20260508-ro-zeropage-v1-1-9808abc20b49@google.com/
> [2] https://lore.kernel.org/linux-mm/0e5b23a6-4895-454a-9dfa-6dc21adc2991@kernel.org/
> [3] https://lore.kernel.org/linux-mm/CAHbLzkrXXe7r3n3jXgDKtwZhRqj=jDx9E6dLOULohnhBguvi9A@mail.gmail.com/
> 
> Suggested-by: David Hildenbrand <david@kernel.org>
> Co-developed-by: Lance Yang <lance.yang@linux.dev>
> Signed-off-by: Lance Yang <lance.yang@linux.dev>
> Signed-off-by: Xueyuan Chen <xueyuan.chen21@gmail.com>
> ---
>  include/linux/set_memory.h | 26 ++++++++++++++++++++++++++
>  mm/huge_memory.c           |  9 ++++++++-
>  2 files changed, 34 insertions(+), 1 deletion(-)
> 
> diff --git a/include/linux/set_memory.h b/include/linux/set_memory.h
> index 3030d9245f5a..a905074fb21d 100644
> --- a/include/linux/set_memory.h
> +++ b/include/linux/set_memory.h
> @@ -40,6 +40,22 @@ static inline int set_direct_map_valid_noflush(struct page *page,
>  	return 0;
>  }
>  
> +/**
> + * set_direct_map_ro_noflush - make direct-map mappings read-only
> + * @addr: start address in the direct map
> + * @nr_pages: number of pages starting at @addr
> + *
> + * Make the direct-map mappings for @nr_pages pages starting at @addr
> + * read-only, without flushing TLBs.
> + *
> + * Return: 0 on success or when unsupported, negative error code on failure.
> + */
> +static inline int set_direct_map_ro_noflush(const void *addr,
> +					    unsigned long nr_pages)
> +{
> +	return 0;
> +}
> +
>  static inline bool kernel_page_present(struct page *page)
>  {
>  	return true;
> @@ -56,6 +72,16 @@ static inline bool can_set_direct_map(void)
>  }
>  #define can_set_direct_map can_set_direct_map
>  #endif
> +
> +#ifndef set_direct_map_ro_noflush
> +static inline int set_direct_map_ro_noflush(const void *addr,
> +					    unsigned long nr_pages)
> +{
> +	return 0;
> +}
> +
> +#define set_direct_map_ro_noflush set_direct_map_ro_noflush
> +#endif
>  #endif /* CONFIG_ARCH_HAS_SET_DIRECT_MAP */
>  
>  #ifdef CONFIG_X86_64
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index bdd8635922f9..6633217b10dc 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -41,6 +41,7 @@
>  #include <linux/pgalloc.h>
>  #include <linux/pgalloc_tag.h>
>  #include <linux/pagewalk.h>
> +#include <linux/set_memory.h>
>  
>  #include <asm/tlb.h>
>  #include "internal.h"
> @@ -981,8 +982,14 @@ static int __init thp_shrinker_init(void)
>  		 * that get_huge_zero_folio() will most likely not fail as
>  		 * thp_shrinker_init() is invoked early on during boot.
>  		 */
> -		if (!get_huge_zero_folio())
> +		if (!get_huge_zero_folio()) {
>  			pr_warn("Allocating persistent huge zero folio failed\n");
> +			return 0;
> +		}
> +
> +		/* Set up during early boot; no explicit TLB flush is needed here. */
> +		set_direct_map_ro_noflush(folio_address(huge_zero_folio),
> +					  HPAGE_PMD_NR);

Hi,

Can this really skip the TLB flush here?  This runs from a
subsys_initcall, after smp_init() and after the folio was allocated and
zeroed through the writable direct map.  The noflush helper updates the
direct-map PTEs but does not invalidate stale writable kernel TLB entries.

I think we need a TLB flush here?

>  		return 0;
>  	}
>  
> -- 
> 2.49.0
> 
> 


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-07 13:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-06 13:04 [RFC PATCH v3 0/3] make persistent huge zero folio read-only Xueyuan Chen
2026-07-06 13:04 ` [RFC PATCH v3 1/3] mm: " Xueyuan Chen
2026-07-07 13:17   ` Usama Arif
2026-07-06 13:04 ` [RFC PATCH v3 2/3] arm64/mm: add set_direct_map_ro_noflush() Xueyuan Chen
2026-07-06 13:04 ` [RFC PATCH v3 3/3] x86/mm: " Xueyuan Chen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox