All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
@ 2026-10-13  9:36 Zhao Gongyi
  2026-10-13 10:13 ` Dev Jain
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Zhao Gongyi @ 2026-10-13  9:36 UTC (permalink / raw)
  To: catalin.marinas, will
  Cc: ryan.roberts, dev.jain, baolin.wang, lance.yang, mark.rutland,
	akpm, david, ljs, liam, vbabka, rppt, surenb, mhocko,
	linux-arm-kernel, linux-mm, linux-kernel, Zhao Gongyi, stable

The transparent contpte layer treats PTE_CONT as software state: when a
PTE has PTE_CONT set, the contpte helpers may align the operation to a
CONT_PTE_SIZE block and operate on the whole block. That invariant only
makes sense for valid mappings. Explicit users such as hugetlb manage
their contiguous encodings separately.

contpte_set_ptes() currently sets PTE_CONT from alignment alone. Batched
set_ptes() users can therefore install present-invalid PTEs with
PTE_CONT set, for example mprotect(PROT_NONE), NUMA hinting through
modify_prot_commit_ptes(), fork() copying a PROT_NONE large folio, or a
PMD split installing PAGE_NONE PTEs.

This is not a hardware programming problem: invalid descriptors are not
translated, so the hardware ignores descriptor bits such as PTE_CONT. It
is a software-state problem. Some arm64 fast paths intentionally test
pte_cont() directly instead of pte_valid_cont(), for example commit
9970a9a27ffc ("arm64: mm: implement the architecture-specific
test_and_clear_young_ptes()"). Fixing the producer side preserves these
fast paths and covers all consumers.

In the observed failure, mprotect(PROT_NONE) installed a full
CONT_PTE_SIZE range of present-invalid PTEs with PTE_CONT set.
MADV_GUARD_INSTALL then zapped one PTE. The nr == 1 clear_full_ptes()
path only unfolds pte_valid_cont() entries, so the neighbouring
present-invalid entries kept PTE_CONT while the target entry became a
guard marker.

clear_refs_pte_range() then reached the marker through:

  ptep_test_and_clear_young()
  test_and_clear_young_ptes()
  contpte_test_and_clear_young_ptes()
  contpte_align_addr_ptep()
  __ptep_test_and_clear_young()

The marker changed from old_pte=0x00000000000047c0 to
new_pte=0x00000000000043c0 by clearing PTE_AF. For swap PTEs, bit 10 is
part of the swap type field, bits 6-10: 0x47c0 is SWP_PTE_MARKER with
offset 4, while 0x43c0 has type 15 and is decoded as an ordinary swap
entry. Zap later warned in swap_put_entries_direct() with a bad swap
entry.

This is triggerable by a user process against its own address space. On
systems running with panic_on_warn, the warning becomes a denial of
service.

Fix the root cause by making contpte_set_ptes() use the transparent
contpte encoding only for valid descriptors. After this change, the
transparent contpte layer preserves the invariant that pte_cont(pte)
implies pte_valid(pte), so fast consumers can keep using PTE_CONT as
sufficient evidence of a transparent contpte block.

Fixes: 4602e5757bcc ("arm64/mm: wire up PTE_CONT for user mappings")
Cc: stable@vger.kernel.org
Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com>
---
Validation on an arm64 QEMU/KVM KASAN setup with CONFIG_ARM64_4K_PAGES=y
and CONFIG_ARM64_CONTPTE=y, using an out-of-tree guard-region reproducer:

  populate a CONT_PTE_SIZE anonymous THP range
  mprotect(PROT_NONE) on the range
  MADV_GUARD_INSTALL on one PTE in that range
  write "1" to /proc/self/clear_refs
  restore access and remove the guard marker

  Unfixed kernel: failed in the first iteration, with 65 bad swap
                  messages and a WARNING in swap_put_entries_direct()
  This patch:     completed 100 iterations without warnings

Additional producer-side instrumentation confirmed the same invalid
batch state outside the mprotect reproducer:

  Unfixed fork():     copy_page_range() wrote out_cont=1
  Unfixed PMD split: split_huge_pmd_locked() wrote out_cont=1
  This patch:        both paths wrote out_cont=0

 arch/arm64/include/asm/pgtable.h | 5 ++++-
 arch/arm64/mm/contpte.c          | 7 +++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
index 6000905a2..d6246042b 100644
--- a/arch/arm64/include/asm/pgtable.h
+++ b/arch/arm64/include/asm/pgtable.h
@@ -1650,7 +1650,10 @@ extern void modify_prot_commit_ptes(struct vm_area_struct *vma, unsigned long ad
 /*
  * The contpte APIs are used to transparently manage the contiguous bit in ptes
  * where it is possible and makes sense to do so. The PTE_CONT bit is considered
- * a private implementation detail of the public ptep API (see below).
+ * a private implementation detail of the public ptep API (see below). The
+ * transparent contpte layer maintains the invariant that pte_cont(pte) implies
+ * pte_valid(pte); explicit contiguous-encoding users such as hugetlb manage
+ * their entries separately.
  */
 extern void __contpte_try_fold(struct mm_struct *mm, unsigned long addr,
 				pte_t *ptep, pte_t pte);
diff --git a/arch/arm64/mm/contpte.c b/arch/arm64/mm/contpte.c
index 0acab179f..851fa2e44 100644
--- a/arch/arm64/mm/contpte.c
+++ b/arch/arm64/mm/contpte.c
@@ -463,6 +463,13 @@ void contpte_set_ptes(struct mm_struct *mm, unsigned long addr,
 	if (!mm_is_user(mm))
 		return __set_ptes(mm, addr, ptep, pte, nr);
 
+	/*
+	 * Keep the producer-side invariant documented in pgtable.h true even
+	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
+	 */
+	if (!pte_valid(pte))
+		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
+
 	end = addr + (nr << PAGE_SHIFT);
 	pfn = pte_pfn(pte);
 	prot = pte_pgprot(pte);
-- 
2.39.5 (Apple Git-154)


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-13  9:36 [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs Zhao Gongyi
@ 2026-10-13 10:13 ` Dev Jain
  2026-10-14  6:16   ` Zhao Gongyi
  2026-10-13 15:39 ` Lance Yang
  2026-10-14  9:41 ` David Hildenbrand (Arm)
  2 siblings, 1 reply; 13+ messages in thread
From: Dev Jain @ 2026-10-13 10:13 UTC (permalink / raw)
  To: Zhao Gongyi, catalin.marinas, will
  Cc: ryan.roberts, baolin.wang, lance.yang, mark.rutland, akpm, david,
	ljs, liam, vbabka, rppt, surenb, mhocko, linux-arm-kernel,
	linux-mm, linux-kernel, stable



On 13/10/26 3:06 pm, Zhao Gongyi wrote:
> The transparent contpte layer treats PTE_CONT as software state: when a
> PTE has PTE_CONT set, the contpte helpers may align the operation to a
> CONT_PTE_SIZE block and operate on the whole block. That invariant only
> makes sense for valid mappings. Explicit users such as hugetlb manage
> their contiguous encodings separately.
> 
> contpte_set_ptes() currently sets PTE_CONT from alignment alone. Batched
> set_ptes() users can therefore install present-invalid PTEs with
> PTE_CONT set, for example mprotect(PROT_NONE), NUMA hinting through
> modify_prot_commit_ptes(), fork() copying a PROT_NONE large folio, or a
> PMD split installing PAGE_NONE PTEs.
> 
> This is not a hardware programming problem: invalid descriptors are not
> translated, so the hardware ignores descriptor bits such as PTE_CONT. It
> is a software-state problem. Some arm64 fast paths intentionally test
> pte_cont() directly instead of pte_valid_cont(), for example commit
> 9970a9a27ffc ("arm64: mm: implement the architecture-specific
> test_and_clear_young_ptes()"). Fixing the producer side preserves these
> fast paths and covers all consumers.
> 
> In the observed failure, mprotect(PROT_NONE) installed a full
> CONT_PTE_SIZE range of present-invalid PTEs with PTE_CONT set.
> MADV_GUARD_INSTALL then zapped one PTE. The nr == 1 clear_full_ptes()
> path only unfolds pte_valid_cont() entries, so the neighbouring
> present-invalid entries kept PTE_CONT while the target entry became a
> guard marker.
> 
> clear_refs_pte_range() then reached the marker through:
> 
>   ptep_test_and_clear_young()
>   test_and_clear_young_ptes()
>   contpte_test_and_clear_young_ptes()
>   contpte_align_addr_ptep()
>   __ptep_test_and_clear_young()
> 
> The marker changed from old_pte=0x00000000000047c0 to
> new_pte=0x00000000000043c0 by clearing PTE_AF. For swap PTEs, bit 10 is
> part of the swap type field, bits 6-10: 0x47c0 is SWP_PTE_MARKER with
> offset 4, while 0x43c0 has type 15 and is decoded as an ordinary swap
> entry. Zap later warned in swap_put_entries_direct() with a bad swap
> entry.
> 
> This is triggerable by a user process against its own address space. On
> systems running with panic_on_warn, the warning becomes a denial of
> service.
> 
> Fix the root cause by making contpte_set_ptes() use the transparent
> contpte encoding only for valid descriptors. After this change, the
> transparent contpte layer preserves the invariant that pte_cont(pte)
> implies pte_valid(pte), so fast consumers can keep using PTE_CONT as
> sufficient evidence of a transparent contpte block.
> 
> Fixes: 4602e5757bcc ("arm64/mm: wire up PTE_CONT for user mappings")
> Cc: stable@vger.kernel.org
> Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com>
> ---
> Validation on an arm64 QEMU/KVM KASAN setup with CONFIG_ARM64_4K_PAGES=y
> and CONFIG_ARM64_CONTPTE=y, using an out-of-tree guard-region reproducer:
> 
>   populate a CONT_PTE_SIZE anonymous THP range
>   mprotect(PROT_NONE) on the range
>   MADV_GUARD_INSTALL on one PTE in that range
>   write "1" to /proc/self/clear_refs
>   restore access and remove the guard marker

Thanks, could you please give the code of the reproducer. That will
save us some time.


> 
>   Unfixed kernel: failed in the first iteration, with 65 bad swap
>                   messages and a WARNING in swap_put_entries_direct()
>   This patch:     completed 100 iterations without warnings
> 
> Additional producer-side instrumentation confirmed the same invalid
> batch state outside the mprotect reproducer:
> 
>   Unfixed fork():     copy_page_range() wrote out_cont=1
>   Unfixed PMD split: split_huge_pmd_locked() wrote out_cont=1
>   This patch:        both paths wrote out_cont=0
> 
>  arch/arm64/include/asm/pgtable.h | 5 ++++-
>  arch/arm64/mm/contpte.c          | 7 +++++++
>  2 files changed, 11 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/arm64/include/asm/pgtable.h b/arch/arm64/include/asm/pgtable.h
> index 6000905a2..d6246042b 100644
> --- a/arch/arm64/include/asm/pgtable.h
> +++ b/arch/arm64/include/asm/pgtable.h
> @@ -1650,7 +1650,10 @@ extern void modify_prot_commit_ptes(struct vm_area_struct *vma, unsigned long ad
>  /*
>   * The contpte APIs are used to transparently manage the contiguous bit in ptes
>   * where it is possible and makes sense to do so. The PTE_CONT bit is considered
> - * a private implementation detail of the public ptep API (see below).
> + * a private implementation detail of the public ptep API (see below). The
> + * transparent contpte layer maintains the invariant that pte_cont(pte) implies
> + * pte_valid(pte); explicit contiguous-encoding users such as hugetlb manage
> + * their entries separately.
>   */
>  extern void __contpte_try_fold(struct mm_struct *mm, unsigned long addr,
>  				pte_t *ptep, pte_t pte);
> diff --git a/arch/arm64/mm/contpte.c b/arch/arm64/mm/contpte.c
> index 0acab179f..851fa2e44 100644
> --- a/arch/arm64/mm/contpte.c
> +++ b/arch/arm64/mm/contpte.c
> @@ -463,6 +463,13 @@ void contpte_set_ptes(struct mm_struct *mm, unsigned long addr,
>  	if (!mm_is_user(mm))
>  		return __set_ptes(mm, addr, ptep, pte, nr);
>  
> +	/*
> +	 * Keep the producer-side invariant documented in pgtable.h true even
> +	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
> +	 */
> +	if (!pte_valid(pte))
> +		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
> +
>  	end = addr + (nr << PAGE_SHIFT);
>  	pfn = pte_pfn(pte);
>  	prot = pte_pgprot(pte);



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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-13  9:36 [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs Zhao Gongyi
  2026-10-13 10:13 ` Dev Jain
