Skip to content

Commit 39b3307

Browse files
Alexandre Ghitipalmer-dabbelt
authored andcommitted
riscv: Introduce CONFIG_RELOCATABLE
This config allows to compile 64b kernel as PIE and to relocate it at any virtual address at runtime: this paves the way to KASLR. Runtime relocation is possible since relocation metadata are embedded into the kernel. Note that relocating at runtime introduces an overhead even if the kernel is loaded at the same address it was linked at and that the compiler options are those used in arm64 which uses the same RELA relocation format. Signed-off-by: Alexandre Ghiti <alexghiti@rivosinc.com> Link: https://lore.kernel.org/r/20230329045329.64565-4-alexghiti@rivosinc.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
1 parent 69a90d2 commit 39b3307

5 files changed

Lines changed: 91 additions & 5 deletions

File tree

arch/riscv/Kconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,20 @@ config COMPAT
561561

562562
If you want to execute 32-bit userspace applications, say Y.
563563

564+
config RELOCATABLE
565+
bool "Build a relocatable kernel"
566+
depends on MMU && 64BIT && !XIP_KERNEL
567+
help
568+
This builds a kernel as a Position Independent Executable (PIE),
569+
which retains all relocation metadata required to relocate the
570+
kernel binary at runtime to a different virtual address than the
571+
address it was linked at.
572+
Since RISCV uses the RELA relocation format, this requires a
573+
relocation pass at runtime even if the kernel is loaded at the
574+
same address it was linked at.
575+
576+
If unsure, say N.
577+
564578
endmenu # "Kernel features"
565579

566580
menu "Boot options"

arch/riscv/Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
#
88

99
OBJCOPYFLAGS := -O binary
10-
LDFLAGS_vmlinux :=
10+
ifeq ($(CONFIG_RELOCATABLE),y)
11+
LDFLAGS_vmlinux += -shared -Bsymbolic -z notext -z norelro
12+
KBUILD_CFLAGS += -fPIE
13+
endif
1114
ifeq ($(CONFIG_DYNAMIC_FTRACE),y)
12-
LDFLAGS_vmlinux := --no-relax
15+
LDFLAGS_vmlinux += --no-relax
1316
KBUILD_CPPFLAGS += -DCC_USING_PATCHABLE_FUNCTION_ENTRY
1417
ifeq ($(CONFIG_RISCV_ISA_C),y)
1518
CC_FLAGS_FTRACE := -fpatchable-function-entry=4

arch/riscv/kernel/vmlinux.lds.S

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,23 @@ SECTIONS
122122
*(.sdata*)
123123
}
124124

125-
.rela.dyn : {
126-
*(.rela*)
125+
.rela.dyn : ALIGN(8) {
126+
__rela_dyn_start = .;
127+
*(.rela .rela*)
128+
__rela_dyn_end = .;
127129
}
128130

131+
#ifdef CONFIG_RELOCATABLE
132+
.data.rel : { *(.data.rel*) }
133+
.got : { *(.got*) }
134+
.plt : { *(.plt) }
135+
.dynamic : { *(.dynamic) }
136+
.dynsym : { *(.dynsym) }
137+
.dynstr : { *(.dynstr) }
138+
.hash : { *(.hash) }
139+
.gnu.hash : { *(.gnu.hash) }
140+
#endif
141+
129142
#ifdef CONFIG_EFI
130143
.pecoff_edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGNMENT); }
131144
__pecoff_data_raw_size = ABSOLUTE(. - __pecoff_text_end);

arch/riscv/mm/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# SPDX-License-Identifier: GPL-2.0-only
22

33
CFLAGS_init.o := -mcmodel=medany
4+
ifdef CONFIG_RELOCATABLE
5+
CFLAGS_init.o += -fno-pie
6+
endif
7+
48
ifdef CONFIG_FTRACE
59
CFLAGS_REMOVE_init.o = $(CC_FLAGS_FTRACE)
610
CFLAGS_REMOVE_cacheflush.o = $(CC_FLAGS_FTRACE)

