Skip to contentSkip to conversation

mm/pgtable: Support for page table check on s390

PATCH Applied Alexander Gordeev 4 participants 9 messages Feb 23 – Feb 26

Subsystems (7)
Memory management x86 RISC-V arm64 +3 more subsystems

v1 of 2 Applied Latest is v2 →

lore
Share & tools
lore

Alexander Gordeev

author maintainer
#0 lore raw

cover mm/pgtable: Support for page table check on s390

Hi All,

This series adds mm_struct parameter to pxx_user_accessible_page() hooks, which is only used on s390 and does not affect other archs.

I reordered pxx_user_accessible_page() parameters to be consistent with the traditional order and removed unnecessary brackets in patch #1 - the only patch that touches the generic code and other archs.

Thanks!

Alexander Gordeev (2):
s390/pgtable: Use set_pmd_bit() to invalidate PMD entry
s390: Enable page table check for debug_defconfig

Tobias Huschle (2):
mm/page_table_check: Pass mm_struct to pxx_user_accessible_page() s390/pgtable: Add s390 support for page table check

 arch/arm64/include/asm/pgtable.h  |  6 ++--
 arch/riscv/include/asm/pgtable.h  |  6 ++--
 arch/s390/Kconfig                 |  1 +
 arch/s390/configs/debug_defconfig |  2 ++
 arch/s390/include/asm/pgtable.h   | 60 +++++++++++++++++++++++++++----
 arch/x86/include/asm/pgtable.h    |  6 ++--
 mm/page_table_check.c             | 15 ++++----
 7 files changed, 71 insertions(+), 25 deletions(-)
Signature · 1 line
2.51.0
author maintainer
#1 lore raw

PATCH 1/4 mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()

From: Tobias Huschle <[email protected]>

Unlike other architectures, s390 does not have means to distinguish kernel vs user page table entries - neither an entry itself, nor the address could be used for that. It is only the mm_struct that indicates whether an entry in question is mapped to a user space. So pass mm_struct to pxx_user_accessible_page() callbacks.

[[email protected]: rephrased commit message, removed braces]

---
 arch/arm64/include/asm/pgtable.h |  6 +++---
 arch/riscv/include/asm/pgtable.h |  6 +++---
 arch/x86/include/asm/pgtable.h   |  6 +++---
 mm/page_table_check.c            | 15 ++++++---------
 4 files changed, 15 insertions(+), 18 deletions(-)