@ 2026-10-13 15:39 ` Lance Yang
  2026-10-14  6:31   ` Zhao Gongyi
  2026-10-14  9:41 ` David Hildenbrand (Arm)
  2 siblings, 1 reply; 13+ messages in thread
From: Lance Yang @ 2026-10-13 15:39 UTC (permalink / raw)
  To: zhaogongyi
  Cc: catalin.marinas, will, ryan.roberts, dev.jain, baolin.wang,
	lance.yang, mark.rutland, akpm, david, ljs, liam, vbabka, rppt,
	surenb, mhocko, linux-arm-kernel, linux-mm, linux-kernel, stable


On Tue, Oct 13, 2026 at 05:36:00PM +0800, Zhao Gongyi wrote:
[...]
>
>Fixes: 4602e5757bcc ("arm64/mm: wire up PTE_CONT for user mappings")
>Cc: stable@vger.kernel.org
>Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com>
>---

Good catch! Thanks!

[...]
>diff --git a/arch/arm64/mm/contpte.c b/arch/arm64/mm/contpte.c
>index 0acab179f..851fa2e44 100644
>--- a/arch/arm64/mm/contpte.c
>+++ b/arch/arm64/mm/contpte.c
>@@ -463,6 +463,13 @@ void contpte_set_ptes(struct mm_struct *mm, unsigned long addr,
> 	if (!mm_is_user(mm))
> 		return __set_ptes(mm, addr, ptep, pte, nr);
> 
>+	/*
>+	 * Keep the producer-side invariant documented in pgtable.h true even
>+	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
>+	 */
>+	if (!pte_valid(pte))
>+		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);

set_ptes() is the only in-tree caller, and it already clears PTE_CONT
before we get here ... no? Why not fold !pte_valid(pte) into the
!mm_is_user(mm) check below and return __set_ptes() directly?

	if (!mm_is_user(mm) || !pte_valid(pte))
		return __set_ptes(mm, addr, ptep, pte, nr);

If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
hide a caller bug :)

Cheers, Lance

>+
> 	end = addr + (nr << PAGE_SHIFT);
> 	pfn = pte_pfn(pte);
> 	prot = pte_pgprot(pte);
>-- 
>2.39.5 (Apple Git-154)
>


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-13 10:13 ` Dev Jain
@ 2026-10-14  6:16   ` Zhao Gongyi
  0 siblings, 0 replies; 13+ messages in thread
From: Zhao Gongyi @ 2026-10-14  6:16 UTC (permalink / raw)
  To: Dev Jain, catalin.marinas, will
  Cc: ryan.roberts, baolin.wang, lance.yang, mark.rutland, akpm, david,
	ljs, liam, vbabka, rppt, surenb, mhocko, linux-arm-kernel,
	linux-mm, linux-kernel, stable

On 2026/10/13 18:13, Dev Jain wrote:
> 
> 
> Thanks, could you please give the code of the reproducer. That will
> save us some time.
> 
> 

Hi Dev,

Sure, sorry for not including it in the first mail.

The standalone reproducer I used is below. It assumes an arm64 kernel 
with CONFIG_ARM64_CONTPTE=y and a CONT_PTE_SIZE anonymous mTHP order 
available. When run as root, it tries to enable THP and the matching 
mTHP order through sysfs. These sysfs writes are best-effort; if the 
matching order is not available or cannot be enabled, the reproducer
may not hit the issue.

On a 4K-page kernel, CONT_PTE_SIZE is 64KB.

Compile and run:

       gcc -O2 -Wall contpte_guard_repro.c -o contpte_guard_repro
       ./contpte_guard_repro 100

On my unfixed KASAN kernel with panic_on_warn=1, the first iteration hit
the bad swap entry / swap_put_entries_direct() warning. With this patch,
100 iterations completed without warnings.

Here is the reproducer:

   #define _GNU_SOURCE
   #include <errno.h>
   #include <fcntl.h>
   #include <stdio.h>
   #include <stdlib.h>
   #include <string.h>
   #include <sys/mman.h>
   #include <unistd.h>

   #ifndef MADV_HUGEPAGE
   #define MADV_HUGEPAGE 14
   #endif
   #ifndef MADV_POPULATE_WRITE
   #define MADV_POPULATE_WRITE 23
   #endif
   #ifndef MADV_GUARD_INSTALL
   #define MADV_GUARD_INSTALL 102
   #endif
   #ifndef MADV_GUARD_REMOVE
   #define MADV_GUARD_REMOVE 103
   #endif

   static void write_file(const char *path, const char *val)
   {
       int fd = open(path, O_WRONLY | O_CLOEXEC);

       if (fd < 0)
           return;
       (void)write(fd, val, strlen(val));
       close(fd);
   }

   static void write_clear_refs(void)
   {
       int fd = open("/proc/self/clear_refs", O_WRONLY | O_CLOEXEC);

       if (fd < 0) {
           perror("open clear_refs");
           exit(1);
       }
       if (write(fd, "1", 1) != 1) {
           perror("write clear_refs");
           exit(1);
       }
       close(fd);
   }

   static unsigned long contpte_size(unsigned long page_size)
   {
       if (page_size == 4096)
           return 64 * 1024;
       if (page_size == 16384 || page_size == 65536)
           return 2 * 1024 * 1024;

       fprintf(stderr, "unsupported page size %lu\n", page_size);
       exit(1);
   }

   static void enable_mthp(unsigned long size)
   {
       char path[256];

       write_file("/sys/kernel/mm/transparent_hugepage/enabled", 
"always\n");
       write_file("/sys/kernel/mm/transparent_hugepage/defrag", "always\n");

       snprintf(path, sizeof(path),
            "/sys/kernel/mm/transparent_hugepage/hugepages-%lukB/enabled",
            size / 1024);
       write_file(path, "always\n");
   }

   static void touch_range(char *p, unsigned long len, unsigned long 
page_size)
   {
       for (unsigned long off = 0; off < len; off += page_size)
           p[off] = (char)off;
   }

   int main(int argc, char **argv)
   {
       const int loops = argc > 1 ? atoi(argv[1]) : 1;
       const unsigned long page_size = sysconf(_SC_PAGESIZE);
       const unsigned long csize = contpte_size(page_size);
       const unsigned long nr_pages = 2 * csize / page_size;
       const unsigned long len = nr_pages * page_size;
       const unsigned long map_len = len + csize;
       const unsigned long guard_idx = csize / page_size - 3;

       enable_mthp(csize);

       for (int i = 0; i < loops; i++) {
           char *map, *p;

           map = mmap(NULL, map_len, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
           if (map == MAP_FAILED) {
               perror("mmap");
               return 1;
           }

           p = (char *)(((unsigned long)map + csize - 1) & ~(csize - 1));

           if (madvise(p, len, MADV_HUGEPAGE))
               perror("madvise MADV_HUGEPAGE");
           if (madvise(p, len, MADV_POPULATE_WRITE))
               perror("madvise MADV_POPULATE_WRITE");

           touch_range(p, len, page_size);

           if (mprotect(p, len, PROT_NONE)) {
               perror("mprotect PROT_NONE");
               return 1;
           }
           if (madvise(p + guard_idx * page_size, page_size,
                   MADV_GUARD_INSTALL)) {
               perror("madvise MADV_GUARD_INSTALL");
               return 1;
           }

           write_clear_refs();

           if (munmap(map, map_len)) {
               perror("munmap");
               return 1;
           }
       }

       return 0;
   }


Thanks,
Zhao


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-13 15:39 ` Lance Yang
@ 2026-10-14  6:31   ` Zhao Gongyi
  2026-10-14  6:46     ` Lance Yang
  2026-10-14 10:03     ` Dev Jain
  0 siblings, 2 replies; 13+ messages in thread
From: Zhao Gongyi @ 2026-10-14  6:31 UTC (permalink / raw)
  To: Lance Yang
  Cc: catalin.marinas, will, ryan.roberts, dev.jain, baolin.wang,
	mark.rutland, akpm, david, ljs, liam, vbabka, rppt, surenb,
	mhocko, linux-arm-kernel, linux-mm, linux-kernel, stable

On 10/13/26 11:39 PM, Lance Yang wrote:
>>
>> +	/*
>> +	 * Keep the producer-side invariant documented in pgtable.h true even
>> +	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
>> +	 */
>> +	if (!pte_valid(pte))
>> +		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
> 
> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
> before we get here ... no? Why not fold !pte_valid(pte) into the
> !mm_is_user(mm) check below and return __set_ptes() directly?
> 
> 	if (!mm_is_user(mm) || !pte_valid(pte))
> 		return __set_ptes(mm, addr, ptep, pte, nr);
> 
> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
> rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
> hide a caller bug :)
> 

Hi Lance,

Thanks for taking a look.

Yes, you are right. The public set_ptes() wrapper already strips PTE_CONT
before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
path is unnecessary for the current in-tree path.

The issue I was trying to avoid is not that the incoming pte already has
PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
and then adds PTE_CONT again based on alignment. For an invalid descriptor,
that transparent contiguous encoding should be skipped entirely.

So your suggested shape makes sense to me. I will change this to return
through __set_ptes() directly for invalid descriptors, and add a warning
for any unexpected direct caller that passes an invalid PTE with PTE_CONT
set, e.g.:

	if (!mm_is_user(mm) || !pte_valid(pte)) {
		VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));
		return __set_ptes(mm, addr, ptep, pte, nr);
	}

That keeps the fix focused on not creating new invalid contpte entries,
while still catching a bad direct caller instead of silently sanitizing it.

Thanks,
Zhao


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14  6:31   ` Zhao Gongyi
@ 2026-10-14  6:46     ` Lance Yang
  2026-10-14  8:38       ` Baolin Wang
  2026-10-14 10:03     ` Dev Jain
  1 sibling, 1 reply; 13+ messages in thread
From: Lance Yang @ 2026-10-14  6:46 UTC (permalink / raw)
  To: Zhao Gongyi
  Cc: catalin.marinas, will, ryan.roberts, dev.jain, baolin.wang,
	mark.rutland, akpm, david, ljs, liam, vbabka, rppt, surenb,
	mhocko, linux-arm-kernel, linux-mm, linux-kernel, stable



On 2026/10/14 14:31, Zhao Gongyi wrote:
> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>
>>> +	/*
>>> +	 * Keep the producer-side invariant documented in pgtable.h true even
>>> +	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
>>> +	 */
>>> +	if (!pte_valid(pte))
>>> +		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>
>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>> before we get here ... no? Why not fold !pte_valid(pte) into the
>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>
>> 	if (!mm_is_user(mm) || !pte_valid(pte))
>> 		return __set_ptes(mm, addr, ptep, pte, nr);
>>
>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
>> hide a caller bug :)
>>
> 
> Hi Lance,
> 
> Thanks for taking a look.

Cheers!

> Yes, you are right. The public set_ptes() wrapper already strips PTE_CONT
> before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
> path is unnecessary for the current in-tree path.
> 
> The issue I was trying to avoid is not that the incoming pte already has
> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
> and then adds PTE_CONT again based on alignment. For an invalid descriptor,
> that transparent contiguous encoding should be skipped entirely.
> 
> So your suggested shape makes sense to me. I will change this to return
> through __set_ptes() directly for invalid descriptors, and add a warning
> for any unexpected direct caller that passes an invalid PTE with PTE_CONT
> set, e.g.:
> 
> 	if (!mm_is_user(mm) || !pte_valid(pte)) {
> 		VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));
> 		return __set_ptes(mm, addr, ptep, pte, nr);
> 	}
> 
> That keeps the fix focused on not creating new invalid contpte entries,
> while still catching a bad direct caller instead of silently sanitizing it.

Thanks for the quick fix! Nothing jumped out at me :)

With that:

Tested-by: Lance Yang <lance.yang@linux.dev>


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14  6:46     ` Lance Yang
@ 2026-10-14  8:38       ` Baolin Wang
  2026-10-14  9:24         ` Lance Yang
  0 siblings, 1 reply; 13+ messages in thread
