s390/mm: Simplify noexec page protection handling
authorHeiko Carstens <hca@linux.ibm.com>
Mon, 9 Dec 2024 09:45:18 +0000 (10:45 +0100)
committerAlexander Gordeev <agordeev@linux.ibm.com>
Tue, 17 Dec 2024 11:46:13 +0000 (12:46 +0100)
By default page protection definitions like PAGE_RX have the _PAGE_NOEXEC
bit set. For older machines without the instruction execution protection
facility this bit is not allowed to be used in page table entries, and
therefore must be removed.

This is done at a couple of page table walkers, but also at some but not
all page table modification functions like ptep_modify_prot_commit(). Avoid
all of this and change the page, segment and region3 protection definitions
so that the noexec bit is masked out automatically if the instruction
execution-protection facility is not available. This is similar to what
also various other architectures do which had to solve the same problem.

Reviewed-by: Gerald Schaefer <gerald.schaefer@linux.ibm.com>
Acked-by: Alexander Gordeev <agordeev@linux.ibm.com>
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
arch/s390/boot/boot.h
arch/s390/boot/startup.c
arch/s390/boot/vmem.c
arch/s390/include/asm/pgtable.h
arch/s390/kernel/setup.c
arch/s390/mm/init.c
arch/s390/mm/mmap.c
arch/s390/mm/pageattr.c
arch/s390/mm/pgtable.c
arch/s390/mm/vmem.c

index 7521a9d..56244fe 100644 (file)
@@ -13,7 +13,6 @@
 struct machine_info {
        unsigned char has_edat1 : 1;
        unsigned char has_edat2 : 1;
-       unsigned char has_nx : 1;
 };
 
 struct vmlinux_info {
index abe6e6c..e00aed2 100644 (file)
@@ -30,6 +30,9 @@ unsigned long __bootdata_preserved(vmemmap_size);
 unsigned long __bootdata_preserved(MODULES_VADDR);
 unsigned long __bootdata_preserved(MODULES_END);
 unsigned long __bootdata_preserved(max_mappable);
+unsigned long __bootdata_preserved(page_noexec_mask);
+unsigned long __bootdata_preserved(segment_noexec_mask);
+unsigned long __bootdata_preserved(region_noexec_mask);
 int __bootdata_preserved(relocate_lowcore);
 
 u64 __bootdata_preserved(stfle_fac_list[16]);
@@ -51,8 +54,14 @@ static void detect_facilities(void)
        }
        if (test_facility(78))
                machine.has_edat2 = 1;
-       if (test_facility(130))
-               machine.has_nx = 1;
+       page_noexec_mask = -1UL;
+       segment_noexec_mask = -1UL;
+       region_noexec_mask = -1UL;
+       if (!test_facility(130)) {
+               page_noexec_mask &= ~_PAGE_NOEXEC;
+               segment_noexec_mask &= ~_SEGMENT_ENTRY_NOEXEC;
+               region_noexec_mask &= ~_REGION_ENTRY_NOEXEC;
+       }
 }
 
 static int cmma_test_essa(void)
index 00a3c59..a0c97cd 100644 (file)
@@ -67,8 +67,6 @@ static void kasan_populate_shadow(unsigned long kernel_start, unsigned long kern
        int i;
 
        pte_z = __pte(__pa(kasan_early_shadow_page) | pgprot_val(PAGE_KERNEL_RO));
-       if (!machine.has_nx)
-               pte_z = clear_pte_bit(pte_z, __pgprot(_PAGE_NOEXEC));
        crst_table_init((unsigned long *)kasan_early_shadow_p4d, p4d_val(p4d_z));
        crst_table_init((unsigned long *)kasan_early_shadow_pud, pud_val(pud_z));
        crst_table_init((unsigned long *)kasan_early_shadow_pmd, pmd_val(pmd_z));
@@ -294,8 +292,6 @@ static void pgtable_pte_populate(pmd_t *pmd, unsigned long addr, unsigned long e
                                continue;
                        entry = __pte(_pa(addr, PAGE_SIZE, mode));
                        entry = set_pte_bit(entry, PAGE_KERNEL);
-                       if (!machine.has_nx)
-                               entry = clear_pte_bit(entry, __pgprot(_PAGE_NOEXEC));
                        set_pte(pte, entry);
                        pages++;
                }
@@ -320,8 +316,6 @@ static void pgtable_pmd_populate(pud_t *pud, unsigned long addr, unsigned long e
                        if (can_large_pmd(pmd, addr, next, mode)) {
                                entry = __pmd(_pa(addr, _SEGMENT_SIZE, mode));
                                entry = set_pmd_bit(entry, SEGMENT_KERNEL);
-                               if (!machine.has_nx)
-                                       entry = clear_pmd_bit(entry, __pgprot(_SEGMENT_ENTRY_NOEXEC));
                                set_pmd(pmd, entry);
                                pages++;
                                continue;
@@ -353,8 +347,6 @@ static void pgtable_pud_populate(p4d_t *p4d, unsigned long addr, unsigned long e
                        if (can_large_pud(pud, addr, next, mode)) {
                                entry = __pud(_pa(addr, _REGION3_SIZE, mode));
                                entry = set_pud_bit(entry, REGION3_KERNEL);
-                               if (!machine.has_nx)
-                                       entry = clear_pud_bit(entry, __pgprot(_REGION_ENTRY_NOEXEC));
                                set_pud(pud, entry);
                                pages++;
                                continue;
index 889974f..a3b5105 100644 (file)
@@ -124,6 +124,8 @@ static inline int is_module_addr(void *addr)
 #define KASLR_LEN      0UL
 #endif
 
+void setup_protection_map(void);
+
 /*
  * A 64 bit pagetable entry of S390 has following format:
  * |                    PFRA                         |0IPC|  OS  |
@@ -442,76 +444,107 @@ static inline int is_module_addr(void *addr)
 /*
  * Page protection definitions.
  */
-#define PAGE_NONE      __pgprot(_PAGE_PRESENT | _PAGE_INVALID | _PAGE_PROTECT)
-#define PAGE_RO                __pgprot(_PAGE_PRESENT | _PAGE_READ | \
+#define __PAGE_NONE            (_PAGE_PRESENT | _PAGE_INVALID | _PAGE_PROTECT)
+#define __PAGE_RO              (_PAGE_PRESENT | _PAGE_READ | \
                                 _PAGE_NOEXEC  | _PAGE_INVALID | _PAGE_PROTECT)
-#define PAGE_RX                __pgprot(_PAGE_PRESENT | _PAGE_READ | \
+#define __PAGE_RX              (_PAGE_PRESENT | _PAGE_READ | \
                                 _PAGE_INVALID | _PAGE_PROTECT)
-#define PAGE_RW                __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
+#define __PAGE_RW              (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
                                 _PAGE_NOEXEC  | _PAGE_INVALID | _PAGE_PROTECT)
-#define PAGE_RWX       __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
+#define __PAGE_RWX             (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
                                 _PAGE_INVALID | _PAGE_PROTECT)
-
-#define PAGE_SHARED    __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
+#define __PAGE_SHARED          (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
                                 _PAGE_YOUNG | _PAGE_DIRTY | _PAGE_NOEXEC)
-#define PAGE_KERNEL    __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
+#define __PAGE_KERNEL          (_PAGE_PRESENT | _PAGE_READ | _PAGE_WRITE | \
                                 _PAGE_YOUNG | _PAGE_DIRTY | _PAGE_NOEXEC)
-#define PAGE_KERNEL_RO __pgprot(_PAGE_PRESENT | _PAGE_READ | _PAGE_YOUNG | \
+#define __PAGE_KERNEL_RO       (_PAGE_PRESENT | _PAGE_READ | _PAGE_YOUNG | \
                                 _PAGE_PROTECT | _PAGE_NOEXEC)
 
+extern unsigned long page_noexec_mask;
+
+#define __pgprot_page_mask(x)  __pgprot((x) & page_noexec_mask)
+
+#define PAGE_NONE              __pgprot_page_mask(__PAGE_NONE)
+#define PAGE_RO                        __pgprot_page_mask(__PAGE_RO)
+#define PAGE_RX                        __pgprot_page_mask(__PAGE_RX)
+#define PAGE_RW                        __pgprot_page_mask(__PAGE_RW)
+#define PAGE_RWX               __pgprot_page_mask(__PAGE_RWX)
+#define PAGE_SHARED            __pgprot_page_mask(__PAGE_SHARED)
+#define PAGE_KERNEL            __pgprot_page_mask(__PAGE_KERNEL)
+#define PAGE_KERNEL_RO         __pgprot_page_mask(__PAGE_KERNEL_RO)
+
 /*
  * Segment entry (large page) protection definitions.
  */
-#define SEGMENT_NONE   __pgprot(_SEGMENT_ENTRY_PRESENT | \
+#define __SEGMENT_NONE         (_SEGMENT_ENTRY_PRESENT | \
                                 _SEGMENT_ENTRY_INVALID | \
                                 _SEGMENT_ENTRY_PROTECT)
-#define SEGMENT_RO     __pgprot(_SEGMENT_ENTRY_PRESENT | \
+#define __SEGMENT_RO           (_SEGMENT_ENTRY_PRESENT | \
                                 _SEGMENT_ENTRY_PROTECT | \
                                 _SEGMENT_ENTRY_READ | \
                                 _SEGMENT_ENTRY_NOEXEC)
-#define SEGMENT_RX     __pgprot(_SEGMENT_ENTRY_PRESENT | \
+#define __SEGMENT_RX           (_SEGMENT_ENTRY_PRESENT | \
                                 _SEGMENT_ENTRY_PROTECT | \
                                 _SEGMENT_ENTRY_READ)
-#define SEGMENT_RW     __pgprot(_SEGMENT_ENTRY_PRESENT | \
+#define __SEGMENT_RW           (_SEGMENT_ENTRY_PRESENT | \
                                 _SEGMENT_ENTRY_READ | \
                                 _SEGMENT_ENTRY_WRITE | \
                                 _SEGMENT_ENTRY_NOEXEC)
-#define SEGMENT_RWX    __pgprot(_SEGMENT_ENTRY_PRESENT | \
+#define __SEGMENT_RWX          (_SEGMENT_ENTRY_PRESENT | \
                                 _SEGMENT_ENTRY_READ | \
                                 _SEGMENT_ENTRY_WRITE)
-#define SEGMENT_KERNEL __pgprot(_SEGMENT_ENTRY |       \
+#define __SEGMENT_KERNEL       (_SEGMENT_ENTRY |       \
                                 _SEGMENT_ENTRY_LARGE | \
                                 _SEGMENT_ENTRY_READ |  \
                                 _SEGMENT_ENTRY_WRITE | \
                                 _SEGMENT_ENTRY_YOUNG | \
                                 _SEGMENT_ENTRY_DIRTY | \
                                 _SEGMENT_ENTRY_NOEXEC)
-#define SEGMENT_KERNEL_RO __pgprot(_SEGMENT_ENTRY |    \
+#define __SEGMENT_KERNEL_RO    (_SEGMENT_ENTRY |       \
                                 _SEGMENT_ENTRY_LARGE | \
                                 _SEGMENT_ENTRY_READ |  \
                                 _SEGMENT_ENTRY_YOUNG | \
                                 _SEGMENT_ENTRY_PROTECT | \
                                 _SEGMENT_ENTRY_NOEXEC)
 