arch/arm64/include/asm/pgtable.h +3 -3
index b3e58735c49b..ccf0e0638767 100644
@@ -1263,17 +1263,17 @@ static inline int pmdp_set_access_flags(struct vm_area_struct *vma,
 #endif
 
 #ifdef CONFIG_PAGE_TABLE_CHECK
-static inline bool pte_user_accessible_page(pte_t pte, unsigned long addr)
+static inline bool pte_user_accessible_page(struct mm_struct *mm, unsigned long addr, pte_t pte)
 {
 	return pte_valid(pte) && (pte_user(pte) || pte_user_exec(pte));
 }
 
-static inline bool pmd_user_accessible_page(pmd_t pmd, unsigned long addr)
+static inline bool pmd_user_accessible_page(struct mm_struct *mm, unsigned long addr, pmd_t pmd)
 {
 	return pmd_valid(pmd) && !pmd_table(pmd) && (pmd_user(pmd) || pmd_user_exec(pmd));
 }
 
-static inline bool pud_user_accessible_page(pud_t pud, unsigned long addr)
+static inline bool pud_user_accessible_page(struct mm_struct *mm, unsigned long addr, pud_t pud)
 {
 	return pud_valid(pud) && !pud_table(pud) && (pud_user(pud) || pud_user_exec(pud));
 }
arch/riscv/include/asm/pgtable.h +3 -3
index 08d1ca047104..affe46cf3bc5 100644
@@ -984,17 +984,17 @@ static inline void set_pud_at(struct mm_struct *mm, unsigned long addr,
 }
 
 #ifdef CONFIG_PAGE_TABLE_CHECK
-static inline bool pte_user_accessible_page(pte_t pte, unsigned long addr)
+static inline bool pte_user_accessible_page(struct mm_struct *mm, unsigned long addr, pte_t pte)
 {
 	return pte_present(pte) && pte_user(pte);
 }
 
-static inline bool pmd_user_accessible_page(pmd_t pmd, unsigned long addr)
+static inline bool pmd_user_accessible_page(struct mm_struct *mm, unsigned long addr, pmd_t pmd)
 {
 	return pmd_leaf(pmd) && pmd_user(pmd);
 }
 
-static inline bool pud_user_accessible_page(pud_t pud, unsigned long addr)
+static inline bool pud_user_accessible_page(struct mm_struct *mm, unsigned long addr, pud_t pud)
 {
 	return pud_leaf(pud) && pud_user(pud);
 }
arch/x86/include/asm/pgtable.h +3 -3
index 1662c5a8f445..f9353d5c7464 100644
@@ -1680,17 +1680,17 @@ static inline bool arch_has_hw_nonleaf_pmd_young(void)
 #endif
 
 #ifdef CONFIG_PAGE_TABLE_CHECK
-static inline bool pte_user_accessible_page(pte_t pte, unsigned long addr)
+static inline bool pte_user_accessible_page(struct mm_struct *mm, unsigned long addr, pte_t pte)
 {
 	return (pte_val(pte) & _PAGE_PRESENT) && (pte_val(pte) & _PAGE_USER);
 }
 
-static inline bool pmd_user_accessible_page(pmd_t pmd, unsigned long addr)
+static inline bool pmd_user_accessible_page(struct mm_struct *mm, unsigned long addr, pmd_t pmd)
 {
 	return pmd_leaf(pmd) && (pmd_val(pmd) & _PAGE_PRESENT) && (pmd_val(pmd) & _PAGE_USER);
 }
 
-static inline bool pud_user_accessible_page(pud_t pud, unsigned long addr)
+static inline bool pud_user_accessible_page(struct mm_struct *mm, unsigned long addr, pud_t pud)
 {
 	return pud_leaf(pud) && (pud_val(pud) & _PAGE_PRESENT) && (pud_val(pud) & _PAGE_USER);
 }
mm/page_table_check.c +6 -9
index 2708c2b3ac1f..53a8997ec043 100644
@@ -151,9 +151,8 @@ void __page_table_check_pte_clear(struct mm_struct *mm, unsigned long addr,
 	if (&init_mm == mm)
 		return;
 
-	if (pte_user_accessible_page(pte, addr)) {
+	if (pte_user_accessible_page(mm, addr, pte))
 		page_table_check_clear(pte_pfn(pte), PAGE_SIZE >> PAGE_SHIFT);
-	}
 }
 EXPORT_SYMBOL(__page_table_check_pte_clear);
 
@@ -163,9 +162,8 @@ void __page_table_check_pmd_clear(struct mm_struct *mm, unsigned long addr,
 	if (&init_mm == mm)
 		return;
 
-	if (pmd_user_accessible_page(pmd, addr)) {
+	if (pmd_user_accessible_page(mm, addr, pmd))
 		page_table_check_clear(pmd_pfn(pmd), PMD_SIZE >> PAGE_SHIFT);
-	}
 }
 EXPORT_SYMBOL(__page_table_check_pmd_clear);
 
@@ -175,9 +173,8 @@ void __page_table_check_pud_clear(struct mm_struct *mm, unsigned long addr,
 	if (&init_mm == mm)
 		return;
 
-	if (pud_user_accessible_page(pud, addr)) {
+	if (pud_user_accessible_page(mm, addr, pud))
 		page_table_check_clear(pud_pfn(pud), PUD_SIZE >> PAGE_SHIFT);
-	}
 }
 EXPORT_SYMBOL(__page_table_check_pud_clear);
 
@@ -211,7 +208,7 @@ void __page_table_check_ptes_set(struct mm_struct *mm, unsigned long addr,
 
 	for (i = 0; i < nr; i++)
 		__page_table_check_pte_clear(mm, addr + PAGE_SIZE * i, ptep_get(ptep + i));
-	if (pte_user_accessible_page(pte, addr))
+	if (pte_user_accessible_page(mm, addr, pte))
 		page_table_check_set(pte_pfn(pte), nr, pte_write(pte));
 }
 EXPORT_SYMBOL(__page_table_check_ptes_set);
@@ -241,7 +238,7 @@ void __page_table_check_pmds_set(struct mm_struct *mm, unsigned long addr,
 
 	for (i = 0; i < nr; i++)
 		__page_table_check_pmd_clear(mm, addr + PMD_SIZE * i, *(pmdp + i));
-	if (pmd_user_accessible_page(pmd, addr))
+	if (pmd_user_accessible_page(mm, addr, pmd))
 		page_table_check_set(pmd_pfn(pmd), stride * nr, pmd_write(pmd));
 }
 EXPORT_SYMBOL(__page_table_check_pmds_set);
@@ -257,7 +254,7 @@ void __page_table_check_puds_set(struct mm_struct *mm, unsigned long addr,
 
 	for (i = 0; i < nr; i++)
 		__page_table_check_pud_clear(mm, addr + PUD_SIZE * i, *(pudp + i));
-	if (pud_user_accessible_page(pud, addr))
+	if (pud_user_accessible_page(mm, addr, pud))
 		page_table_check_set(pud_pfn(pud), stride * nr, pud_write(pud));
 }
 EXPORT_SYMBOL(__page_table_check_puds_set);
Signature · 1 line
2.51.0
maintainer Reviewed-by on v1 1/4
#2 lore raw

Re: [PATCH 1/4] mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()

Quoted 9 lines · On Mon, 23 Feb 2026 12:53:13 +0100 Alexander Gordeev <[email protected]>…

From: Tobias Huschle <[email protected]>

Unlike other architectures, s390 does not have means to distinguish kernel vs user page table entries - neither an entry itself, nor the address could be used for that. It is only the mm_struct that indicates whether an entry in question is mapped to a user space. So pass mm_struct to pxx_user_accessible_page() callbacks.

Quoted 7 lines

...

 arch/arm64/include/asm/pgtable.h |  6 +++---
 arch/riscv/include/asm/pgtable.h |  6 +++---
 arch/x86/include/asm/pgtable.h   |  6 +++---
 mm/page_table_check.c            | 15 ++++++---------
 4 files changed, 15 insertions(+), 18 deletions(-)

I assume this can go into the s390 tree?

author maintainer
#3 lore raw

Re: [PATCH 1/4] mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()

Quoted 7 lines · Andrew Morton
Quoted 5 lines
 arch/arm64/include/asm/pgtable.h |  6 +++---
 arch/riscv/include/asm/pgtable.h |  6 +++---
 arch/x86/include/asm/pgtable.h   |  6 +++---
 mm/page_table_check.c            | 15 ++++++---------
 4 files changed, 15 insertions(+), 18 deletions(-)

Thank you, Andrew!

Quoted 1 line

I assume this can go into the s390 tree?

I think so.
@Vasily?

maintainer Applied
#4 lore raw

Re: [PATCH 1/4] mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()

Quoted 7 lines · Alexander Gordeev
Quoted 6 lines · Andrew Morton
Quoted 5 lines
 arch/arm64/include/asm/pgtable.h |  6 +++---
 arch/riscv/include/asm/pgtable.h |  6 +++---
 arch/x86/include/asm/pgtable.h   |  6 +++---
 mm/page_table_check.c            | 15 ++++++---------
 4 files changed, 15 insertions(+), 18 deletions(-)
Quoted 8 lines
Quoted 1 line

Thank you, Andrew!

Quoted 1 line

I assume this can go into the s390 tree?

I think so.
@Vasily?

I've picked it up, thank you!

Acked-by on v1 1/4
#5 lore raw

Re: [PATCH 1/4] mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()

In reply to Alexander Gordeev
Quoted 9 lines · Alexander Gordeev

From: Tobias Huschle <[email protected]>

Unlike other architectures, s390 does not have means to distinguish kernel vs user page table entries - neither an entry itself, nor the address could be used for that. It is only the mm_struct that indicates whether an entry in question is mapped to a user space. So pass mm_struct to pxx_user_accessible_page() callbacks.

Quoted 6 lines

[[email protected]: rephrased commit message, removed braces]

---

Signature · 3 lines
Cheers,

David
author maintainer
#6 lore raw

PATCH 2/4 s390/pgtable: Use set_pmd_bit() to invalidate PMD entry

In reply to Alexander Gordeev

Commit 3a5a8d343e1c ("mm: fix race between __split_huge_pmd_locked() and GUP-fast") failed to follow the convention and used direct PMD entry modification instead of set_pmd_bit().

---
 arch/s390/include/asm/pgtable.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
arch/s390/include/asm/pgtable.h +3 -3
index 1c3c3be93be9..04ec9fee6498 100644
@@ -1744,10 +1744,10 @@ static inline pmd_t pmdp_huge_clear_flush(struct vm_area_struct *vma,
 static inline pmd_t pmdp_invalidate(struct vm_area_struct *vma,
 				   unsigned long addr, pmd_t *pmdp)
 {
-	pmd_t pmd;
+	pmd_t pmd = *pmdp;
 
-	VM_WARN_ON_ONCE(!pmd_present(*pmdp));
-	pmd = __pmd(pmd_val(*pmdp) | _SEGMENT_ENTRY_INVALID);
+	VM_WARN_ON_ONCE(!pmd_present(pmd));
+	pmd = set_pmd_bit(pmd, __pgprot(_SEGMENT_ENTRY_INVALID));
 	return pmdp_xchg_direct(vma->vm_mm, addr, pmdp, pmd);
 }
 
Signature · 1 line
2.51.0
author maintainer
#7 lore raw

PATCH 3/4 s390/pgtable: Add s390 support for page table check

In reply to Alexander Gordeev

From: Tobias Huschle <[email protected]>

Add page table check hooks into routines that modify user page tables.

Unlike other architectures s390 does not have means to distinguish between kernel and user page table entries. Rely on the fact the page table check infrastructure itself operates on non-init_mm memory spaces only.

Use the provided mm_struct to verify that the memory space is not init_mm (aka not the kernel memory space) indeed. That check is supposed to be succeeded already (on some code paths even twice).

If the passed memory space by contrast is init_mm that would be an unexpected semantical change in generic code, so do VM_BUG_ON() in such case.

Unset _SEGMENT_ENTRY_READ bit to indicate that pmdp_invalidate() was applied against a huge PMD and is going to be updated by set_pmd_at() shortly. The hook pmd_user_accessible_page() should skip such entries until that, otherwise the page table accounting falls apart and BUG_ON() gets hit as result.

The invalidated huge PMD entry should not be confused with a PROT_NONE entry as reported by pmd_protnone(), though the entry characteristics exactly match: _SEGMENT_ENTRY_LARGE is set while _SEGMENT_ENTRY_READ is unset. Since pmd_protnone() implementation depends on NUMA_BALANCING configuration option, it should not be used in pmd_user_accessible_page() check, which is expected to be CONFIG_NUMA_BALANCING-agnostic.

Nevertheless, an invalidated huge PMD is technically still pmd_protnone() entry and it should not break other code paths once _SEGMENT_ENTRY_READ is unset. As of now, all pmd_protnone() checks are done under page table locks or exercise GUP-fast and HMM code paths, which are expected to be safe against concurrent page table updates.

Alternative approach would be using the last remaining unused PMD entry bit 0x800 to indicate that pmdp_invalidate() was called on a PMD. That would allow avoiding collisions with pmd_protnone() handling code paths, but saving the bit is more preferable way to go.

---
 arch/s390/Kconfig               |  1 +
 arch/s390/include/asm/pgtable.h | 54 ++++++++++++++++++++++++++++++---
 2 files changed, 51 insertions(+), 4 deletions(-)
arch/s390/Kconfig +1 -0
index edc927d9e85a..7bda45d30455 100644
@@ -154,6 +154,7 @@ config S390
 	select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 && CC_IS_CLANG
 	select ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS
 	select ARCH_SUPPORTS_NUMA_BALANCING
+	select ARCH_SUPPORTS_PAGE_TABLE_CHECK
 	select ARCH_SUPPORTS_PER_VMA_LOCK
 	select ARCH_USE_BUILTIN_BSWAP
 	select ARCH_USE_CMPXCHG_LOCKREF
arch/s390/include/asm/pgtable.h +50 -4

120 lines not on this page · raw message

Signature · 1 line
2.51.0
author maintainer
#8 lore raw

PATCH 4/4 s390: Enable page table check for debug_defconfig

In reply to Alexander Gordeev
---
 arch/s390/configs/debug_defconfig | 2 ++
 1 file changed, 2 insertions(+)
arch/s390/configs/debug_defconfig +2 -0
index 98fd0a2f51c6..12cdaaefb6db 100644
@@ -929,3 +929,5 @@ CONFIG_PERCPU_TEST=m
 CONFIG_ATOMIC64_SELFTEST=y
 CONFIG_TEST_BITOPS=m
 CONFIG_TEST_BPF=m
+CONFIG_PAGE_TABLE_CHECK=y
+CONFIG_PAGE_TABLE_CHECK_ENFORCED=y
Signature · 1 line
2.51.0