[RFC/RFT PATCH 19/19] x86/kernel: Switch to PIE linking for the relocatable kernel

Ard Biesheuvel
2026-01-08T09:28:49Z

If the toolchain supports RELR relocation packing, build the virtually
relocatable kernels as Position Independent (PIE) Executables. This
results in more efficient relocation processing for the virtual
displacement of the kernel applied at boot, using RELR relocations that
take up only a fraction of the space occupied by ordinary RELA
relocations.

More importantly, it instructs the linker to generate a binary that is
really meant to be relocated at boot, using data structures that are
intended for this purpose. Doing so is important for a couple of
reasons:

- Relying on --emit-relocs is problematic, because it produces the
static relocations that are consumed by the linker as input, and these
are not meant for describing a runtime relocatable image. For example,
the linker may apply relaxations that result in the code and the
static relocation going out of sync (and ld.bfd and ld.lld already
handle this in a different way).

- The 'relocs' tool relies on manually kept allow/deny lists of symbol
names. These are needed because ELF absolute/relative symbol
designations are often inaccurate.

- x86 deviates from other architectures in the kernel when it comes to
its implementation of boot-time relocation, making it difficult to
implement further enhancements (e.g., fgkaslr, EFI zboot) in a
portable manner.

Note that this means that all codegen on x86_64 should be position
independent, to be compatible with PIE linking, but only if KASLR is
enabled. On i386, no changes to the codegen are needed, as the ordinary
position dependent relocation model is supported by the linker when
operating in PIE mode.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
---
arch/x86/Kconfig | 3 ++-
arch/x86/Makefile | 5 +++++
arch/x86/kernel/vmlinux.lds.S | 18 ++++++++++++++++++
3 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index b3a64cfe04cf..2aa50aa8dc68 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -103,6 +103,7 @@ config X86
select ARCH_HAS_NONLEAF_PMD_YOUNG if PGTABLE_LEVELS > 2
select ARCH_HAS_UACCESS_FLUSHCACHE if X86_64
select ARCH_HAS_COPY_MC if X86_64
+ select ARCH_HAS_RELR
select ARCH_HAS_SET_MEMORY
select ARCH_HAS_SET_DIRECT_MAP
select ARCH_HAS_STRICT_KERNEL_RWX
@@ -2129,7 +2130,7 @@ config RANDOMIZE_BASE
# Relocation on x86 needs some additional build support
config X86_NEED_RELOCS
def_bool y
- depends on RELOCATABLE
+ depends on RELOCATABLE && !TOOLS_SUPPORT_RELR
select ARCH_VMLINUX_NEEDS_RELOCS

config PHYSICAL_ALIGN
diff --git a/arch/x86/Makefile b/arch/x86/Makefile
index b211d6c950aa..7eac705c4ff4 100644
--- a/arch/x86/Makefile
+++ b/arch/x86/Makefile
@@ -258,6 +258,11 @@ endif

KBUILD_LDFLAGS += -m elf_$(UTS_MACHINE)

+ldflags-pie-$(CONFIG_LD_IS_LLD) := --apply-dynamic-relocs
+ldflags-pie-$(CONFIG_LD_IS_BFD) := -z call-nop=suffix-nop
+ldflags-$(CONFIG_RELOCATABLE_PIE) := --pie -z notext $(ldflags-pie-y)
+LDFLAGS_vmlinux += $(ldflags-y)
+
#
# The 64-bit kernel must be aligned to 2MB. Pass -z max-page-size=0x200000 to
# the linker to force 2MB page size regardless of the default page size used
diff --git a/arch/x86/kernel/vmlinux.lds.S b/arch/x86/kernel/vmlinux.lds.S
index 6772fe9a9957..cfaf6ab80684 100644
--- a/arch/x86/kernel/vmlinux.lds.S
+++ b/arch/x86/kernel/vmlinux.lds.S
@@ -127,6 +127,9 @@ PHDRS {
text PT_LOAD FLAGS(5); /* R_E */
data PT_LOAD FLAGS(6); /* RW_ */
note PT_NOTE FLAGS(0); /* ___ */
+#ifdef CONFIG_RELOCATABLE_PIE
+ dynamic PT_DYNAMIC;
+#endif
}

SECTIONS
@@ -201,6 +204,21 @@ SECTIONS
DATA_SEGMENT_START
INIT_DATA_SECTION(16) :data

+#ifdef CONFIG_RELOCATABLE_PIE
+ /DISCARD/ : {
+ *(.interp .dynbss .eh_frame .sframe .relr.auth.dyn)
+ }
+
+ .dynamic : { *(.dynamic) } :dynamic :data
+ .dynstr : { *(.dynstr) } :data
+ .dynsym : { *(.dynsym) }
+ .gnu.hash : { *(.gnu.hash) }
+ .hash : { *(.hash) }
+ .init.rela : { *(.rela.*) *(.rela_*) }
+ .init.rel : { *(.rel.*) *(.rel_*) }
+ .init.relr : { *(.relr.*) }
+#endif
+
.x86_cpu_dev.init : AT(ADDR(.x86_cpu_dev.init) - LOAD_OFFSET) {
__x86_cpu_dev_start = .;
*(.x86_cpu_dev.init)
--
2.47.3