Linux-mm Archive on 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

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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox