diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 39e937309fc2..2dc722fb31b4 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -583,14 +583,23 @@ config RELOCATABLE help This builds a kernel image that retains relocation information so it can be loaded at an arbitrary address. - The kernel is linked as a position-independent executable (PIE) - and contains dynamic relocations which are processed early in the - bootup process. The relocations make the kernel image about 15% larger (compressed 10%), but are discarded at runtime. Note: this option exists only for documentation purposes, please do not remove it. +config PIE_BUILD + def_bool CC_IS_CLANG && !$(cc-option,-munaligned-symbols) + help + If the compiler is unable to generate code that can manage unaligned + symbols, the kernel is linked as a position-independent executable + (PIE) and includes dynamic relocations that are processed early + during bootup. + + For kpatch functionality, it is recommended to build the kernel + without the PIE_BUILD option. PIE_BUILD is only enabled when the + compiler lacks proper support for handling unaligned symbols. + config RANDOMIZE_BASE bool "Randomize the address of the kernel image (KASLR)" default y diff --git a/arch/s390/Makefile b/arch/s390/Makefile index 994f9b3d575f..2a58e1864931 100644 --- a/arch/s390/Makefile +++ b/arch/s390/Makefile @@ -14,8 +14,14 @@ KBUILD_AFLAGS_MODULE += -fPIC KBUILD_CFLAGS_MODULE += -fPIC KBUILD_AFLAGS += -m64 KBUILD_CFLAGS += -m64 +ifdef CONFIG_PIE_BUILD KBUILD_CFLAGS += -fPIE LDFLAGS_vmlinux := -pie -z notext +else +KBUILD_CFLAGS += $(call cc-option,-munaligned-symbols,) +LDFLAGS_vmlinux := --emit-relocs --discard-none +extra_tools := relocs +endif aflags_dwarf := -Wa,-gdwarf-2 KBUILD_AFLAGS_DECOMPRESSOR := $(CLANG_FLAGS) -m64 -D__ASSEMBLY__ ifndef CONFIG_AS_IS_LLVM @@ -143,7 +149,7 @@ archheaders: archprepare: $(Q)$(MAKE) $(build)=$(syscalls) kapi - $(Q)$(MAKE) $(build)=$(tools) kapi + $(Q)$(MAKE) $(build)=$(tools) kapi $(extra_tools) ifeq ($(KBUILD_EXTMOD),) # We need to generate vdso-offsets.h before compiling certain files in kernel/. # In order to do that, we should use the archprepare target, but we can't since diff --git a/arch/s390/boot/.gitignore b/arch/s390/boot/.gitignore index f56591bc0897..f5ef099e2fd3 100644 --- a/arch/s390/boot/.gitignore +++ b/arch/s390/boot/.gitignore @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only image bzImage +relocs.S section_cmp.* vmlinux vmlinux.lds diff --git a/arch/s390/boot/Makefile b/arch/s390/boot/Makefile index d40135efdec4..aecafabc2054 100644 --- a/arch/s390/boot/Makefile +++ b/arch/s390/boot/Makefile @@ -37,7 +37,8 @@ CFLAGS_sclp_early_core.o += -I$(srctree)/drivers/s390/char obj-y := head.o als.o startup.o physmem_info.o ipl_parm.o ipl_report.o vmem.o obj-y += string.o ebcdic.o sclp_early_core.o mem.o ipl_vmparm.o cmdline.o -obj-y += version.o pgm_check_info.o ctype.o ipl_data.o machine_kexec_reloc.o +obj-y += version.o pgm_check_info.o ctype.o ipl_data.o +obj-y += $(if $(CONFIG_PIE_BUILD),machine_kexec_reloc.o,relocs.o) obj-$(findstring y, $(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) $(CONFIG_PGSTE)) += uv.o obj-$(CONFIG_RANDOMIZE_BASE) += kaslr.o obj-y += $(if $(CONFIG_KERNEL_UNCOMPRESSED),,decompressor.o) info.o @@ -48,6 +49,9 @@ targets := bzImage section_cmp.boot.data section_cmp.boot.preserved.data $(obj-y targets += vmlinux.lds vmlinux vmlinux.bin vmlinux.bin.gz vmlinux.bin.bz2 targets += vmlinux.bin.xz vmlinux.bin.lzma vmlinux.bin.lzo vmlinux.bin.lz4 targets += vmlinux.bin.zst info.bin syms.bin vmlinux.syms $(obj-all) +ifndef CONFIG_PIE_BUILD +targets += relocs.S +endif OBJECTS := $(addprefix $(obj)/,$(obj-y)) OBJECTS_ALL := $(addprefix $(obj)/,$(obj-all)) @@ -106,6 +110,14 @@ OBJCOPYFLAGS_vmlinux.bin := -O binary --remove-section=.comment --remove-section $(obj)/vmlinux.bin: vmlinux FORCE $(call if_changed,objcopy) +ifndef CONFIG_PIE_BUILD +CMD_RELOCS=arch/s390/tools/relocs +quiet_cmd_relocs = RELOCS $@ + cmd_relocs = $(CMD_RELOCS) $< > $@ +$(obj)/relocs.S: vmlinux FORCE + $(call if_changed,relocs) +endif + suffix-$(CONFIG_KERNEL_GZIP) := .gz suffix-$(CONFIG_KERNEL_BZIP2) := .bz2 suffix-$(CONFIG_KERNEL_LZ4) := .lz4 diff --git a/arch/s390/boot/boot.h b/arch/s390/boot/boot.h index 222c6886acf6..8943d5be7ca2 100644 --- a/arch/s390/boot/boot.h +++ b/arch/s390/boot/boot.h @@ -25,9 +25,14 @@ struct vmlinux_info { unsigned long bootdata_size; unsigned long bootdata_preserved_off; unsigned long bootdata_preserved_size; +#ifdef CONFIG_PIE_BUILD unsigned long dynsym_start; unsigned long rela_dyn_start; unsigned long rela_dyn_end; +#else + unsigned long got_off; + unsigned long got_size; +#endif unsigned long amode31_size; unsigned long init_mm_off; unsigned long swapper_pg_dir_off; @@ -83,6 +88,7 @@ extern unsigned long vmalloc_size; extern int vmalloc_size_set; extern char __boot_data_start[], __boot_data_end[]; extern char __boot_data_preserved_start[], __boot_data_preserved_end[]; +extern int __vmlinux_relocs_64_start[], __vmlinux_relocs_64_end[]; extern char _decompressor_syms_start[], _decompressor_syms_end[]; extern char _stack_start[], _stack_end[]; extern char _end[], _decompressor_end[]; diff --git a/arch/s390/boot/startup.c b/arch/s390/boot/startup.c index 9cc76e631759..cb0d89801c43 100644 --- a/arch/s390/boot/startup.c +++ b/arch/s390/boot/startup.c @@ -141,7 +141,8 @@ static void copy_bootdata(void) memcpy((void *)vmlinux.bootdata_preserved_off, __boot_data_preserved_start, vmlinux.bootdata_preserved_size); } -static void handle_relocs(unsigned long offset) +#ifdef CONFIG_PIE_BUILD +static void kaslr_adjust_relocs(unsigned long min_addr, unsigned long offset) { Elf64_Rela *rela_start, *rela_end, *rela; int r_type, r_sym, rc; @@ -172,6 +173,62 @@ static void handle_relocs(unsigned long offset) } } +static void kaslr_adjust_got(unsigned long offset) {} +static void rescue_relocs(void) {} +static void free_relocs(void) {} +#else +int *vmlinux_relocs_64_start; +int *vmlinux_relocs_64_end; + +static void rescue_relocs(void) +{ + unsigned long size, nrelocs; + + nrelocs = __vmlinux_relocs_64_end - __vmlinux_relocs_64_start; + size = nrelocs * sizeof(uint32_t); + vmlinux_relocs_64_start = (void *)physmem_alloc_top_down(RR_RELOC, size, 0); + memmove(vmlinux_relocs_64_start, (void *)__vmlinux_relocs_64_start, size); + vmlinux_relocs_64_end = vmlinux_relocs_64_start + nrelocs; +} + +static void free_relocs(void) +{ + physmem_free(RR_RELOC); +} + +static void kaslr_adjust_relocs(unsigned long min_addr, unsigned long offset) +{ + int *reloc; + unsigned long max_addr = min_addr + vmlinux.image_size; + long loc; + + /* Adjust R_390_64 relocations */ + for (reloc = vmlinux_relocs_64_start; + reloc < vmlinux_relocs_64_end && *reloc; + reloc++) { + loc = (long)*reloc + offset; + if (loc < min_addr || loc > max_addr) + error("64-bit relocation outside of kernel!\n"); + *(u64 *)loc += offset; + } +} + +static void kaslr_adjust_got(unsigned long offset) +{ + u64 *entry; + + /* + * Even without -fPIE, Clang still uses a global offset table for some + * reason. Adjust the GOT entries. + */ + for (entry = (u64 *)vmlinux.got_off; + entry < (u64 *)(vmlinux.got_off + vmlinux.got_size); + entry++) { + *entry += offset; + } +} +#endif + /* * Merge information from several sources into a single ident_map_size value. * "ident_map_size" represents the upper limit of physical memory we may ever @@ -299,14 +356,18 @@ static void setup_vmalloc_size(void) vmalloc_size = max(size, vmalloc_size); } -static void offset_vmlinux_info(unsigned long offset) +static void kaslr_adjust_vmlinux_info(unsigned long offset) { *(unsigned long *)(&vmlinux.entry) += offset; vmlinux.bootdata_off += offset; vmlinux.bootdata_preserved_off += offset; +#ifdef CONFIG_PIE_BUILD vmlinux.rela_dyn_start += offset; vmlinux.rela_dyn_end += offset; vmlinux.dynsym_start += offset; +#else + vmlinux.got_off += offset; +#endif vmlinux.init_mm_off += offset; vmlinux.swapper_pg_dir_off += offset; vmlinux.invalid_pg_dir_off += offset; @@ -361,6 +422,7 @@ void startup_kernel(void) detect_physmem_online_ranges(max_physmem_end); save_ipl_cert_comp_list(); rescue_initrd(safe_addr, ident_map_size); + rescue_relocs(); if (kaslr_enabled()) { vmlinux_lma = randomize_within_range(vmlinux.image_size + vmlinux.bss_size, @@ -368,7 +430,7 @@ void startup_kernel(void) ident_map_size); if (vmlinux_lma) { __kaslr_offset = vmlinux_lma - vmlinux.default_lma; - offset_vmlinux_info(__kaslr_offset); + kaslr_adjust_vmlinux_info(__kaslr_offset); } } vmlinux_lma = vmlinux_lma ?: vmlinux.default_lma; @@ -393,18 +455,20 @@ void startup_kernel(void) /* * The order of the following operations is important: * - * - handle_relocs() must follow clear_bss_section() to establish static + * - kaslr_adjust_relocs() must follow clear_bss_section() to establish static * memory references to data in .bss to be used by setup_vmem() * (i.e init_mm.pgd) * - * - setup_vmem() must follow handle_relocs() to be able using + * - setup_vmem() must follow kaslr_adjust_relocs() to be able using * static memory references to data in .bss (i.e init_mm.pgd) * - * - copy_bootdata() must follow setup_vmem() to propagate changes to - * bootdata made by setup_vmem() + * - copy_bootdata() must follow setup_vmem() to propagate changes + * to bootdata made by setup_vmem() */ clear_bss_section(vmlinux_lma); - handle_relocs(__kaslr_offset); + kaslr_adjust_relocs(vmlinux_lma, __kaslr_offset); + kaslr_adjust_got(__kaslr_offset); + free_relocs(); setup_vmem(asce_limit); copy_bootdata(); diff --git a/arch/s390/boot/vmlinux.lds.S b/arch/s390/boot/vmlinux.lds.S index 4e3fb2b7abb6..e3208893ba6b 100644 --- a/arch/s390/boot/vmlinux.lds.S +++ b/arch/s390/boot/vmlinux.lds.S @@ -110,6 +110,24 @@ SECTIONS _compressed_end = .; } +#ifndef CONFIG_PIE_BUILD + /* + * When the kernel is built with CONFIG_KERNEL_UNCOMPRESSED, the entire + * uncompressed vmlinux.bin is positioned in the bzImage decompressor + * image at the default kernel LMA of 0x100000, enabling it to be + * executed in-place. However, the size of .vmlinux.relocs could be + * large enough to cause an overlap with the uncompressed kernel at the + * address 0x100000. To address this issue, .vmlinux.relocs is + * positioned after the .rodata.compressed. + */ + . = ALIGN(4); + .vmlinux.relocs : { + __vmlinux_relocs_64_start = .; + *(.vmlinux.relocs_64) + __vmlinux_relocs_64_end = .; + } +#endif + #define SB_TRAILER_SIZE 32 /* Trailer needed for Secure Boot */ . += SB_TRAILER_SIZE; /* make sure .sb.trailer does not overwrite the previous section */ diff --git a/arch/s390/include/asm/physmem_info.h b/arch/s390/include/asm/physmem_info.h index 9e41a74fce9a..e747b067f8db 100644 --- a/arch/s390/include/asm/physmem_info.h +++ b/arch/s390/include/asm/physmem_info.h @@ -22,6 +22,7 @@ enum reserved_range_type { RR_DECOMPRESSOR, RR_INITRD, RR_VMLINUX, + RR_RELOC, RR_AMODE31, RR_IPLREPORT, RR_CERT_COMP_LIST, diff --git a/arch/s390/kernel/vmlinux.lds.S b/arch/s390/kernel/vmlinux.lds.S index 63cb403b3344..404883b1b023 100644 --- a/arch/s390/kernel/vmlinux.lds.S +++ b/arch/s390/kernel/vmlinux.lds.S @@ -63,7 +63,9 @@ SECTIONS *(.data.rel.ro .data.rel.ro.*) } .got : { + __got_start = .; *(.got) + __got_end = .; } . = ALIGN(PAGE_SIZE); @@ -190,6 +192,7 @@ SECTIONS PERCPU_SECTION(0x100) +#ifdef CONFIG_PIE_BUILD .dynsym ALIGN(8) : { __dynsym_start = .; *(.dynsym) @@ -206,6 +209,7 @@ SECTIONS .dynstr ALIGN(8) : { *(.dynstr) } +#endif .hash ALIGN(8) : { *(.hash) } @@ -235,9 +239,14 @@ SECTIONS QUAD(__boot_data_preserved_start) /* bootdata_preserved_off */ QUAD(__boot_data_preserved_end - __boot_data_preserved_start) /* bootdata_preserved_size */ +#ifdef CONFIG_PIE_BUILD QUAD(__dynsym_start) /* dynsym_start */ QUAD(__rela_dyn_start) /* rela_dyn_start */ QUAD(__rela_dyn_end) /* rela_dyn_end */ +#else + QUAD(__got_start) /* got_off */ + QUAD(__got_end - __got_start) /* got_size */ +#endif QUAD(_eamode31 - _samode31) /* amode31_size */ QUAD(init_mm) QUAD(swapper_pg_dir) @@ -268,6 +277,12 @@ SECTIONS *(.plt) *(.plt.*) *(.iplt) *(.igot .igot.plt) } ASSERT(SIZEOF(.plt) == 0, "Unexpected run-time procedure linkages detected!") +#ifndef CONFIG_PIE_BUILD + .rela.dyn : { + *(.rela.*) *(.rela_*) + } + ASSERT(SIZEOF(.rela.dyn) == 0, "Unexpected run-time relocations (.rela) detected!") +#endif /* Sections to be discarded */ DISCARDS