From: Baolin Wang @ 2026-10-14  8:38 UTC (permalink / raw)
  To: Lance Yang, Zhao Gongyi
  Cc: catalin.marinas, will, ryan.roberts, dev.jain, mark.rutland, akpm,
	david, ljs, liam, vbabka, rppt, surenb, mhocko, linux-arm-kernel,
	linux-mm, linux-kernel, stable



On 10/14/26 2:46 PM, Lance Yang wrote:
> 
> 
> On 2026/10/14 14:31, Zhao Gongyi wrote:
>> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>>
>>>> +    /*
>>>> +     * Keep the producer-side invariant documented in pgtable.h 
>>>> true even
>>>> +     * if a direct caller passes an invalid descriptor with 
>>>> PTE_CONT set.
>>>> +     */
>>>> +    if (!pte_valid(pte))
>>>> +        return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>>
>>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>>> before we get here ... no? Why not fold !pte_valid(pte) into the
>>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>>
>>>     if (!mm_is_user(mm) || !pte_valid(pte))
>>>         return __set_ptes(mm, addr, ptep, pte, nr);
>>>
>>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
>>> hide a caller bug :)
>>>
>>
>> Hi Lance,
>>
>> Thanks for taking a look.
> 
> Cheers!
> 
>> Yes, you are right. The public set_ptes() wrapper already strips PTE_CONT
>> before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
>> path is unnecessary for the current in-tree path.
>>
>> The issue I was trying to avoid is not that the incoming pte already has
>> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
>> and then adds PTE_CONT again based on alignment. For an invalid 
>> descriptor,
>> that transparent contiguous encoding should be skipped entirely.
>>
>> So your suggested shape makes sense to me. I will change this to return
>> through __set_ptes() directly for invalid descriptors, and add a warning
>> for any unexpected direct caller that passes an invalid PTE with PTE_CONT
>> set, e.g.:
>>
>>     if (!mm_is_user(mm) || !pte_valid(pte)) {
>>         VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));