+extern unsigned long segment_noexec_mask;
+
+#define __pgprot_segment_mask(x) __pgprot((x) & segment_noexec_mask)
+
+#define SEGMENT_NONE           __pgprot_segment_mask(__SEGMENT_NONE)
+#define SEGMENT_RO             __pgprot_segment_mask(__SEGMENT_RO)
+#define SEGMENT_RX             __pgprot_segment_mask(__SEGMENT_RX)
+#define SEGMENT_RW             __pgprot_segment_mask(__SEGMENT_RW)
+#define SEGMENT_RWX            __pgprot_segment_mask(__SEGMENT_RWX)
+#define SEGMENT_KERNEL         __pgprot_segment_mask(__SEGMENT_KERNEL)
+#define SEGMENT_KERNEL_RO      __pgprot_segment_mask(__SEGMENT_KERNEL_RO)
+
 /*
  * Region3 entry (large page) protection definitions.
  */
 
-#define REGION3_KERNEL __pgprot(_REGION_ENTRY_TYPE_R3 | \
+#define __REGION3_KERNEL       (_REGION_ENTRY_TYPE_R3 | \
                                 _REGION3_ENTRY_PRESENT | \
-                                _REGION3_ENTRY_LARGE |  \
-                                _REGION3_ENTRY_READ |   \
-                                _REGION3_ENTRY_WRITE |  \
-                                _REGION3_ENTRY_YOUNG |  \
+                                _REGION3_ENTRY_LARGE | \
+                                _REGION3_ENTRY_READ | \
+                                _REGION3_ENTRY_WRITE | \
+                                _REGION3_ENTRY_YOUNG | \
                                 _REGION3_ENTRY_DIRTY | \
                                 _REGION_ENTRY_NOEXEC)
-#define REGION3_KERNEL_RO __pgprot(_REGION_ENTRY_TYPE_R3 | \
-                                  _REGION3_ENTRY_PRESENT | \
-                                  _REGION3_ENTRY_LARGE |  \
-                                  _REGION3_ENTRY_READ |   \
-                                  _REGION3_ENTRY_YOUNG |  \
-                                  _REGION_ENTRY_PROTECT | \
-                                  _REGION_ENTRY_NOEXEC)
+#define __REGION3_KERNEL_RO    (_REGION_ENTRY_TYPE_R3 | \
+                                _REGION3_ENTRY_PRESENT | \
+                                _REGION3_ENTRY_LARGE | \
+                                _REGION3_ENTRY_READ | \
+                                _REGION3_ENTRY_YOUNG | \
+                                _REGION_ENTRY_PROTECT | \
+                                _REGION_ENTRY_NOEXEC)
+
+extern unsigned long region_noexec_mask;
+
+#define __pgprot_region_mask(x)        __pgprot((x) & region_noexec_mask)
+
+#define REGION3_KERNEL         __pgprot_region_mask(__REGION3_KERNEL)
+#define REGION3_KERNEL_RO      __pgprot_region_mask(__REGION3_KERNEL_RO)
 
 static inline bool mm_p4d_folded(struct mm_struct *mm)
 {
@@ -1412,8 +1445,6 @@ static inline pte_t mk_pte_phys(unsigned long physpage, pgprot_t pgprot)
        pte_t __pte;
 
        __pte = __pte(physpage | pgprot_val(pgprot));
-       if (!MACHINE_HAS_NX)
-               __pte = clear_pte_bit(__pte, __pgprot(_PAGE_NOEXEC));
        return pte_mkyoung(__pte);
 }
 
