diff options
-rw-r--r-- | arch/s390/boot/ipl_parm.c | 11 | ||||
-rw-r--r-- | arch/s390/boot/string.c | 38 | ||||
-rw-r--r-- | arch/s390/include/asm/pgtable.h | 6 | ||||
-rw-r--r-- | arch/s390/include/asm/setup.h | 1 | ||||
-rw-r--r-- | arch/s390/kernel/setup.c | 1 | ||||
-rw-r--r-- | arch/s390/mm/kasan_init.c | 66 |
6 files changed, 118 insertions, 5 deletions
diff --git a/arch/s390/boot/ipl_parm.c b/arch/s390/boot/ipl_parm.c index 7f8e546400a1..9dab596be98e 100644 --- a/arch/s390/boot/ipl_parm.c +++ b/arch/s390/boot/ipl_parm.c @@ -13,6 +13,7 @@ int __bootdata(early_ipl_block_valid); unsigned long __bootdata(memory_end); int __bootdata(memory_end_set); +int __bootdata(noexec_disabled); static inline int __diag308(unsigned long subcode, void *addr) { @@ -145,8 +146,10 @@ void setup_boot_command_line(void) static char command_line_buf[COMMAND_LINE_SIZE] __section(.data); static void parse_mem_opt(void) { - char *args; char *param, *val; + bool enabled; + char *args; + int rc; args = strcpy(command_line_buf, early_command_line); while (*args) { @@ -156,6 +159,12 @@ static void parse_mem_opt(void) memory_end = memparse(val, NULL); memory_end_set = 1; } + + if (!strcmp(param, "noexec")) { + rc = kstrtobool(val, &enabled); + if (!rc && !enabled) + noexec_disabled = 1; + } } } diff --git a/arch/s390/boot/string.c b/arch/s390/boot/string.c index 09ca9130e73a..25aca07898ba 100644 --- a/arch/s390/boot/string.c +++ b/arch/s390/boot/string.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/ctype.h> #include <linux/kernel.h> +#include <linux/errno.h> #include "../lib/string.c" int strncmp(const char *cs, const char *ct, size_t count) @@ -98,3 +99,40 @@ long simple_strtol(const char *cp, char **endp, unsigned int base) return simple_strtoull(cp, endp, base); } + +int kstrtobool(const char *s, bool *res) +{ + if (!s) + return -EINVAL; + + switch (s[0]) { + case 'y': + case 'Y': + case '1': + *res = true; + return 0; + case 'n': + case 'N': + case '0': + *res = false; + return 0; + case 'o': + case 'O': + switch (s[1]) { + case 'n': + case 'N': + *res = true; + return 0; + case 'f': + case 'F': + *res = false; + return 0; + default: + break; + } + default: + break; + } + + return -EINVAL; +} diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h index 3baf8f1fb8f2..411d435e7a7d 100644 --- a/arch/s390/include/asm/pgtable.h +++ b/arch/s390/include/asm/pgtable.h @@ -468,6 +468,12 @@ static inline int is_module_addr(void *addr) _SEGMENT_ENTRY_YOUNG | \ _SEGMENT_ENTRY_PROTECT | \ _SEGMENT_ENTRY_NOEXEC) +#define SEGMENT_KERNEL_EXEC __pgprot(_SEGMENT_ENTRY | \ + _SEGMENT_ENTRY_LARGE | \ + _SEGMENT_ENTRY_READ | \ + _SEGMENT_ENTRY_WRITE | \ + _SEGMENT_ENTRY_YOUNG | \ + _SEGMENT_ENTRY_DIRTY) /* * Region3 entry (large page) protection definitions. diff --git a/arch/s390/include/asm/setup.h b/arch/s390/include/asm/setup.h index 522e4553373a..efda97804aa4 100644 --- a/arch/s390/include/asm/setup.h +++ b/arch/s390/include/asm/setup.h @@ -65,6 +65,7 @@ #define OLDMEM_SIZE (*(unsigned long *) (OLDMEM_SIZE_OFFSET)) #define COMMAND_LINE ((char *) (COMMAND_LINE_OFFSET)) +extern int noexec_disabled; extern int memory_end_set; extern unsigned long memory_end; extern unsigned long max_physmem_end; diff --git a/arch/s390/kernel/setup.c b/arch/s390/kernel/setup.c index 93cf9bce98a1..d7548806d887 100644 --- a/arch/s390/kernel/setup.c +++ b/arch/s390/kernel/setup.c @@ -90,6 +90,7 @@ char elf_platform[ELF_PLATFORM_SIZE]; unsigned long int_hwcap = 0; +int __bootdata(noexec_disabled); int __bootdata(memory_end_set); unsigned long __bootdata(memory_end); unsigned long __bootdata(max_physmem_end); diff --git a/arch/s390/mm/kasan_init.c b/arch/s390/mm/kasan_init.c index 714ac41e3ee5..e4697900e884 100644 --- a/arch/s390/mm/kasan_init.c +++ b/arch/s390/mm/kasan_init.c @@ -7,11 +7,16 @@ #include <asm/kasan.h> #include <asm/processor.h> #include <asm/sclp.h> +#include <asm/facility.h> #include <asm/sections.h> #include <asm/setup.h> +static unsigned long segment_pos __initdata; +static unsigned long segment_low __initdata; static unsigned long pgalloc_pos __initdata; static unsigned long pgalloc_low __initdata; +static bool has_edat __initdata; +static bool has_nx __initdata; #define __sha(x) ((unsigned long)kasan_mem_to_shadow((void *)x)) @@ -24,6 +29,16 @@ static void __init kasan_early_panic(const char *reason) disabled_wait(0); } +static void * __init kasan_early_alloc_segment(void) +{ + segment_pos -= _SEGMENT_SIZE; + + if (segment_pos < segment_low) + kasan_early_panic("out of memory during initialisation\n"); + + return (void *)segment_pos; +} + static void * __init kasan_early_alloc_pages(unsigned int order) { pgalloc_pos -= (PAGE_SIZE << order); @@ -71,7 +86,7 @@ static void __init kasan_early_vmemmap_populate(unsigned long address, unsigned long end, enum populate_mode mode) { - unsigned long pgt_prot_zero, pgt_prot; + unsigned long pgt_prot_zero, pgt_prot, sgt_prot; pgd_t *pg_dir; p4d_t *p4_dir; pud_t *pu_dir; @@ -79,8 +94,10 @@ static void __init kasan_early_vmemmap_populate(unsigned long address, pte_t *pt_dir; pgt_prot_zero = pgprot_val(PAGE_KERNEL_RO); - pgt_prot_zero &= ~_PAGE_NOEXEC; + if (!has_nx) + pgt_prot_zero &= ~_PAGE_NOEXEC; pgt_prot = pgprot_val(PAGE_KERNEL_EXEC); + sgt_prot = pgprot_val(SEGMENT_KERNEL_EXEC); while (address < end) { pg_dir = pgd_offset_k(address); @@ -131,8 +148,27 @@ static void __init kasan_early_vmemmap_populate(unsigned long address, address = (address + PMD_SIZE) & PMD_MASK; continue; } + /* the first megabyte of 1:1 is mapped with 4k pages */ + if (has_edat && address && end - address >= PMD_SIZE && + mode != POPULATE_ZERO_SHADOW) { + void *page; + + if (mode == POPULATE_ONE2ONE) { + page = (void *)address; + } else { + page = kasan_early_alloc_segment(); + memset(page, 0, _SEGMENT_SIZE); + } + pmd_val(*pm_dir) = __pa(page) | sgt_prot; + address = (address + PMD_SIZE) & PMD_MASK; + continue; + } + pt_dir = kasan_early_pte_alloc(); pmd_populate(&init_mm, pm_dir, pt_dir); + } else if (pmd_large(*pm_dir)) { + address = (address + PMD_SIZE) & PMD_MASK; + continue; } pt_dir = pte_offset_kernel(pm_dir, address); @@ -182,6 +218,20 @@ static void __init kasan_enable_dat(void) __load_psw_mask(psw.mask); } +static void __init kasan_early_detect_facilities(void) +{ + stfle(S390_lowcore.stfle_fac_list, + ARRAY_SIZE(S390_lowcore.stfle_fac_list)); + if (test_facility(8)) { + has_edat = true; + __ctl_set_bit(0, 23); + } + if (!noexec_disabled && test_facility(130)) { + has_nx = true; + __ctl_set_bit(0, 20); + } +} + void __init kasan_early_init(void) { unsigned long untracked_mem_end; @@ -196,7 +246,9 @@ void __init kasan_early_init(void) pud_t pud_z = __pud(__pa(kasan_zero_pmd) | _REGION3_ENTRY); p4d_t p4d_z = __p4d(__pa(kasan_zero_pud) | _REGION2_ENTRY); - pgt_prot &= ~_PAGE_NOEXEC; + kasan_early_detect_facilities(); + if (!has_nx) + pgt_prot &= ~_PAGE_NOEXEC; pte_z = __pte(__pa(kasan_zero_page) | pgt_prot); /* 3 level paging */ @@ -224,7 +276,13 @@ void __init kasan_early_init(void) if (pgalloc_low + shadow_alloc_size > memsize) kasan_early_panic("out of memory during initialisation\n"); - pgalloc_pos = memsize; + if (has_edat) { + segment_pos = round_down(memsize, _SEGMENT_SIZE); + segment_low = segment_pos - shadow_alloc_size; + pgalloc_pos = segment_low; + } else { + pgalloc_pos = memsize; + } init_mm.pgd = early_pg_dir; /* * Current memory layout: |