mprotect() can trigger the warning here when setting PAGE_NONE for a 
batch of PTEs?

>>         return __set_ptes(mm, addr, ptep, pte, nr);

I am trying to convince myself that the 'nr' passed in is always 
CONT_PTE_SIZE aligned, but it seems OK.


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14  8:38       ` Baolin Wang
@ 2026-10-14  9:24         ` Lance Yang
  2026-10-14  9:35           ` Baolin Wang
  0 siblings, 1 reply; 13+ messages in thread
From: Lance Yang @ 2026-10-14  9:24 UTC (permalink / raw)
  To: Baolin Wang, Zhao Gongyi
  Cc: catalin.marinas, will, ryan.roberts, dev.jain, mark.rutland, akpm,
	david, ljs, liam, vbabka, rppt, surenb, mhocko, linux-arm-kernel,
	linux-mm, linux-kernel, stable



On 2026/10/14 16:38, Baolin Wang wrote:
> 
> 
> On 10/14/26 2:46 PM, Lance Yang wrote:
>>
>>
>> On 2026/10/14 14:31, Zhao Gongyi wrote:
>>> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>>>
>>>>> +    /*
>>>>> +     * Keep the producer-side invariant documented in pgtable.h 
>>>>> true even
>>>>> +     * if a direct caller passes an invalid descriptor with 
>>>>> PTE_CONT set.
>>>>> +     */
>>>>> +    if (!pte_valid(pte))
>>>>> +        return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>>>
>>>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>>>> before we get here ... no? Why not fold !pte_valid(pte) into the
>>>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>>>
>>>>     if (!mm_is_user(mm) || !pte_valid(pte))
>>>>         return __set_ptes(mm, addr, ptep, pte, nr);
>>>>
>>>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>>>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit 
>>>> would
>>>> hide a caller bug :)
>>>>
>>>
>>> Hi Lance,
>>>
>>> Thanks for taking a look.
>>
>> Cheers!
>>
>>> Yes, you are right. The public set_ptes() wrapper already strips 
>>> PTE_CONT
>>> before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
>>> path is unnecessary for the current in-tree path.
>>>
>>> The issue I was trying to avoid is not that the incoming pte already has
>>> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
>>> and then adds PTE_CONT again based on alignment. For an invalid 
>>> descriptor,
>>> that transparent contiguous encoding should be skipped entirely.
>>>
>>> So your suggested shape makes sense to me. I will change this to return
>>> through __set_ptes() directly for invalid descriptors, and add a warning
>>> for any unexpected direct caller that passes an invalid PTE with 
>>> PTE_CONT
>>> set, e.g.:
>>>
>>>     if (!mm_is_user(mm) || !pte_valid(pte)) {
>>>         VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));
> 
> mprotect() can trigger the warning here when setting PAGE_NONE for a 
> batch of PTEs?

Nope. set_ptes() clears PTE_CONT before calling contpte_set_ptes().
PAGE_NONE makes !pte_valid(pte) true, but pte_cont(pte) is false,
so the warning won't fire :)

>>>         return __set_ptes(mm, addr, ptep, pte, nr);
> 
> I am trying to convince myself that the 'nr' passed in is always 
> CONT_PTE_SIZE aligned, but it seems OK.

Still nope :)

IIRC, any partially covered contpte block should already be unfolded
by the time we get here ...

We're just installing PTEs without PTE_CONT into cleared entries,
so nr doesn't need to be aligned to CONT_PTES.

Cheers, Lance


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14  9:24         ` Lance Yang
@ 2026-10-14  9:35           ` Baolin Wang
  0 siblings, 0 replies; 13+ messages in thread
From: Baolin Wang @ 2026-10-14  9:35 UTC (permalink / raw)
  To: Lance Yang, Zhao Gongyi
  Cc: catalin.marinas, will, ryan.roberts, dev.jain, mark.rutland, akpm,
	david, ljs, liam, vbabka, rppt, surenb, mhocko, linux-arm-kernel,
	linux-mm, linux-kernel, stable



On 10/14/26 5:24 PM, Lance Yang wrote:
> 
> 
> On 2026/10/14 16:38, Baolin Wang wrote:
>>
>>
>> On 10/14/26 2:46 PM, Lance Yang wrote:
>>>
>>>
>>> On 2026/10/14 14:31, Zhao Gongyi wrote:
>>>> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>>>>
>>>>>> +    /*
>>>>>> +     * Keep the producer-side invariant documented in pgtable.h 
>>>>>> true even
>>>>>> +     * if a direct caller passes an invalid descriptor with 
>>>>>> PTE_CONT set.
>>>>>> +     */
>>>>>> +    if (!pte_valid(pte))
>>>>>> +        return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>>>>
>>>>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>>>>> before we get here ... no? Why not fold !pte_valid(pte) into the
>>>>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>>>>
>>>>>     if (!mm_is_user(mm) || !pte_valid(pte))
>>>>>         return __set_ptes(mm, addr, ptep, pte, nr);
>>>>>
>>>>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>>>>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit 
>>>>> would
>>>>> hide a caller bug :)
>>>>>
>>>>
>>>> Hi Lance,
>>>>
>>>> Thanks for taking a look.
>>>
>>> Cheers!
>>>
>>>> Yes, you are right. The public set_ptes() wrapper already strips 
>>>> PTE_CONT
>>>> before calling contpte_set_ptes(), so the pte_mknoncont() in the 
>>>> invalid
>>>> path is unnecessary for the current in-tree path.
>>>>
>>>> The issue I was trying to avoid is not that the incoming pte already 
>>>> has
>>>> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/ 
>>>> prot
>>>> and then adds PTE_CONT again based on alignment. For an invalid 
>>>> descriptor,
>>>> that transparent contiguous encoding should be skipped entirely.
>>>>
>>>> So your suggested shape makes sense to me. I will change this to return
>>>> through __set_ptes() directly for invalid descriptors, and add a 
>>>> warning
>>>> for any unexpected direct caller that passes an invalid PTE with 
>>>> PTE_CONT
>>>> set, e.g.:
>>>>
>>>>     if (!mm_is_user(mm) || !pte_valid(pte)) {
>>>>         VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));
>>
>> mprotect() can trigger the warning here when setting PAGE_NONE for a 
>> batch of PTEs?
> 
> Nope. set_ptes() clears PTE_CONT before calling contpte_set_ptes().
> PAGE_NONE makes !pte_valid(pte) true, but pte_cont(pte) is false,
> so the warning won't fire :)

Ah, right.

>>>>         return __set_ptes(mm, addr, ptep, pte, nr);
>>
>> I am trying to convince myself that the 'nr' passed in is always 
>> CONT_PTE_SIZE aligned, but it seems OK.
> 
> Still nope :)
> 
> IIRC, any partially covered contpte block should already be unfolded
> by the time we get here ...
> 
> We're just installing PTEs without PTE_CONT into cleared entries,
> so nr doesn't need to be aligned to CONT_PTES.

OK. I see. Thanks for explanation. So looks good to me.

Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-13  9:36 [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs Zhao Gongyi
  2026-10-13 10:13 ` Dev Jain
  2026-10-13 15:39 ` Lance Yang
@ 2026-10-14  9:41 ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 13+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-14  9:41 UTC (permalink / raw)
  To: Zhao Gongyi, catalin.marinas, will, Ryan Roberts
  Cc: ryan.roberts, dev.jain, baolin.wang, lance.yang, mark.rutland,
	akpm, ljs, liam, vbabka, rppt, surenb, mhocko, linux-arm-kernel,
	linux-mm, linux-kernel, stable

CCing Ryan:

https://lore.kernel.org/r/20261013093600.54995-1-zhaogongyi@bytedance.com

On 10/13/26 11:36, Zhao Gongyi wrote:
> The transparent contpte layer treats PTE_CONT as software state: when a
> PTE has PTE_CONT set, the contpte helpers may align the operation to a
> CONT_PTE_SIZE block and operate on the whole block. That invariant only
> makes sense for valid mappings. Explicit users such as hugetlb manage
> their contiguous encodings separately.
> 
> contpte_set_ptes() currently sets PTE_CONT from alignment alone. Batched
> set_ptes() users can therefore install present-invalid PTEs with
> PTE_CONT set, for example mprotect(PROT_NONE), NUMA hinting through
> modify_prot_commit_ptes(), fork() copying a PROT_NONE large folio, or a
> PMD split installing PAGE_NONE PTEs.
> 
> This is not a hardware programming problem: invalid descriptors are not
> translated, so the hardware ignores descriptor bits such as PTE_CONT. It
> is a software-state problem. Some arm64 fast paths intentionally test
> pte_cont() directly instead of pte_valid_cont(), for example commit
> 9970a9a27ffc ("arm64: mm: implement the architecture-specific
> test_and_clear_young_ptes()"). Fixing the producer side preserves these
> fast paths and covers all consumers.
> 
> In the observed failure, mprotect(PROT_NONE) installed a full
> CONT_PTE_SIZE range of present-invalid PTEs with PTE_CONT set.
> MADV_GUARD_INSTALL then zapped one PTE. The nr == 1 clear_full_ptes()
> path only unfolds pte_valid_cont() entries, so the neighbouring
> present-invalid entries kept PTE_CONT while the target entry became a
> guard marker.

Hm, is there a good reason why we cannot just use the cont-pte bit for all
*present* PTEs, even if HW would not be prepared to benefit (!pte_valid())?

I guess we really only have to make sure to shatter properly, right?

-- 
Cheers,

David


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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14  6:31   ` Zhao Gongyi
  2026-10-14  6:46     ` Lance Yang
@ 2026-10-14 10:03     ` Dev Jain
  2026-10-14 10:45       ` Lance Yang
  1 sibling, 1 reply; 13+ messages in thread
From: Dev Jain @ 2026-10-14 10:03 UTC (permalink / raw)
  To: Zhao Gongyi, Lance Yang
  Cc: catalin.marinas, will, ryan.roberts, baolin.wang, mark.rutland,
	akpm, david, ljs, liam, vbabka, rppt, surenb, mhocko,
	linux-arm-kernel, linux-mm, linux-kernel, stable



On 14/10/26 12:01 pm, Zhao Gongyi wrote:
> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>
>>> +	/*
>>> +	 * Keep the producer-side invariant documented in pgtable.h true even
>>> +	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
>>> +	 */
>>> +	if (!pte_valid(pte))
>>> +		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>
>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>> before we get here ... no? Why not fold !pte_valid(pte) into the
>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>
>> 	if (!mm_is_user(mm) || !pte_valid(pte))
>> 		return __set_ptes(mm, addr, ptep, pte, nr);
>>
>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
>> hide a caller bug :)
>>
> 
> Hi Lance,
> 
> Thanks for taking a look.
> 
> Yes, you are right. The public set_ptes() wrapper already strips PTE_CONT
> before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
> path is unnecessary for the current in-tree path.
> 
> The issue I was trying to avoid is not that the incoming pte already has
> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
> and then adds PTE_CONT again based on alignment. For an invalid descriptor,
> that transparent contiguous encoding should be skipped entirely.
> 
> So your suggested shape makes sense to me. I will change this to return
> through __set_ptes() directly for invalid descriptors, and add a warning
> for any unexpected direct caller that passes an invalid PTE with PTE_CONT
> set, e.g.:
> 
> 	if (!mm_is_user(mm) || !pte_valid(pte)) {
> 		VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));

set_ptes does pte_mknoncont(), so the warning will *never* fire I think?


> 		return __set_ptes(mm, addr, ptep, pte, nr);
> 	}
> 
> That keeps the fix focused on not creating new invalid contpte entries,
> while still catching a bad direct caller instead of silently sanitizing it.
> 
> Thanks,
> Zhao



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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14 10:03     ` Dev Jain
@ 2026-10-14 10:45       ` Lance Yang
  2026-10-14 11:02         ` Dev Jain
  0 siblings, 1 reply; 13+ messages in thread