@@ -1781,8 +1812,6 @@ static inline int pmdp_clear_flush_young(struct vm_area_struct *vma,
 static inline void set_pmd_at(struct mm_struct *mm, unsigned long addr,
                              pmd_t *pmdp, pmd_t entry)
 {
-       if (!MACHINE_HAS_NX)
-               entry = clear_pmd_bit(entry, __pgprot(_SEGMENT_ENTRY_NOEXEC));
        set_pmd(pmdp, entry);
 }
 
index 95324aa..0ce550f 100644 (file)
@@ -971,6 +971,7 @@ void __init setup_arch(char **cmdline_p)
        if (test_facility(193))
                static_branch_enable(&cpu_has_bear);
 
+       setup_protection_map();
        /*
         * Create kernel page tables.
         */
index 7a96623..f2298f7 100644 (file)
@@ -56,6 +56,15 @@ pgd_t invalid_pg_dir[PTRS_PER_PGD] __section(".bss..invalid_pg_dir");
 
 struct ctlreg __bootdata_preserved(s390_invalid_asce);
 
+unsigned long __bootdata_preserved(page_noexec_mask);
+EXPORT_SYMBOL(page_noexec_mask);
+
+unsigned long __bootdata_preserved(segment_noexec_mask);
+EXPORT_SYMBOL(segment_noexec_mask);
+
+unsigned long __bootdata_preserved(region_noexec_mask);
+EXPORT_SYMBOL(region_noexec_mask);
+
 unsigned long empty_zero_page, zero_page_mask;
 EXPORT_SYMBOL(empty_zero_page);
 EXPORT_SYMBOL(zero_page_mask);
index 33f3504..76f3768 100644 (file)
@@ -196,22 +196,28 @@ void arch_pick_mmap_layout(struct mm_struct *mm, struct rlimit *rlim_stack)
        }
 }
 
-static const pgprot_t protection_map[16] = {
-       [VM_NONE]                                       = PAGE_NONE,
-       [VM_READ]                                       = PAGE_RO,
-       [VM_WRITE]                                      = PAGE_RO,
-       [VM_WRITE | VM_READ]                            = PAGE_RO,
-       [VM_EXEC]                                       = PAGE_RX,
-       [VM_EXEC | VM_READ]                             = PAGE_RX,
-       [VM_EXEC | VM_WRITE]                            = PAGE_RX,
-       [VM_EXEC | VM_WRITE | VM_READ]                  = PAGE_RX,
-       [VM_SHARED]                                     = PAGE_NONE,
-       [VM_SHARED | VM_READ]                           = PAGE_RO,
-       [VM_SHARED | VM_WRITE]                          = PAGE_RW,
-       [VM_SHARED | VM_WRITE | VM_READ]                = PAGE_RW,
-       [VM_SHARED | VM_EXEC]                           = PAGE_RX,
-       [VM_SHARED | VM_EXEC | VM_READ]                 = PAGE_RX,
-       [VM_SHARED | VM_EXEC | VM_WRITE]                = PAGE_RWX,
-       [VM_SHARED | VM_EXEC | VM_WRITE | VM_READ]      = PAGE_RWX
-};
+static pgprot_t protection_map[16] __ro_after_init;
+
+void __init setup_protection_map(void)
+{
+       pgprot_t *pm = protection_map;
+
+       pm[VM_NONE]                                     = PAGE_NONE;
+       pm[VM_READ]                                     = PAGE_RO;
+       pm[VM_WRITE]                                    = PAGE_RO;
+       pm[VM_WRITE | VM_READ]                          = PAGE_RO;
+       pm[VM_EXEC]                                     = PAGE_RX;
+       pm[VM_EXEC | VM_READ]                           = PAGE_RX;
+       pm[VM_EXEC | VM_WRITE]                          = PAGE_RX;
+       pm[VM_EXEC | VM_WRITE | VM_READ]                = PAGE_RX;
+       pm[VM_SHARED]                                   = PAGE_NONE;
+       pm[VM_SHARED | VM_READ]                         = PAGE_RO;
+       pm[VM_SHARED | VM_WRITE]                        = PAGE_RW;
+       pm[VM_SHARED | VM_WRITE | VM_READ]              = PAGE_RW;
+       pm[VM_SHARED | VM_EXEC]                         = PAGE_RX;
+       pm[VM_SHARED | VM_EXEC | VM_READ]               = PAGE_RX;
+       pm[VM_SHARED | VM_EXEC | VM_WRITE]              = PAGE_RWX;
+       pm[VM_SHARED | VM_EXEC | VM_WRITE | VM_READ]    = PAGE_RWX;
+}
+
 DECLARE_VM_GET_PAGE_PROT