arch/riscv/mm/init.c

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
#include <linux/dma-map-ops.h>
2121
#include <linux/crash_dump.h>
2222
#include <linux/hugetlb.h>
23+
#ifdef CONFIG_RELOCATABLE
24+
#include <linux/elf.h>
25+
#endif
2326

2427
#include <asm/fixmap.h>
2528
#include <asm/tlbflush.h>
@@ -146,7 +149,7 @@ static void __init print_vm_layout(void)
146149
print_ml("kasan", KASAN_SHADOW_START, KASAN_SHADOW_END);
147150
#endif
148151

149-
print_ml("kernel", (unsigned long)KERNEL_LINK_ADDR,
152+
print_ml("kernel", (unsigned long)kernel_map.virt_addr,
150153
(unsigned long)ADDRESS_SPACE_END);
151154
}
152155
}
@@ -820,6 +823,44 @@ static __init void set_satp_mode(void)
820823
#error "setup_vm() is called from head.S before relocate so it should not use absolute addressing."
821824
#endif
822825

826+
#ifdef CONFIG_RELOCATABLE
827+
extern unsigned long __rela_dyn_start, __rela_dyn_end;
828+
829+
static void __init relocate_kernel(void)
830+
{
831+
Elf64_Rela *rela = (Elf64_Rela *)&__rela_dyn_start;
832+
/*
833+
* This holds the offset between the linked virtual address and the
834+
* relocated virtual address.
835+
*/
836+
uintptr_t reloc_offset = kernel_map.virt_addr - KERNEL_LINK_ADDR;
837+
/*
838+
* This holds the offset between kernel linked virtual address and
839+
* physical address.
840+
*/
841+
uintptr_t va_kernel_link_pa_offset = KERNEL_LINK_ADDR - kernel_map.phys_addr;
842+
843+
for ( ; rela < (Elf64_Rela *)&__rela_dyn_end; rela++) {
844+
Elf64_Addr addr = (rela->r_offset - va_kernel_link_pa_offset);
845+
Elf64_Addr relocated_addr = rela->r_addend;
846+
847+
if (rela->r_info != R_RISCV_RELATIVE)
848+
continue;
849+
850+
/*
851+
* Make sure to not relocate vdso symbols like rt_sigreturn
852+
* which are linked from the address 0 in vmlinux since
853+
* vdso symbol addresses are actually used as an offset from
854+
* mm->context.vdso in VDSO_OFFSET macro.
855+
*/
856+
if (relocated_addr >= KERNEL_LINK_ADDR)
857+
relocated_addr += reloc_offset;
858+
859+
*(Elf64_Addr *)addr = relocated_addr;
860+
}
861+
}
862+
#endif /* CONFIG_RELOCATABLE */
863+
823864
#ifdef CONFIG_XIP_KERNEL
824865
static void __init create_kernel_page_table(pgd_t *pgdir,
825866
__always_unused bool early)
@@ -1007,6 +1048,17 @@ asmlinkage void __init setup_vm(uintptr_t dtb_pa)
10071048
BUG_ON((kernel_map.virt_addr + kernel_map.size) > ADDRESS_SPACE_END - SZ_4K);
10081049
#endif
10091050

1051+
#ifdef CONFIG_RELOCATABLE
1052+
/*
1053+
* Early page table uses only one PUD, which makes it possible
1054+
* to map PUD_SIZE aligned on PUD_SIZE: if the relocation offset
1055+
* makes the kernel cross over a PUD_SIZE boundary, raise a bug
1056+
* since a part of the kernel would not get mapped.
1057+
*/
1058+
BUG_ON(PUD_SIZE - (kernel_map.virt_addr & (PUD_SIZE - 1)) < kernel_map.size);
1059+
relocate_kernel();
1060+
#endif
1061+
10101062
apply_early_boot_alternatives();
10111063
pt_ops_set_early();
10121064

0 commit comments

Comments
 (0)