From: Lance Yang @ 2026-10-14 10:45 UTC (permalink / raw)
  To: Dev Jain, Zhao Gongyi
  Cc: catalin.marinas, will, ryan.roberts, baolin.wang, mark.rutland,
	akpm, david, ljs, liam, vbabka, rppt, surenb, mhocko,
	linux-arm-kernel, linux-mm, linux-kernel, stable



On 2026/10/14 18:03, Dev Jain wrote:
> 
> 
> On 14/10/26 12:01 pm, Zhao Gongyi wrote:
>> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>>
>>>> +	/*
>>>> +	 * Keep the producer-side invariant documented in pgtable.h true even
>>>> +	 * if a direct caller passes an invalid descriptor with PTE_CONT set.
>>>> +	 */
>>>> +	if (!pte_valid(pte))
>>>> +		return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>>
>>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>>> before we get here ... no? Why not fold !pte_valid(pte) into the
>>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>>
>>> 	if (!mm_is_user(mm) || !pte_valid(pte))
>>> 		return __set_ptes(mm, addr, ptep, pte, nr);
>>>
>>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
>>> hide a caller bug :)
>>>
>>
>> Hi Lance,
>>
>> Thanks for taking a look.
>>
>> Yes, you are right. The public set_ptes() wrapper already strips PTE_CONT
>> before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
>> path is unnecessary for the current in-tree path.
>>
>> The issue I was trying to avoid is not that the incoming pte already has
>> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
>> and then adds PTE_CONT again based on alignment. For an invalid descriptor,
>> that transparent contiguous encoding should be skipped entirely.
>>
>> So your suggested shape makes sense to me. I will change this to return
>> through __set_ptes() directly for invalid descriptors, and add a warning
>> for any unexpected direct caller that passes an invalid PTE with PTE_CONT
>> set, e.g.:
>>
>> 	if (!mm_is_user(mm) || !pte_valid(pte)) {
>> 		VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));
> 
> set_ptes does pte_mknoncont(), so the warning will *never* fire I think?

