On creation and clearing of a page table mapping, instrument such calls
by invoking page_table_check_pte_set and page_table_check_pte_clear
respectively. These calls serve as a sanity check against illegal
mappings.

Enable ARCH_SUPPORTS_PAGE_TABLE_CHECK for all platforms.

Use set_pte internally, and cause this function to reassign a page table
entry without instrumentation. Generic code will be instrumented, as it
references set_pte_at.

See also:

riscv support in commit 3fee229a8eb9 ("riscv/mm: enable
ARCH_SUPPORTS_PAGE_TABLE_CHECK")
arm64 in commit 42b2547137f5 ("arm64/mm: enable
ARCH_SUPPORTS_PAGE_TABLE_CHECK")
x86_64 in commit d283d422c6c4 ("x86: mm: add x86_64 support for page table
check")

Signed-off-by: Rohan McLure <rmcl...@linux.ibm.com>
---
V2: Update spacing and types assigned to pte_update calls.
V3: Update one last pte_update call to remove __pte invocation.
V5: Fix 32-bit nohash double set
V6: Omit __set_pte_at instrumentation - should be instrumented by
set_pte_at, with set_pte in between, performing all prior checks.
Instrument pmds. Use set_pte where needed.
V7: Make set_pte_at an inline function. Fix commit message.
Detail changes of internal references to set_pte_at, and its semantics.
---
 arch/powerpc/Kconfig                         |  1 +
 arch/powerpc/include/asm/book3s/32/pgtable.h |  8 +++-
 arch/powerpc/include/asm/book3s/64/pgtable.h | 44 ++++++++++++++++----
 arch/powerpc/include/asm/nohash/32/pgtable.h |  7 +++-
 arch/powerpc/include/asm/nohash/64/pgtable.h |  8 +++-
 arch/powerpc/include/asm/pgtable.h           | 10 ++++-
 arch/powerpc/mm/book3s64/hash_pgtable.c      |  2 +-
 arch/powerpc/mm/book3s64/pgtable.c           | 16 ++++---
 arch/powerpc/mm/book3s64/radix_pgtable.c     | 10 ++---
 arch/powerpc/mm/nohash/book3e_pgtable.c      |  2 +-
 arch/powerpc/mm/pgtable_32.c                 |  2 +-
 11 files changed, 83 insertions(+), 27 deletions(-)

diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 2c9cdf1d8761..2474e2699037 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -154,6 +154,7 @@ config PPC
        select ARCH_STACKWALK
        select ARCH_SUPPORTS_ATOMIC_RMW
        select ARCH_SUPPORTS_DEBUG_PAGEALLOC    if PPC_BOOK3S || PPC_8xx || 40x
+       select ARCH_SUPPORTS_PAGE_TABLE_CHECK
        select ARCH_USE_BUILTIN_BSWAP
        select ARCH_USE_CMPXCHG_LOCKREF         if PPC64
        select ARCH_USE_MEMTEST
diff --git a/arch/powerpc/include/asm/book3s/32/pgtable.h 
b/arch/powerpc/include/asm/book3s/32/pgtable.h
index afd672e84791..8850b4fb22a4 100644
--- a/arch/powerpc/include/asm/book3s/32/pgtable.h
+++ b/arch/powerpc/include/asm/book3s/32/pgtable.h
@@ -53,6 +53,8 @@
 
 #ifndef __ASSEMBLY__
 
+#include <linux/page_table_check.h>
+
 static inline bool pte_user(pte_t pte)
 {
        return pte_val(pte) & _PAGE_USER;
@@ -338,7 +340,11 @@ static inline int __ptep_test_and_clear_young(struct 
mm_struct *mm,
 static inline pte_t ptep_get_and_clear(struct mm_struct *mm, unsigned long 
addr,
                                       pte_t *ptep)
 {
-       return __pte(pte_update(mm, addr, ptep, ~_PAGE_HASHPTE, 0, 0));
+       pte_t old_pte = __pte(pte_update(mm, addr, ptep, ~_PAGE_HASHPTE, 0, 0));
+
+       page_table_check_pte_clear(mm, addr, old_pte);
+
+       return old_pte;
 }
 
 #define __HAVE_ARCH_PTEP_SET_WRPROTECT
diff --git a/arch/powerpc/include/asm/book3s/64/pgtable.h 
b/arch/powerpc/include/asm/book3s/64/pgtable.h
index a6ed93d01da1..0c6838875720 100644
--- a/arch/powerpc/include/asm/book3s/64/pgtable.h
+++ b/arch/powerpc/include/asm/book3s/64/pgtable.h
@@ -162,6 +162,8 @@
 #define PAGE_KERNEL_ROX        __pgprot(_PAGE_BASE | _PAGE_KERNEL_ROX)
 
 #ifndef __ASSEMBLY__
+#include <linux/page_table_check.h>
+
 /*
  * page table defines
  */
@@ -431,8 +433,11 @@ static inline void huge_ptep_set_wrprotect(struct 
mm_struct *mm,
 static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
                                       unsigned long addr, pte_t *ptep)
 {
-       unsigned long old = pte_update(mm, addr, ptep, ~0UL, 0, 0);
-       return __pte(old);
+       pte_t old_pte = __pte(pte_update(mm, addr, ptep, ~0UL, 0, 0));
+
+       page_table_check_pte_clear(mm, addr, old_pte);
+
+       return old_pte;
 }
 
 #define __HAVE_ARCH_PTEP_GET_AND_CLEAR_FULL
@@ -441,11 +446,16 @@ static inline pte_t ptep_get_and_clear_full(struct 
mm_struct *mm,
                                            pte_t *ptep, int full)
 {
        if (full && radix_enabled()) {
+               pte_t old_pte;
+
                /*
                 * We know that this is a full mm pte clear and
                 * hence can be sure there is no parallel set_pte.
                 */
-               return radix__ptep_get_and_clear_full(mm, addr, ptep, full);
+               old_pte = radix__ptep_get_and_clear_full(mm, addr, ptep, full);
+               page_table_check_pte_clear(mm, addr, old_pte);
+
+               return old_pte;
        }
        return ptep_get_and_clear(mm, addr, ptep);
 }
@@ -1249,17 +1259,33 @@ extern int pmdp_test_and_clear_young(struct 
vm_area_struct *vma,
 static inline pmd_t pmdp_huge_get_and_clear(struct mm_struct *mm,
                                            unsigned long addr, pmd_t *pmdp)
 {
-       if (radix_enabled())
-               return radix__pmdp_huge_get_and_clear(mm, addr, pmdp);
-       return hash__pmdp_huge_get_and_clear(mm, addr, pmdp);
+       pmd_t old_pmd;
+
+       if (radix_enabled()) {
+               old_pmd = radix__pmdp_huge_get_and_clear(mm, addr, pmdp);
+       } else {
+               old_pmd = hash__pmdp_huge_get_and_clear(mm, addr, pmdp);
+       }
+
+       page_table_check_pmd_clear(mm, addr, old_pmd);
+
+       return old_pmd;
 }
 
 static inline pmd_t __pmdp_collapse_flush(struct vm_area_struct *vma, struct 
mm_struct *mm,
                                          unsigned long address, pmd_t *pmdp)
 {
-       if (radix_enabled())
-               return radix__pmdp_collapse_flush(vma, address, pmdp);
-       return hash__pmdp_collapse_flush(vma, address, pmdp);
+       pmd_t old_pmd;
+
+       if (radix_enabled()) {
+               old_pmd = radix__pmdp_collapse_flush(vma, address, pmdp);
+       } else {
+               old_pmd = hash__pmdp_collapse_flush(vma, address, pmdp);
+       }
+
+       page_table_check_pmd_clear(mm, address, old_pmd);
+
+       return old_pmd;
 }
 #define pmdp_collapse_flush(__vma, __addr, __pmdp)                     \
 ({                                                                     \
diff --git a/arch/powerpc/include/asm/nohash/32/pgtable.h 
b/arch/powerpc/include/asm/nohash/32/pgtable.h
index d953533c56ff..e9c77054fe0b 100644
--- a/arch/powerpc/include/asm/nohash/32/pgtable.h
+++ b/arch/powerpc/include/asm/nohash/32/pgtable.h
@@ -166,6 +166,7 @@ void unmap_kernel_page(unsigned long va);
 #define _PAGE_CHG_MASK (PTE_RPN_MASK | _PAGE_DIRTY | _PAGE_ACCESSED | 
_PAGE_SPECIAL)
 
 #ifndef __ASSEMBLY__
+#include <linux/page_table_check.h>
 
 #define pte_clear(mm, addr, ptep) \
        do { pte_update(mm, addr, ptep, ~0, 0, 0); } while (0)
@@ -316,7 +317,11 @@ static inline int __ptep_test_and_clear_young(struct 
mm_struct *mm,
 static inline pte_t ptep_get_and_clear(struct mm_struct *mm, unsigned long 
addr,
                                       pte_t *ptep)
 {
-       return __pte(pte_update(mm, addr, ptep, ~0, 0, 0));
+       pte_t old_pte = __pte(pte_update(mm, addr, ptep, ~0, 0, 0));
+
+       page_table_check_pte_clear(mm, addr, old_pte);
+
+       return old_pte;
 }
 
 #define __HAVE_ARCH_PTEP_SET_WRPROTECT
diff --git a/arch/powerpc/include/asm/nohash/64/pgtable.h 
b/arch/powerpc/include/asm/nohash/64/pgtable.h
index 14e69ebad31f..d88b22c753d3 100644
--- a/arch/powerpc/include/asm/nohash/64/pgtable.h
+++ b/arch/powerpc/include/asm/nohash/64/pgtable.h
@@ -83,6 +83,7 @@
 #define H_PAGE_4K_PFN 0
 
 #ifndef __ASSEMBLY__
+#include <linux/page_table_check.h>
 /* pte_clear moved to later in this file */
 
 static inline pte_t pte_mkwrite(pte_t pte)
@@ -259,8 +260,11 @@ static inline void huge_ptep_set_wrprotect(struct 
mm_struct *mm,
 static inline pte_t ptep_get_and_clear(struct mm_struct *mm,
                                       unsigned long addr, pte_t *ptep)
 {
-       unsigned long old = pte_update(mm, addr, ptep, ~0UL, 0, 0);
-       return __pte(old);
+       pte_t old_pte = __pte(pte_update(mm, addr, ptep, ~0UL, 0, 0));
+
+       page_table_check_pte_clear(mm, addr, old_pte);
+
+       return old_pte;
 }
 
 static inline void pte_clear(struct mm_struct *mm, unsigned long addr,
diff --git a/arch/powerpc/include/asm/pgtable.h 
b/arch/powerpc/include/asm/pgtable.h
index b76fdb80b6c9..5715350e603a 100644
--- a/arch/powerpc/include/asm/pgtable.h
+++ b/arch/powerpc/include/asm/pgtable.h
@@ -5,6 +5,7 @@
 #ifndef __ASSEMBLY__
 #include <linux/mmdebug.h>
 #include <linux/mmzone.h>
+#include <linux/page_table_check.h>
 #include <asm/processor.h>             /* For TASK_SIZE */
 #include <asm/mmu.h>
 #include <asm/page.h>
@@ -48,7 +49,7 @@ struct mm_struct;
 /* Keep these as a macros to avoid include dependency mess */
 #define pte_page(x)            pfn_to_page(pte_pfn(x))
 #define mk_pte(page, pgprot)   pfn_pte(page_to_pfn(page), (pgprot))
-#define set_pte_at             set_pte
+
 /*
  * Select all bits except the pfn
  */
@@ -153,6 +154,13 @@ struct seq_file;
 void arch_report_meminfo(struct seq_file *m);
 #endif /* CONFIG_PPC64 */
 
+static inline void set_pte_at(struct mm_struct *mm, unsigned long addr,
+                             pte_t *ptep, pte_t pte)
+{
+       page_table_check_pte_set(mm, addr, ptep, pte);
+       set_pte(mm, addr, ptep, pte);
+}
+
 /*
  * Currently only consumed by page_table_check_pud_{set,clear}. Since clears
  * and sets to page table entries at any level are done through
diff --git a/arch/powerpc/mm/book3s64/hash_pgtable.c 
b/arch/powerpc/mm/book3s64/hash_pgtable.c
index 51f48984abca..a92a8a7c9199 100644
--- a/arch/powerpc/mm/book3s64/hash_pgtable.c
+++ b/arch/powerpc/mm/book3s64/hash_pgtable.c
@@ -165,7 +165,7 @@ int hash__map_kernel_page(unsigned long ea, unsigned long 
pa, pgprot_t prot)
                ptep = pte_alloc_kernel(pmdp, ea);
                if (!ptep)
                        return -ENOMEM;
-               set_pte_at(&init_mm, ea, ptep, pfn_pte(pa >> PAGE_SHIFT, prot));
+               set_pte(&init_mm, ea, ptep, pfn_pte(pa >> PAGE_SHIFT, prot));
        } else {
                /*
                 * If the mm subsystem is not fully up, we cannot create a
diff --git a/arch/powerpc/mm/book3s64/pgtable.c 
b/arch/powerpc/mm/book3s64/pgtable.c
index 85c84e89e3ea..d95be1d08b79 100644
--- a/arch/powerpc/mm/book3s64/pgtable.c
+++ b/arch/powerpc/mm/book3s64/pgtable.c
@@ -9,6 +9,7 @@
 #include <linux/memremap.h>
 #include <linux/pkeys.h>
 #include <linux/debugfs.h>
+#include <linux/page_table_check.h>
 #include <misc/cxl-base.h>
 
 #include <asm/pgalloc.h>
@@ -87,7 +88,10 @@ void set_pmd_at(struct mm_struct *mm, unsigned long addr,
        WARN_ON(!(pmd_large(pmd)));
 #endif
        trace_hugepage_set_pmd(addr, pmd_val(pmd));
-       return set_pte_at(mm, addr, pmdp_ptep(pmdp), pmd_pte(pmd));
+
+       page_table_check_pmd_set(mm, addr, pmdp, pmd);
+
+       return set_pte(mm, addr, pmdp_ptep(pmdp), pmd_pte(pmd));
 }
 
 static void do_serialize(void *arg)
@@ -122,11 +126,13 @@ void serialize_against_pte_lookup(struct mm_struct *mm)
 pmd_t pmdp_invalidate(struct vm_area_struct *vma, unsigned long address,
                     pmd_t *pmdp)
 {
-       unsigned long old_pmd;
+       pmd_t old_pmd;
 
-       old_pmd = pmd_hugepage_update(vma->vm_mm, address, pmdp, _PAGE_PRESENT, 
_PAGE_INVALID);
+       old_pmd = __pmd(pmd_hugepage_update(vma->vm_mm, address, pmdp, 
_PAGE_PRESENT, _PAGE_INVALID));
        flush_pmd_tlb_range(vma, address, address + HPAGE_PMD_SIZE);
-       return __pmd(old_pmd);
+       page_table_check_pmd_clear(vma->vm_mm, address, old_pmd);
+
+       return old_pmd;
 }
 
 pmd_t pmdp_huge_get_and_clear_full(struct vm_area_struct *vma,
@@ -460,7 +466,7 @@ void ptep_modify_prot_commit(struct vm_area_struct *vma, 
unsigned long addr,
        if (radix_enabled())
                return radix__ptep_modify_prot_commit(vma, addr,
                                                      ptep, old_pte, pte);
-       set_pte_at(vma->vm_mm, addr, ptep, pte);
+       set_pte(vma->vm_mm, addr, ptep, pte);
 }
 
 /*
diff --git a/arch/powerpc/mm/book3s64/radix_pgtable.c 
b/arch/powerpc/mm/book3s64/radix_pgtable.c
index 4e46e001c3c3..9359e3589107 100644
--- a/arch/powerpc/mm/book3s64/radix_pgtable.c
+++ b/arch/powerpc/mm/book3s64/radix_pgtable.c
@@ -110,7 +110,7 @@ static int early_map_kernel_page(unsigned long ea, unsigned 
long pa,
        ptep = pte_offset_kernel(pmdp, ea);
 
 set_the_pte:
-       set_pte_at(&init_mm, ea, ptep, pfn_pte(pfn, flags));
+       set_pte(&init_mm, ea, ptep, pfn_pte(pfn, flags));
        asm volatile("ptesync": : :"memory");
        return 0;
 }
@@ -170,7 +170,7 @@ static int __map_kernel_page(unsigned long ea, unsigned 
long pa,
                return -ENOMEM;
 
 set_the_pte:
-       set_pte_at(&init_mm, ea, ptep, pfn_pte(pfn, flags));
+       set_pte(&init_mm, ea, ptep, pfn_pte(pfn, flags));
        asm volatile("ptesync": : :"memory");
        return 0;
 }
@@ -1094,7 +1094,7 @@ void radix__ptep_modify_prot_commit(struct vm_area_struct 
*vma,
            (atomic_read(&mm->context.copros) > 0))
                radix__flush_tlb_page(vma, addr);
 
-       set_pte_at(mm, addr, ptep, pte);
+       set_pte(mm, addr, ptep, pte);
 }
 
 int pud_set_huge(pud_t *pud, phys_addr_t addr, pgprot_t prot)
@@ -1105,7 +1105,7 @@ int pud_set_huge(pud_t *pud, phys_addr_t addr, pgprot_t 
prot)
        if (!radix_enabled())
                return 0;
 
-       set_pte_at(&init_mm, 0 /* radix unused */, ptep, new_pud);
+       set_pte(&init_mm, 0 /* radix unused */, ptep, new_pud);
 
        return 1;
 }
@@ -1152,7 +1152,7 @@ int pmd_set_huge(pmd_t *pmd, phys_addr_t addr, pgprot_t 
prot)
        if (!radix_enabled())
                return 0;
 
-       set_pte_at(&init_mm, 0 /* radix unused */, ptep, new_pmd);
+       set_pte(&init_mm, 0 /* radix unused */, ptep, new_pmd);
 
        return 1;
 }
diff --git a/arch/powerpc/mm/nohash/book3e_pgtable.c 
b/arch/powerpc/mm/nohash/book3e_pgtable.c
index b80fc4a91a53..e50d22c6f983 100644
--- a/arch/powerpc/mm/nohash/book3e_pgtable.c
+++ b/arch/powerpc/mm/nohash/book3e_pgtable.c
@@ -111,7 +111,7 @@ int __ref map_kernel_page(unsigned long ea, unsigned long 
pa, pgprot_t prot)
                }
                ptep = pte_offset_kernel(pmdp, ea);
        }
-       set_pte_at(&init_mm, ea, ptep, pfn_pte(pa >> PAGE_SHIFT, prot));
+       set_pte(&init_mm, ea, ptep, pfn_pte(pa >> PAGE_SHIFT, prot));
 
        smp_wmb();
        return 0;
diff --git a/arch/powerpc/mm/pgtable_32.c b/arch/powerpc/mm/pgtable_32.c
index 5c02fd08d61e..a86a16be24ea 100644
--- a/arch/powerpc/mm/pgtable_32.c
+++ b/arch/powerpc/mm/pgtable_32.c
@@ -89,7 +89,7 @@ int __ref map_kernel_page(unsigned long va, phys_addr_t pa, 
pgprot_t prot)
                 * hash table
                 */
                BUG_ON((pte_present(*pg) | pte_hashpte(*pg)) && 
pgprot_val(prot));
-               set_pte_at(&init_mm, va, pg, pfn_pte(pa >> PAGE_SHIFT, prot));
+               set_pte(&init_mm, va, pg, pfn_pte(pa >> PAGE_SHIFT, prot));
        }
        smp_wmb();
        return err;
-- 
2.37.2

Reply via email to