]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
s390: Add infrastructure to patch lowcore accesses
authorSven Schnelle <svens@linux.ibm.com>
Mon, 22 Jul 2024 13:41:14 +0000 (15:41 +0200)
committerVasily Gorbik <gor@linux.ibm.com>
Tue, 23 Jul 2024 14:02:32 +0000 (16:02 +0200)
The s390 architecture defines two special per-CPU data pages
called the "prefix area". In s390-linux terminology this is usually
called "lowcore". This memory area contains system configuration
data like old/new PSW's for system call/interrupt/machine check
handlers and lots of other data. It is normally mapped to logical
address 0. This area can only be accessed when in supervisor mode.

This means that kernel code can dereference NULL pointers, because
accesses to address 0 are allowed. Parts of lowcore can be write
protected, but read accesses and write accesses outside of the write
protected areas are not caught.

To remove this limitation for debugging and testing, remap lowcore to
another address and define a function get_lowcore() which simply
returns the address where lowcore is mapped at. This would normally
introduce a pointer dereference (=memory read). As lowcore is used
for several very often used variables, add code to patch this function
during runtime, so we avoid the memory reads.

For C code get_lowcore() has to be used, for assembly code it is
the GET_LC macro. When using this macro/function a reference is added
to alternative patching. All these locations will be patched to the
actual lowcore location when the kernel is booted or a module is loaded.

To make debugging/bisecting problems easier, this patch adds all the
infrastructure but the lowcore address is still hardwired to 0. This
way the code can be converted on a per function basis, and the
functionality is enabled in a patch after all the functions have
been converted.

Note that this requires at least z16 because the old lpsw instruction
only allowed a 12 bit displacement. z16 introduced lpswey which allows
20 bits (signed), so the lowcore can effectively be mapped from
address 0 - 0x7e000. To use 0x7e000 as address, a 6 byte lgfi
instruction would have to be used in the alternative. To save two
bytes, llilh can be used, but this only allows to set bits 16-31 of
the address. In order to use the llilh instruction, use 0x70000 as
alternative lowcore address. This is still large enough to catch
NULL pointer dereferences into large arrays.

Reviewed-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Sven Schnelle <svens@linux.ibm.com>
Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
12 files changed:
arch/s390/boot/boot.h
arch/s390/boot/ipl_parm.c
arch/s390/boot/startup.c
arch/s390/boot/vmem.c
arch/s390/include/asm/abs_lowcore.h
arch/s390/include/asm/alternative.h
arch/s390/include/asm/lowcore.h
arch/s390/kernel/abs_lowcore.c
arch/s390/kernel/alternative.c
arch/s390/kernel/alternative.h [new file with mode: 0644]
arch/s390/kernel/early.c
arch/s390/kernel/setup.c

index ed2f0ec24f0da45b3ac1f539e5148ec6f3d898e3..83e2ce050b6cef93df150e098a24607211268307 100644 (file)
@@ -91,8 +91,10 @@ extern char _end[], _decompressor_end[];
 extern unsigned char _compressed_start[];
 extern unsigned char _compressed_end[];
 extern struct vmlinux_info _vmlinux_info;
+
 #define vmlinux _vmlinux_info
 
+#define __lowcore_pa(x)                ((unsigned long)(x) % sizeof(struct lowcore))
 #define __abs_lowcore_pa(x)    (((unsigned long)(x) - __abs_lowcore) % sizeof(struct lowcore))
 #define __kernel_va(x)         ((void *)((unsigned long)(x) - __kaslr_offset_phys + __kaslr_offset))
 #define __kernel_pa(x)         ((unsigned long)(x) - __kaslr_offset + __kaslr_offset_phys)
index a21f301acd2962b23677cce09a269746637e50cb..337c14931ccbf5c038257dde6e1869aaffed6b6d 100644 (file)
@@ -3,6 +3,7 @@
 #include <linux/init.h>
 #include <linux/ctype.h>
 #include <linux/pgtable.h>
+#include <asm/abs_lowcore.h>
 #include <asm/page-states.h>
 #include <asm/ebcdic.h>
 #include <asm/sclp.h>
index cca2f1bad33c9d932115e1bef00cb2e28856336f..ce232552bc1c38684ad8036203041ec9874eb8af 100644 (file)
@@ -30,6 +30,7 @@ unsigned long __bootdata_preserved(vmemmap_size);
 unsigned long __bootdata_preserved(MODULES_VADDR);
 unsigned long __bootdata_preserved(MODULES_END);
 unsigned long __bootdata_preserved(max_mappable);
+int __bootdata_preserved(relocate_lowcore);
 
 u64 __bootdata_preserved(stfle_fac_list[16]);
 struct oldmem_data __bootdata_preserved(oldmem_data);
index a255ca189aaad959aa5afe5b11bbcf72183671de..2847cc059ab7aca7a7d89a2e023d300a85b718ea 100644 (file)
@@ -26,6 +26,7 @@ atomic_long_t __bootdata_preserved(direct_pages_count[PG_DIRECT_MAP_MAX]);
 enum populate_mode {
        POPULATE_NONE,
        POPULATE_DIRECT,
+       POPULATE_LOWCORE,
        POPULATE_ABS_LOWCORE,
        POPULATE_IDENTITY,
        POPULATE_KERNEL,
@@ -242,6 +243,8 @@ static unsigned long _pa(unsigned long addr, unsigned long size, enum populate_m
                return -1;
        case POPULATE_DIRECT:
                return addr;
+       case POPULATE_LOWCORE:
+               return __lowcore_pa(addr);
        case POPULATE_ABS_LOWCORE:
                return __abs_lowcore_pa(addr);
        case POPULATE_KERNEL:
@@ -418,6 +421,7 @@ static void pgtable_populate(unsigned long addr, unsigned long end, enum populat
 
 void setup_vmem(unsigned long kernel_start, unsigned long kernel_end, unsigned long asce_limit)
 {
+       unsigned long lowcore_address = 0;
        unsigned long start, end;
        unsigned long asce_type;
        unsigned long asce_bits;
@@ -455,12 +459,17 @@ void setup_vmem(unsigned long kernel_start, unsigned long kernel_end, unsigned l
        __arch_set_page_dat((void *)swapper_pg_dir, 1UL << CRST_ALLOC_ORDER);
        __arch_set_page_dat((void *)invalid_pg_dir, 1UL << CRST_ALLOC_ORDER);
 
+       if (relocate_lowcore)
+               lowcore_address = LOWCORE_ALT_ADDRESS;
+
        /*
         * To allow prefixing the lowcore must be mapped with 4KB pages.
         * To prevent creation of a large page at address 0 first map
         * the lowcore and create the identity mapping only afterwards.
         */
-       pgtable_populate(0, sizeof(struct lowcore), POPULATE_DIRECT);
+       pgtable_populate(lowcore_address,
+                        lowcore_address + sizeof(struct lowcore),
+                        POPULATE_LOWCORE);
        for_each_physmem_usable_range(i, &start, &end) {
                pgtable_populate((unsigned long)__identity_va(start),
                                 (unsigned long)__identity_va(end),
index 6f264b79e37794736ab5eb10c5971fe5fa1c5d7b..d20df8c923fc70fa2a59265ae28474503ea85b2c 100644 (file)
@@ -2,6 +2,7 @@
 #ifndef _ASM_S390_ABS_LOWCORE_H
 #define _ASM_S390_ABS_LOWCORE_H
 
+#include <asm/sections.h>
 #include <asm/lowcore.h>
 
 #define ABS_LOWCORE_MAP_SIZE   (NR_CPUS * sizeof(struct lowcore))
@@ -24,4 +25,11 @@ static inline void put_abs_lowcore(struct lowcore *lc)
        put_cpu();
 }
 
+extern int __bootdata_preserved(relocate_lowcore);
+
+static inline int have_relocated_lowcore(void)
+{
+       return relocate_lowcore;
+}
+
 #endif /* _ASM_S390_ABS_LOWCORE_H */
index 3ddd6dbe5635dfcb02038d8d1077376800299531..de980c938a3e148f15044fff9cd4e943ccad1bb1 100644 (file)
@@ -33,6 +33,7 @@
 
 #define ALT_TYPE_FACILITY      0
 #define ALT_TYPE_SPEC          1
+#define ALT_TYPE_LOWCORE       2
 
 #define ALT_DATA_SHIFT         0
 #define ALT_TYPE_SHIFT         20
@@ -50,6 +51,9 @@
                                         ALT_TYPE_SPEC << ALT_TYPE_SHIFT        | \
                                         (facility) << ALT_DATA_SHIFT)
 
+#define ALT_LOWCORE                    (ALT_CTX_EARLY << ALT_CTX_SHIFT         | \
+                                        ALT_TYPE_LOWCORE << ALT_TYPE_SHIFT)
+
 #ifndef __ASSEMBLY__
 
 #include <linux/types.h>
index bce3a69ab2a3083b1169efb214a06f87410dfd09..52c90b65a2b84f14561ae04dba1c123afcfa6901 100644 (file)
 #include <asm/ctlreg.h>
 #include <asm/cpu.h>
 #include <asm/types.h>
+#include <asm/alternative.h>
 
 #define LC_ORDER 1
 #define LC_PAGES 2
 
+#define LOWCORE_ALT_ADDRESS    _AC(0x70000, UL)
+
+#ifndef __ASSEMBLY__
+
 struct pgm_tdb {
        u64 data[32];
 };
@@ -214,7 +219,14 @@ struct lowcore {
 
 static __always_inline struct lowcore *get_lowcore(void)
 {
-       return NULL;
+       struct lowcore *lc;
+
+       if (__is_defined(__DECOMPRESSOR))
+               return NULL;
+       asm(ALTERNATIVE("llilh %[lc],0", "llilh %[lc],%[alt]", ALT_LOWCORE)
+           : [lc] "=d" (lc)
+           : [alt] "i" (LOWCORE_ALT_ADDRESS >> 16));
+       return lc;
 }
 
 extern struct lowcore *lowcore_ptr[];
@@ -224,4 +236,13 @@ static inline void set_prefix(__u32 address)
        asm volatile("spx %0" : : "Q" (address) : "memory");
 }
 
+#else /* __ASSEMBLY__ */
+
+.macro GET_LC reg
+       ALTERNATIVE "llilh      \reg,0",                                        \
+               __stringify(llilh       \reg, LOWCORE_ALT_ADDRESS >> 16),       \
+               ALT_LOWCORE
+.endm
+
+#endif /* __ASSEMBLY__ */
 #endif /* _ASM_S390_LOWCORE_H */
index f9efc54ec4b7cdae459cc5a29437e70caf2e8680..09cd24cbe74e4c7e09f567fc31606ba3f13fd920 100644 (file)
@@ -4,6 +4,7 @@
 #include <asm/abs_lowcore.h>
 
 unsigned long __bootdata_preserved(__abs_lowcore);
+int __bootdata_preserved(relocate_lowcore);
 
 int abs_lowcore_map(int cpu, struct lowcore *lc, bool alloc)
 {
index eae254466192298ccc74484c0e3984cf26c50990..8d5d0de35de05d8ef02e4a144144ff8ae2fea859 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <linux/uaccess.h>
 #include <asm/nospec-branch.h>
+#include <asm/abs_lowcore.h>
 #include <asm/alternative.h>
 #include <asm/facility.h>
 
@@ -25,6 +26,9 @@ void __apply_alternatives(struct alt_instr *start, struct alt_instr *end, unsign
                case ALT_TYPE_SPEC:
                        replace = nobp_enabled();
                        break;
+               case ALT_TYPE_LOWCORE:
+                       replace = have_relocated_lowcore();
+                       break;
                default:
                        replace = false;
                }
diff --git a/arch/s390/kernel/alternative.h b/arch/s390/kernel/alternative.h
new file mode 100644 (file)
index 0000000..e69de29
index 3ce77cee272dd0575530de86f67ecf296f118375..14d324865e33fc99790597fc28d11c91394eefe7 100644 (file)
@@ -48,6 +48,7 @@ decompressor_handled_param(dfltcc);
 decompressor_handled_param(facilities);
 decompressor_handled_param(nokaslr);
 decompressor_handled_param(cmma);
+decompressor_handled_param(relocate_lowcore);
 #if IS_ENABLED(CONFIG_KVM)
 decompressor_handled_param(prot_virt);
 #endif
index 700003e1bc76eb7ee66cbe6e7ff869364f983de9..4ec99f73fa27e19b2cc612a92ac3507206bc23ac 100644 (file)
@@ -889,6 +889,9 @@ void __init setup_arch(char **cmdline_p)
        else
                pr_info("Linux is running as a guest in 64-bit mode\n");
 
+       if (have_relocated_lowcore())
+               pr_info("Lowcore relocated to 0x%px\n", get_lowcore());
+
        log_component_list();
 
        /* Have one command line that is parsed and saved in /proc/cmdline */