YES. Just a sanity check to catch a future direct caller passing
a !pte_valid PTE with PTE_CONT set, rather than silently clearing
the bit :)

Cheers, Lance

> 
>> 		return __set_ptes(mm, addr, ptep, pte, nr);
>> 	}
>>
>> That keeps the fix focused on not creating new invalid contpte entries,
>> while still catching a bad direct caller instead of silently sanitizing it.
>>
>> Thanks,
>> Zhao
> 



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

* Re: [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs
  2026-10-14 10:45       ` Lance Yang
@ 2026-10-14 11:02         ` Dev Jain
  0 siblings, 0 replies; 13+ messages in thread
From: Dev Jain @ 2026-10-14 11:02 UTC (permalink / raw)
  To: Lance Yang, Zhao Gongyi
  Cc: catalin.marinas, will, ryan.roberts, baolin.wang, mark.rutland,
	akpm, david, ljs, liam, vbabka, rppt, surenb, mhocko,
	linux-arm-kernel, linux-mm, linux-kernel, stable



On 14/10/26 4:15 pm, Lance Yang wrote:
> 
> 
> On 2026/10/14 18:03, Dev Jain wrote:
>>
>>
>> On 14/10/26 12:01 pm, Zhao Gongyi wrote:
>>> On 10/13/26 11:39 PM, Lance Yang wrote:
>>>>>
>>>>> +    /*
>>>>> +     * Keep the producer-side invariant documented in pgtable.h true even
>>>>> +     * if a direct caller passes an invalid descriptor with PTE_CONT set.
>>>>> +     */
>>>>> +    if (!pte_valid(pte))
>>>>> +        return __set_ptes(mm, addr, ptep, pte_mknoncont(pte), nr);
>>>>
>>>> set_ptes() is the only in-tree caller, and it already clears PTE_CONT
>>>> before we get here ... no? Why not fold !pte_valid(pte) into the
>>>> !mm_is_user(mm) check below and return __set_ptes() directly?
>>>>
>>>>     if (!mm_is_user(mm) || !pte_valid(pte))
>>>>         return __set_ptes(mm, addr, ptep, pte, nr);
>>>>
>>>> If a direct caller does pass a !pte_valid PTE with PTE_CONT set, I'd
>>>> rather add a VM_WARN_ON() to catch that. Silently clearing the bit would
>>>> hide a caller bug :)
>>>>
>>>
>>> Hi Lance,
>>>
>>> Thanks for taking a look.
>>>
>>> Yes, you are right. The public set_ptes() wrapper already strips PTE_CONT
>>> before calling contpte_set_ptes(), so the pte_mknoncont() in the invalid
>>> path is unnecessary for the current in-tree path.
>>>
>>> The issue I was trying to avoid is not that the incoming pte already has
>>> PTE_CONT set, but that contpte_set_ptes() rebuilds the pte from pfn/prot
>>> and then adds PTE_CONT again based on alignment. For an invalid descriptor,
>>> that transparent contiguous encoding should be skipped entirely.
>>>
>>> So your suggested shape makes sense to me. I will change this to return
>>> through __set_ptes() directly for invalid descriptors, and add a warning
>>> for any unexpected direct caller that passes an invalid PTE with PTE_CONT
>>> set, e.g.:
>>>
>>>     if (!mm_is_user(mm) || !pte_valid(pte)) {
>>>         VM_WARN_ON_ONCE(!pte_valid(pte) && pte_cont(pte));
>>
>> set_ptes does pte_mknoncont(), so the warning will *never* fire I think?
> 
> YES. Just a sanity check to catch a future direct caller passing
> a !pte_valid PTE with PTE_CONT set, rather than silently clearing
> the bit :)