index 8f56a21..eae97fb 100644 (file)
@@ -109,8 +109,6 @@ static int walk_pte_level(pmd_t *pmdp, unsigned long addr, unsigned long end,
                } else if (flags & SET_MEMORY_DEF) {
                        new = __pte(pte_val(new) & PAGE_MASK);
                        new = set_pte_bit(new, PAGE_KERNEL);
-                       if (!MACHINE_HAS_NX)
-                               new = clear_pte_bit(new, __pgprot(_PAGE_NOEXEC));
                }
                pgt_set((unsigned long *)ptep, pte_val(new), addr, CRDTE_DTT_PAGE);
                ptep++;
@@ -167,8 +165,6 @@ static void modify_pmd_page(pmd_t *pmdp, unsigned long addr,
        } else if (flags & SET_MEMORY_DEF) {
                new = __pmd(pmd_val(new) & PMD_MASK);
                new = set_pmd_bit(new, SEGMENT_KERNEL);
-               if (!MACHINE_HAS_NX)
-                       new = clear_pmd_bit(new, __pgprot(_SEGMENT_ENTRY_NOEXEC));
        }
        pgt_set((unsigned long *)pmdp, pmd_val(new), addr, CRDTE_DTT_SEGMENT);
 }
@@ -256,8 +252,6 @@ static void modify_pud_page(pud_t *pudp, unsigned long addr,
        } else if (flags & SET_MEMORY_DEF) {
                new = __pud(pud_val(new) & PUD_MASK);
                new = set_pud_bit(new, REGION3_KERNEL);
-               if (!MACHINE_HAS_NX)
-                       new = clear_pud_bit(new, __pgprot(_REGION_ENTRY_NOEXEC));
        }
        pgt_set((unsigned long *)pudp, pud_val(new), addr, CRDTE_DTT_REGION3);
 }
index cea5dba..f05e62e 100644 (file)
@@ -360,8 +360,6 @@ void ptep_modify_prot_commit(struct vm_area_struct *vma, unsigned long addr,
        pgste_t pgste;
        struct mm_struct *mm = vma->vm_mm;
 
-       if (!MACHINE_HAS_NX)
-               pte = clear_pte_bit(pte, __pgprot(_PAGE_NOEXEC));
        if (mm_has_pgste(mm)) {
                pgste = pgste_get(ptep);
                pgste_set_key(ptep, pgste, pte, mm);
index 665b822..7c684c5 100644 (file)
@@ -171,9 +171,6 @@ static int __ref modify_pte_table(pmd_t *pmd, unsigned long addr,
        pte_t *pte;
 
        prot = pgprot_val(PAGE_KERNEL);
-       if (!MACHINE_HAS_NX)
-               prot &= ~_PAGE_NOEXEC;
-
        pte = pte_offset_kernel(pmd, addr);
        for (; addr < end; addr += PAGE_SIZE, pte++) {
                if (!add) {
@@ -230,9 +227,6 @@ static int __ref modify_pmd_table(pud_t *pud, unsigned long addr,
        pte_t *pte;
 
        prot = pgprot_val(SEGMENT_KERNEL);
-       if (!MACHINE_HAS_NX)
-               prot &= ~_SEGMENT_ENTRY_NOEXEC;
-
        pmd = pmd_offset(pud, addr);
        for (; addr < end; addr = next, pmd++) {
                next = pmd_addr_end(addr, end);
@@ -324,8 +318,6 @@ static int modify_pud_table(p4d_t *p4d, unsigned long addr, unsigned long end,
        pmd_t *pmd;
 
        prot = pgprot_val(REGION3_KERNEL);
-       if (!MACHINE_HAS_NX)
-               prot &= ~_REGION_ENTRY_NOEXEC;
        pud = pud_offset(p4d, addr);
        for (; addr < end; addr = next, pud++) {
                next = pud_addr_end(addr, end);