mm/pgtable: Support for page table check on s390
Subsystems (7)
Alexander Gordeev
maintainercover 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
Alexander Gordeev
maintainerPATCH 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
@@ -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
@@ -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
@@ -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
@@ -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
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_structthat indicates whether an entry in question is mapped to a user space. So passmm_structtopxx_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?
Alexander Gordeev
maintainerRe: [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(-)Reviewed-by Andrew Morton <[email protected]>
Thank you, Andrew!
Quoted 1 line
I assume this can go into the s390 tree?
I think so.
@Vasily?
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
Reviewed-by Andrew Morton <[email protected]>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!
Re: [PATCH 1/4] mm/page_table_check: Pass mm_struct to pxx_user_accessible_page()
In reply to Alexander GordeevQuoted 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_structthat indicates whether an entry in question is mapped to a user space. So passmm_structtopxx_user_accessible_page()callbacks.
Quoted 6 lines
[[email protected]: rephrased commit message, removed braces]
Reviewed-by Gerald Schaefer <[email protected]>Signed-off-by Tobias Huschle <[email protected]>Signed-off-by Alexander Gordeev <[email protected]>---
Signature · 3 lines
Cheers, David
Alexander Gordeev
maintainerPATCH 2/4 s390/pgtable: Use set_pmd_bit() to invalidate PMD entry
In reply to Alexander GordeevCommit 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
@@ -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
Alexander Gordeev
maintainerPATCH 3/4 s390/pgtable: Add s390 support for page table check
In reply to Alexander GordeevFrom: 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
@@ -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
Alexander Gordeev
maintainerPATCH 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
@@ -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