Ah okay you said "future direct caller". I got confused since if the only
caller is set_ptes() then the warning is useless by construction.


> 
> Cheers, Lance
> 
>>
>>>         return __set_ptes(mm, addr, ptep, pte, nr);
>>>     }
>>>
>>> That keeps the fix focused on not creating new invalid contpte entries,
>>> while still catching a bad direct caller instead of silently sanitizing it.
>>>
>>> Thanks,
>>> Zhao
>>
> 



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

end of thread, other threads:[~2026-10-14 11:02 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13  9:36 [PATCH] arm64/mm: restrict transparent contpte mappings to valid PTEs Zhao Gongyi
2026-10-13 10:13 ` Dev Jain
2026-10-14  6:16   ` Zhao Gongyi
2026-10-13 15:39 ` Lance Yang
2026-10-14  6:31   ` Zhao Gongyi
2026-10-14  6:46     ` Lance Yang
2026-10-14  8:38       ` Baolin Wang
2026-10-14  9:24         ` Lance Yang
2026-10-14  9:35           ` Baolin Wang
2026-10-14 10:03     ` Dev Jain
2026-10-14 10:45       ` Lance Yang
2026-10-14 11:02         ` Dev Jain
2026-10-14  9:41 ` David Hildenbrand (Arm)

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.