Skip to contentSkip to conversation

arm64: implement virtual KASLR

PATCH New Ard Biesheuvel 4 participants 47 messages Feb 1, 2016 – Feb 25, 2016

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

cover arm64: implement virtual KASLR

At the request of Catalin, this series has been split off from my series 'arm64: implement support for KASLR v4' [1]. This sub-series deals with creating a relocatable binary, and randomizing the virtual placement of the kernel itself, the modules and the linear region based on entropy supplied by the bootloader in the /chosen/kaslr-seed DT property.

Changes since v4:
- add randomization of the linear region, i.e., if the linear region is substantially larger than the space spanned by RAM, the mapping of RAM is moved to a random offset inside the linear region.
- dropped the CRC check in kaslr_early_init(), since the only code that may modify .data through __fixmap_remap_fdt() is instrumentation that should deal with that correctly (For instance, the branch profiling records taken branches in structs allocated in .data, and these counts will not be reset to 0 between the first and the second call to __mmap_switched())
- add Mark's ack to patch #4

Patch #1 adds support for resolving cross-module branches via veneers if the module is located too far from the target. This should not be necessary in the common case even with KASRL enabled, but since the space around the kernel image will no longer be strictly reserved for modules, there is a slight chance that the region may be exhausted, in which case modules will be moved further away from the kernel (and each other)

Patches #2 and #3 deal with the fact that relocations of 64-bit values are deferred to runtime when building a PIE executable, and some values are required before the relocation routine executes (either in the code, or as an externally visible constant in the Image header)

Patch #4 rehuffles asm/elf.h so we can use its preprocessor constants in head.S

Patch #5 and #6 implement building vmlinux as a PIE (Position Independent Executable) binary, which is emitted with relocation information that is processed by the program itself in the early boot code.

Patch #7 implements KASLR, i.e., CONFIG_RANDOMIZE_BASE for the core kernel and for the module region.

Patch #8 implements KASLR for the linear region.

[1] http://thread.gmane.org/gmane.linux.kernel/2135931

Ard Biesheuvel (8):
arm64: add support for module PLTs
arm64: avoid R_AARCH64_ABS64 relocations for Image header fields
arm64: avoid dynamic relocations in early boot code
arm64: make asm/elf.h available to asm files scripts/sortextable: add support for ET_DYN binaries
arm64: add support for building vmlinux as a relocatable PIE binary
arm64: add support for kernel ASLR
arm64: kaslr: randomize the linear region

 arch/arm64/Kconfig                 |  34 ++++
 arch/arm64/Makefile                |  10 +-
 arch/arm64/include/asm/assembler.h |  11 ++
 arch/arm64/include/asm/elf.h       |  24 ++-
 arch/arm64/include/asm/memory.h    |   5 +-
 arch/arm64/include/asm/module.h    |  11 ++
 arch/arm64/kernel/Makefile         |   2 +
 arch/arm64/kernel/efi-entry.S      |   2 +-
 arch/arm64/kernel/head.S           | 128 +++++++++++--
 arch/arm64/kernel/image.h          |  32 ++--
 arch/arm64/kernel/kaslr.c          | 143 ++++++++++++++
 arch/arm64/kernel/module-plts.c    | 201 ++++++++++++++++++++
 arch/arm64/kernel/module.c         |  20 +-
 arch/arm64/kernel/module.lds       |   3 +
 arch/arm64/kernel/setup.c          |  29 +++
 arch/arm64/kernel/vmlinux.lds.S    |  16 ++
 arch/arm64/mm/init.c               |  22 ++-
 arch/arm64/mm/mmu.c                |  33 +++-
 scripts/sortextable.c              |   8 +-
 19 files changed, 672 insertions(+), 62 deletions(-)
 create mode 100644 arch/arm64/kernel/kaslr.c
 create mode 100644 arch/arm64/kernel/module-plts.c
 create mode 100644 arch/arm64/kernel/module.lds
Signature · 1 line
2.5.0
#1 lore raw

PATCH 1/8 arm64: add support for module PLTs

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

---

In this version, I removed the distinction between relocations against .init executable sections and ordinary executable sections. The reason is that it is hardly worth the trouble, given that .init.text usually does not contain that many far branches, and this version now only reserves PLT entry space for jump and call relocations against undefined symbols (since symbols defined in the same module can be assumed to be within +/- 128 MB)

For example, the mac80211.ko module (which is fairly sizable at ~400 KB) built with -mcmodel=large gives the following relocation counts:

relocs branches unique !local
.text 3925 3347 518 219

  .init.text           11          8         7          1
  .exit.text            4          4         4          1

.text.unlikely 81 67 36 17

('unique' means branches to unique type/symbol/addend combos, of which !local is the subset referring to undefined symbols)

IOW, we are only emitting a single PLT entry for the .init sections, and we are better off just adding it to the core PLT section instead.

---
 arch/arm64/Kconfig              |   9 +
 arch/arm64/Makefile             |   6 +-
 arch/arm64/include/asm/module.h |  11 ++
 arch/arm64/kernel/Makefile      |   1 +
 arch/arm64/kernel/module-plts.c | 201 ++++++++++++++++++++
 arch/arm64/kernel/module.c      |  12 ++
 arch/arm64/kernel/module.lds    |   3 +
 7 files changed, 242 insertions(+), 1 deletion(-)
arch/arm64/Kconfig +9 -0
index cd767fa3037a..141f65ab0ed5 100644
@@ -394,6 +394,7 @@ config ARM64_ERRATUM_843419
 	bool "Cortex-A53: 843419: A load or store might access an incorrect address"
 	depends on MODULES
 	default y
+	select ARM64_MODULE_CMODEL_LARGE
 	help
 	  This option builds kernel modules using the large memory model in
 	  order to avoid the use of the ADRP instruction, which can cause
@@ -753,6 +754,14 @@ config ARM64_LSE_ATOMICS
 
 endmenu
 
+config ARM64_MODULE_CMODEL_LARGE
+	bool
+
+config ARM64_MODULE_PLTS
+	bool
+	select ARM64_MODULE_CMODEL_LARGE
+	select HAVE_MOD_ARCH_SPECIFIC
+
 endmenu
 
 menu "Boot options"
arch/arm64/Makefile +5 -1
index 307237cfe728..a6bba9623836 100644
@@ -43,10 +43,14 @@ endif
 
 CHECKFLAGS	+= -D__aarch64__
 
-ifeq ($(CONFIG_ARM64_ERRATUM_843419), y)
+ifeq ($(CONFIG_ARM64_MODULE_CMODEL_LARGE), y)
 KBUILD_CFLAGS_MODULE	+= -mcmodel=large
 endif
 
+ifeq ($(CONFIG_ARM64_MODULE_PLTS),y)
+KBUILD_LDFLAGS_MODULE	+= -T $(srctree)/arch/arm64/kernel/module.lds
+endif
+
 # Default value
 head-y		:= arch/arm64/kernel/head.o
 
arch/arm64/include/asm/module.h +11 -0
index e80e232b730e..8652fb613304 100644
@@ -20,4 +20,15 @@
 
 #define MODULE_ARCH_VERMAGIC	"aarch64"
 
+#ifdef CONFIG_ARM64_MODULE_PLTS
+struct mod_arch_specific {
+	struct elf64_shdr	*plt;
+	int			plt_num_entries;
+	int			plt_max_entries;
+};
+#endif
+
+u64 module_emit_plt_entry(struct module *mod, const Elf64_Rela *rela,
+			  Elf64_Sym *sym);
+
 #endif /* __ASM_MODULE_H */
arch/arm64/kernel/Makefile +1 -0
index 83cd7e68e83b..e2f0a755beaa 100644
@@ -30,6 +30,7 @@ arm64-obj-$(CONFIG_COMPAT)		+= sys32.o kuser32.o signal32.o 	\
 					   ../../arm/kernel/opcodes.o
 arm64-obj-$(CONFIG_FUNCTION_TRACER)	+= ftrace.o entry-ftrace.o
 arm64-obj-$(CONFIG_MODULES)		+= arm64ksyms.o module.o
+arm64-obj-$(CONFIG_ARM64_MODULE_PLTS)	+= module-plts.o
 arm64-obj-$(CONFIG_PERF_EVENTS)		+= perf_regs.o perf_callchain.o
 arm64-obj-$(CONFIG_HW_PERF_EVENTS)	+= perf_event.o
 arm64-obj-$(CONFIG_HAVE_HW_BREAKPOINT)	+= hw_breakpoint.o
arch/arm64/kernel/module-plts.c new file +201 -0

201 lines not on this page · raw message

arch/arm64/kernel/module.c +12 -0
index 93e970231ca9..84113d3e1df1 100644
@@ -38,6 +38,11 @@ void *module_alloc(unsigned long size)
 				GFP_KERNEL, PAGE_KERNEL_EXEC, 0,
 				NUMA_NO_NODE, __builtin_return_address(0));
 
+	if (!p && IS_ENABLED(CONFIG_ARM64_MODULE_PLTS))
+		p = __vmalloc_node_range(size, MODULE_ALIGN, VMALLOC_START,
+				VMALLOC_END, GFP_KERNEL, PAGE_KERNEL_EXEC, 0,
+				NUMA_NO_NODE, __builtin_return_address(0));
+
 	if (p && (kasan_module_alloc(p, size) < 0)) {
 		vfree(p);
 		return NULL;
@@ -361,6 +366,13 @@ int apply_relocate_add(Elf64_Shdr *sechdrs,
 		case R_AARCH64_CALL26:
 			ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2, 26,
 					     AARCH64_INSN_IMM_26);
+
+			if (IS_ENABLED(CONFIG_ARM64_MODULE_PLTS) &&
+			    ovf == -ERANGE) {
+				val = module_emit_plt_entry(me, &rel[i], sym);
+				ovf = reloc_insn_imm(RELOC_OP_PREL, loc, val, 2,
+						     26, AARCH64_INSN_IMM_26);
+			}
 			break;
 
 		default:
arch/arm64/kernel/module.lds new file +3 -0
new file mode 100644
index 000000000000..8949f6c6f729
@@ -0,0 +1,3 @@
+SECTIONS {
+	.plt (NOLOAD) : { BYTE(0) }
+}
Signature · 1 line
2.5.0
maintainer
#2 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Signature · 1 line
Catalin
#3 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 10 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

So while this patch was a requirement at first (since in the first version of the series, the modules always remained below the vmalloc area so they would always be out of range when KASLR was enabled), in the current series, it is simply a last resort (although still required) to make sure that modules can be loaded far away from the kernel in the unlikely event that all VMA space in close proximity is given to another user.

Reserving a dedicated module region around the kernel while it is loaded at a random offset is not straight forward, since it affects all other users of the vmalloc area. So generic vmalloc/vmap/ioremap code would need to be updated to disregard the area around the kernel, since simply reserving the VMA area for the modules would make it inaccessible to module_alloc() as well (unless we reserve the whole region and manage the module allocations separately)

So the bottom line is that it may be possible, but it is unlikely to be worth the effort imo

Signature · 1 line
Ard.
maintainer
#4 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

My worry is that we merge some code that's rarely tested.

Quoted 2 lines

So the bottom line is that it may be possible, but it is unlikely to be worth the effort imo

The alternative of having our own allocator for modules isn't that appealing either, so let's stick with the PLTs.

Signature · 1 line
Catalin
#5 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 2 lines

My worry is that we merge some code that's rarely tested.

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

Quoted 8 lines
Quoted 2 lines

So the bottom line is that it may be possible, but it is unlikely to be worth the effort imo

The alternative of having our own allocator for modules isn't that appealing either, so let's stick with the PLTs.

--
Catalin

maintainer
#6 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 24 lines · Ard Biesheuvel
Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 1 line

My worry is that we merge some code that's rarely tested.

Quoted 7 lines

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

I'm fine with this. You can post it as a separate patch that we can easily revert/modify later (like turning it into a config option).

Signature · 1 line
Catalin
#7 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 28 lines · Catalin Marinas
Quoted 27 lines · Ard Biesheuvel
Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 1 line

My worry is that we merge some code that's rarely tested.

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Quoted 8 lines
Quoted 4 lines

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

I'm fine with this. You can post it as a separate patch that we can easily revert/modify later (like turning it into a config option).

OK, I will hack something up

maintainer
#8 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 34 lines · Ard Biesheuvel
Quoted 33 lines · Catalin Marinas
Quoted 31 lines · Ard Biesheuvel
Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 1 line

My worry is that we merge some code that's rarely tested.

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

Quoted 4 lines
Quoted 2 lines

I'm fine with this. You can post it as a separate patch that we can easily revert/modify later (like turning it into a config option).

OK, I will hack something up

If it's simpler, you can just add a config option but defaulting to the full vmalloc space for modules.

Signature · 1 line
Catalin
#9 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 35 lines · Catalin Marinas
Quoted 34 lines · Ard Biesheuvel
Quoted 33 lines · Catalin Marinas
Quoted 31 lines · Ard Biesheuvel
Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 1 line

My worry is that we merge some code that's rarely tested.

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

I'm fine with this. You can post it as a separate patch that we can easily revert/modify later (like turning it into a config option).

OK, I will hack something up

If it's simpler, you can just add a config option but defaulting to the full vmalloc space for modules.

What would be the simplest is to randomize the 128 MB module region as a whole, and either put it close to the kernel (which is what I am doing now), or put it at a random offset inside the vmalloc space, in which case all branches will be resolved via PLTs. My suggestion to randomize each module_alloc() call separately is actually not that straight-forward.

So what I propose now is to keep a single module_load_offset that randomizes the base of the region, and add a default-n config option that shrinks the interval it is chosen from so that PLTs are usually not needed.

Signature · 1 line
Ard.
maintainer
#10 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 51 lines · Ard Biesheuvel
Quoted 35 lines · Catalin Marinas
Quoted 34 lines · Ard Biesheuvel
Quoted 33 lines · Catalin Marinas
Quoted 31 lines · Ard Biesheuvel
Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 1 line

My worry is that we merge some code that's rarely tested.

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

I'm fine with this. You can post it as a separate patch that we can easily revert/modify later (like turning it into a config option).

OK, I will hack something up

If it's simpler, you can just add a config option but defaulting to the full vmalloc space for modules.

What would be the simplest is to randomize the 128 MB module region as a whole, and either put it close to the kernel (which is what I am doing now), or put it at a random offset inside the vmalloc space, in which case all branches will be resolved via PLTs. My suggestion to randomize each module_alloc() call separately is actually not that straight-forward.

Quoted 4 lines

So what I propose now is to keep a single module_load_offset that randomizes the base of the region, and add a default-n config option that shrinks the interval it is chosen from so that PLTs are usually not needed.

Sounds fine.

Signature · 1 line
Catalin
#11 lore raw

PATCH 1/8 arm64: add support for module PLTs

In reply to Ard Biesheuvel
Quoted 45 lines · Ard Biesheuvel
Quoted 35 lines · Catalin Marinas
Quoted 34 lines · Ard Biesheuvel
Quoted 33 lines · Catalin Marinas
Quoted 31 lines · Ard Biesheuvel
Quoted 21 lines · Catalin Marinas
Quoted 11 lines · Ard Biesheuvel
Quoted 9 lines · Catalin Marinas
Quoted 5 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

Any downside to trying to keep the kernel+modules coupled together so that we avoid the PLT?

Quoted 8 lines

First of all, note that it is unlikely that the PLTs are ever required in practice, considering that either
a) the kernel is loaded at the default location right at the base of the vmalloc range, and in this case, the module space is reserved for modules only, or
b) the kernel is loaded at some random offset in the 240+ GB vmalloc space, and it is unlikely that all VMA space around the kernel happens to be given out to non-randomized vmalloc/ioremap allocations

Quoted 1 line

My worry is that we merge some code that's rarely tested.

I understand. But unfortunately, having corner cases that are unlikely but not impossible comes with the territory of randomization.

Alternatively, we could take the performance hit if KASLR is in effect and allocate each module completely randomly as well. This way, the code is always exercised (for now), and we can always backpedal later if the performance is measurably worse.

Quoted 8 lines
Quoted 4 lines
Quoted 2 lines

I'm fine with this. You can post it as a separate patch that we can easily revert/modify later (like turning it into a config option).

OK, I will hack something up

If it's simpler, you can just add a config option but defaulting to the full vmalloc space for modules.

Quoted 6 lines

What would be the simplest is to randomize the 128 MB module region as a whole, and either put it close to the kernel (which is what I am doing now), or put it at a random offset inside the vmalloc space, in which case all branches will be resolved via PLTs. My suggestion to randomize each module_alloc() call separately is actually not that straight-forward.

FWIW, it also turns out that this ends up not being very random in a limited address range. It ends up turning into a greedy-allocation hole-filling routine, and for a given system ends up being pretty deterministic.

-Kees

Signature · 2 lines
Kees Cook
Chrome OS & Brillo Security
#12 lore raw

PATCH 1/1 arm64: allow the module region to be randomized independently

This patch applies directly onto '[PATCH v5sub2 7/8] arm64: add support for kernel ASLR' so it can be folded in or applied separately, as desired.

As pointed out by Kees, fully randomizing each module allocation does not add anything in terms of security, and only hurts performance even more, since it would cause all inter-module branches to be resolved via veneers as well.

----------8<--------------
This adds the option to randomize the module region independently from the core kernel, and enables it by default. This makes it less likely that the location of core kernel data structures can be determined by an adversary, but causes all function calls from modules into the core kernel to be resolved via entries in the module PLTs.

---
 arch/arm64/Kconfig              | 15 ++++++++
 arch/arm64/include/asm/module.h |  6 ++++
 arch/arm64/kernel/kaslr.c       | 36 +++++++++++++++-----
 arch/arm64/kernel/module.c      |  9 ++---
 4 files changed, 50 insertions(+), 16 deletions(-)
arch/arm64/Kconfig +15 -0
index e3049d5c1246..666aacc4c763 100644
@@ -787,6 +787,21 @@ config RANDOMIZE_BASE
 
 	  If unsure, say N.
 
+config RANDOMIZE_MODULE_REGION_FULL
+	bool "Randomize the module region independently from the core kernel"
+	depends on RANDOMIZE_BASE
+	default y
+	help
+	  Randomizes the location of the module region without considering the
+	  location of the core kernel. This way, it is impossible for modules
+	  to leak information about the location of core kernel data structures
+	  but it does imply that function calls between modules and the core
+	  kernel will need to be resolved via veneers in the module PLT.
+
+	  When this option is not set, the module region will be randomized over
+	  a limited range that contains the [_stext, _etext] interval of the
+	  core kernel, so branch relocations are always in range.
+
 endmenu
 
 menu "Boot options"
arch/arm64/include/asm/module.h +6 -0
index 8652fb613304..e12af6754634 100644
@@ -31,4 +31,10 @@ struct mod_arch_specific {
 u64 module_emit_plt_entry(struct module *mod, const Elf64_Rela *rela,
 			  Elf64_Sym *sym);
 
+#ifdef CONFIG_RANDOMIZE_BASE
+extern u64 module_alloc_base;
+#else
+#define module_alloc_base	((u64)_etext - MODULES_VSIZE)
+#endif
+
 #endif /* __ASM_MODULE_H */
arch/arm64/kernel/kaslr.c +27 -9
index b0bf628ba51f..7a40aa4ba93d 100644
@@ -20,7 +20,7 @@
 #include <asm/pgtable.h>
 #include <asm/sections.h>
 
-u32 __read_mostly module_load_offset;
+u64 __read_mostly module_alloc_base;
 
 static __init u64 get_kaslr_seed(void *fdt)
 {
@@ -126,14 +126,32 @@ u64 __init kaslr_early_init(u64 dt_phys)
 	    (((u64)_end + offset) >> SWAPPER_TABLE_SHIFT))
 		offset = (offset + (u64)(_end - _text)) & mask;
 
-	/*
-	 * Randomize the module region, by setting module_load_offset to
-	 * a PAGE_SIZE multiple in the interval [0, module_range). This
-	 * ensures that the resulting region still covers [_stext, _etext],
-	 * and that all relative branches can be resolved without veneers.
-	 */
-	module_range = MODULES_VSIZE - (u64)(_etext - _stext);
-	module_load_offset = ((module_range * (u16)seed) >> 16) & PAGE_MASK;
+	if (IS_ENABLED(CONFIG_RANDOMIZE_MODULE_REGION_FULL)) {
+		/*
+		 * Randomize the module region independently from the core
+		 * kernel. This prevents modules from leaking any information
+		 * about the address of the kernel itself, but results in
+		 * branches between modules and the core kernel that are
+		 * resolved via PLTs. (Branches between modules will be
+		 * resolved normally.)
+		 */
+		module_range = VMALLOC_END - VMALLOC_START - MODULES_VSIZE;
+		module_alloc_base = VMALLOC_START;
+	} else {
+		/*
+		 * Randomize the module region by setting module_alloc_base to
+		 * a PAGE_SIZE multiple in the range [_etext - MODULES_VSIZE,
+		 * _stext) . This guarantees that the resulting region still
+		 * covers [_stext, _etext], and that all relative branches can
+		 * be resolved without veneers.
+		 */
+		module_range = MODULES_VSIZE - (u64)(_etext - _stext);
+		module_alloc_base = (u64)_etext + offset - MODULES_VSIZE;
+	}
+
+	/* use the lower 21 bits to randomize the base of the module region */
+	module_alloc_base += (module_range * (seed & ((1 << 21) - 1))) >> 21;
+	module_alloc_base &= PAGE_MASK;
 
 	return offset;
 }
arch/arm64/kernel/module.c +2 -7
index 54702d456680..dfa1ffaa9844 100644
@@ -33,14 +33,9 @@
 void *module_alloc(unsigned long size)
 {
 	void *p;
-	u64 base = (u64)_etext - MODULES_VSIZE;
 
-	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
-		extern u32 module_load_offset;
-		base += module_load_offset;
-	}
-
-	p = __vmalloc_node_range(size, MODULE_ALIGN, base, base + MODULES_VSIZE,
+	p = __vmalloc_node_range(size, MODULE_ALIGN, module_alloc_base,
+				module_alloc_base + MODULES_VSIZE,
 				GFP_KERNEL, PAGE_KERNEL_EXEC, 0,
 				NUMA_NO_NODE, __builtin_return_address(0));
 
Signature · 1 line
2.5.0
maintainer
#13 lore raw

PATCH 1/1 arm64: allow the module region to be randomized independently

Quoted 6 lines · Ard Biesheuvel

This adds the option to randomize the module region independently from the core kernel, and enables it by default. This makes it less likely that the location of core kernel data structures can be determined by an adversary, but causes all function calls from modules into the core kernel to be resolved via entries in the module PLTs.

Quoted 7 lines
---
 arch/arm64/Kconfig              | 15 ++++++++
 arch/arm64/include/asm/module.h |  6 ++++
 arch/arm64/kernel/kaslr.c       | 36 +++++++++++++++-----
 arch/arm64/kernel/module.c      |  9 ++---
 4 files changed, 50 insertions(+), 16 deletions(-)

With this patch I get an unhandled paging request, coming from kernel/module.c:2982 (the memset). The PC is wrongly attributed but it's in arch/arm64/lib/memset.S:

[    7.140606] Unable to handle kernel paging request at virtual address 00004000
[    7.147794] pgd = ffffffc060171000
[    7.151190] [00004000] *pgd=0000000000000000, *pud=0000000000000000
[    7.157447] Internal error: Oops: 96000045 [#1] PREEMPT SMP
[    7.162962] Modules linked in:
[    7.165995] CPU: 1 PID: 875 Comm: systemd-modules Not tainted 4.5.0-rc1+ #95
[    7.172976] Hardware name: Juno (DT)
[    7.176520] task: ffffffc9760bb000 ti: ffffffc079538000 task.ti: ffffffc079538000
[    7.183939] PC is at __efistub_memset+0x1ac/0x200
[    7.188601] LR is at load_module+0xfc8/0x1df8
[    7.192912] pc : [<ffffff8008336fac>] lr : [<ffffff8008120d88>] pstate: 40000145
[    7.200233] sp : ffffffc07953bd40
[    7.203514] x29: ffffffc07953bd40 x28: 0000000000002361
[    7.208791] x27: ffffff80086bb000 x26: ffffff8008f84aa0
[    7.214054] x25: 0000000000000111 x24: 000000000000006e
[    7.219317] x23: 0000007f7bc01918 x22: ffffff8008f0e100
[    7.224580] x21: ffffff8008f4d2c0 x20: 0000000000004000
[    7.229855] x19: ffffffc07953be70 x18: 0000000000000000
[    7.235127] x17: 0000000000000000 x16: 0000000000000002
[    7.240398] x15: ffffffffffffffff x14: ffffff0000000000
[    7.245667] x13: ffffffbdc3e55340 x12: 0000000000006fff
[    7.250934] x11: ffffffc97fed46a8 x10: 0000000000000010
[    7.256198] x9 : 0000000000000000 x8 : 0000000000004000
[    7.261462] x7 : 0000000000000000 x6 : 000000000000003f
[    7.266823] x5 : 0000000000000040 x4 : 0000000000000000 [    7.271219] systemd-journald[864]: Received request to flush runtime journal from PID 1
[    7.279835]
[    7.281487] x3 : 0000000000000004 x2 : 000000000000229e
[    7.286758] x1 : 0000000000000000 x0 : 0000000000004000
[    7.292019]
[    7.293495] Process systemd-modules (pid: 875, stack limit = 0xffffffc079538020)
[    7.300822] Stack: (0xffffffc07953bd40 to 0xffffffc07953c000)
[    7.306522] bd40: ffffffc07953be40 ffffff8008121de0 0000000000000000 0000000000000005
[    7.314276] bd60: 0000007f7bc01918 0000007f7bb24ad4 0000000080000000 0000000000000015
[    7.322029] bd80: 000000000000011e 0000000000000111 ffffff80086b0000 ffffffc079538000
[    7.329781] bda0: 0000000000000000 0000000000000005 0000007f7bc01918 0000007f7bb24ad4
[    7.337536] bdc0: ffffff8008f0e288 ffffff8008f84ae0 ffffff8008f0e2d8 ffffff8008f0d000
[    7.345288] bde0: ffff81a40000000f 0000000000000001 0000000000000000 0000000000077b20
[    7.353041] be00: 0000000056b8d7f8 00000000134c2b98 0000000056b8d7f8 000000001163e398
[    7.360793] be20: 0000000056b8d7f8 000000001163e398 0000000000001000 00000000000003c0
[    7.368545] be40: 0000000000000000 ffffff8008085d30 0000000000000000 0000000000000000
[    7.376298] be60: ffffffffffffffff 0000005571c2aa60 ffffff8008f0d000 0000000000077b20
[    7.384051] be80: ffffff8008f84120 ffffff8008f4b7af ffffff8008f4d2c0 0000000000001388
[    7.391803] bea0: 0000000000001dd8 0000000000000000 0000000000000000 0000002700000026
[    7.399555] bec0: 0000000000000011 000000000000000b 0000000000000005 0000007f7bc01918
[    7.407307] bee0: 0000000000000000 0000000000000005 0000000000000000 60ceffffffffffff
[    7.415060] bf00: ffffffffffffffff ffffffffffffffff 0000000000000111 0000000000000038
[    7.422812] bf20: 0101010101010101 0000000000000001 0000000000000000 ffffffffffff0000
[    7.430565] bf40: 0000007f7bc43000 0000007f7ba626b8 0000007f7bb24ab0 0000007f7bc132d8
[    7.438317] bf60: 0000005565850710 0000005571c2a8a0 0000000000000000 0000007f7bc01918
[    7.446069] bf80: 0000005571c2a920 0000000000020000 0000000000000000 0000000000000000
[    7.453821] bfa0: 0000005571c29330 0000000000000000 0000000000000000 0000007ff3bc1e80
[    7.461575] bfc0: 0000007f7bbfa1ac 0000007ff3bc1e80 0000007f7bb24ad4 0000000080000000
[    7.469327] bfe0: 0000000000000005 0000000000000111 f712e45f3fdb5baf 5d70fcf3d73b5fa3
[    7.477075] Call trace:
[    7.479494] Exception stack(0xffffffc07953bb80 to 0xffffffc07953bca0)
[    7.485871] bb80: ffffffc07953be70 0000000000004000 ffffffc07953bd40 ffffff8008336fac
[    7.493624] bba0: 0000000000400000 00000000024000c0 ffffffc975853300 00c8000000000713
[    7.501376] bbc0: ffffff80086bb000 0000000000002361 0000000000004000 0000000000000000
[    7.509128] bbe0: ffffffc07953bc60 ffffff80081885d8 ffffffc07953bca0 ffffff8008187fb8
[    7.516880] bc00: 0000000000000003 ffffffc975853480 00000000ffffffff 00000000024002c0
[    7.524631] bc20: 0000000000004000 0000000000000000 000000000000229e 0000000000000004
[    7.532383] bc40: 0000000000000000 0000000000000040 000000000000003f 0000000000000000
[    7.540135] bc60: 0000000000004000 0000000000000000 0000000000000010 ffffffc97fed46a8
[    7.547888] bc80: 0000000000006fff ffffffbdc3e55340 ffffff0000000000 ffffffffffffffff
[    7.555646] [<ffffff8008336fac>] __efistub_memset+0x1ac/0x200
[    7.561334] [<ffffff8008121de0>] SyS_finit_module+0xb0/0xc0
[    7.566852] [<ffffff8008085d30>] el0_svc_naked+0x24/0x28
[    7.572112] Code: 91010108 54ffff4a 8b040108 cb050042 (d50b7428)
[    7.578196] ---[ end trace 13bd770b734da68a ]---
Signature · 1 line
Catalin
#14 lore raw

PATCH 1/1 arm64: allow the module region to be randomized independently

Quoted 15 lines · Catalin Marinas
Quoted 6 lines · Ard Biesheuvel

This adds the option to randomize the module region independently from the core kernel, and enables it by default. This makes it less likely that the location of core kernel data structures can be determined by an adversary, but causes all function calls from modules into the core kernel to be resolved via entries in the module PLTs.

Quoted 7 lines
---
 arch/arm64/Kconfig              | 15 ++++++++
 arch/arm64/include/asm/module.h |  6 ++++
 arch/arm64/kernel/kaslr.c       | 36 +++++++++++++++-----
 arch/arm64/kernel/module.c      |  9 ++---
 4 files changed, 50 insertions(+), 16 deletions(-)
Quoted 4 lines

With this patch I get an unhandled paging request, coming from kernel/module.c:2982 (the memset). The PC is wrongly attributed but it's in arch/arm64/lib/memset.S:

That is quite surprising tbh. The only thing that is supposed to happen here is that the low and high limits of the __vmalloc_node_range() call in module_alloc() are modified. I don't see how a successful __vmalloc_node_range() call should end up here.

I will try to reproduce tomorrow.

Signature · 1 line
Ard.
Quoted 73 lines
[    7.140606] Unable to handle kernel paging request at virtual address 00004000
[    7.147794] pgd = ffffffc060171000
[    7.151190] [00004000] *pgd=0000000000000000, *pud=0000000000000000
[    7.157447] Internal error: Oops: 96000045 [#1] PREEMPT SMP
[    7.162962] Modules linked in:
[    7.165995] CPU: 1 PID: 875 Comm: systemd-modules Not tainted 4.5.0-rc1+ #95
[    7.172976] Hardware name: Juno (DT)
[    7.176520] task: ffffffc9760bb000 ti: ffffffc079538000 task.ti: ffffffc079538000
[    7.183939] PC is at __efistub_memset+0x1ac/0x200
[    7.188601] LR is at load_module+0xfc8/0x1df8
[    7.192912] pc : [<ffffff8008336fac>] lr : [<ffffff8008120d88>] pstate: 40000145
[    7.200233] sp : ffffffc07953bd40
[    7.203514] x29: ffffffc07953bd40 x28: 0000000000002361
[    7.208791] x27: ffffff80086bb000 x26: ffffff8008f84aa0
[    7.214054] x25: 0000000000000111 x24: 000000000000006e
[    7.219317] x23: 0000007f7bc01918 x22: ffffff8008f0e100
[    7.224580] x21: ffffff8008f4d2c0 x20: 0000000000004000
[    7.229855] x19: ffffffc07953be70 x18: 0000000000000000
[    7.235127] x17: 0000000000000000 x16: 0000000000000002
[    7.240398] x15: ffffffffffffffff x14: ffffff0000000000
[    7.245667] x13: ffffffbdc3e55340 x12: 0000000000006fff
[    7.250934] x11: ffffffc97fed46a8 x10: 0000000000000010
[    7.256198] x9 : 0000000000000000 x8 : 0000000000004000
[    7.261462] x7 : 0000000000000000 x6 : 000000000000003f
[    7.266823] x5 : 0000000000000040 x4 : 0000000000000000 [    7.271219] systemd-journald[864]: Received request to flush runtime journal from PID 1
[    7.279835]
[    7.281487] x3 : 0000000000000004 x2 : 000000000000229e
[    7.286758] x1 : 0000000000000000 x0 : 0000000000004000
[    7.292019]
[    7.293495] Process systemd-modules (pid: 875, stack limit = 0xffffffc079538020)
[    7.300822] Stack: (0xffffffc07953bd40 to 0xffffffc07953c000)
[    7.306522] bd40: ffffffc07953be40 ffffff8008121de0 0000000000000000 0000000000000005
[    7.314276] bd60: 0000007f7bc01918 0000007f7bb24ad4 0000000080000000 0000000000000015
[    7.322029] bd80: 000000000000011e 0000000000000111 ffffff80086b0000 ffffffc079538000
[    7.329781] bda0: 0000000000000000 0000000000000005 0000007f7bc01918 0000007f7bb24ad4
[    7.337536] bdc0: ffffff8008f0e288 ffffff8008f84ae0 ffffff8008f0e2d8 ffffff8008f0d000
[    7.345288] bde0: ffff81a40000000f 0000000000000001 0000000000000000 0000000000077b20
[    7.353041] be00: 0000000056b8d7f8 00000000134c2b98 0000000056b8d7f8 000000001163e398
[    7.360793] be20: 0000000056b8d7f8 000000001163e398 0000000000001000 00000000000003c0
[    7.368545] be40: 0000000000000000 ffffff8008085d30 0000000000000000 0000000000000000
[    7.376298] be60: ffffffffffffffff 0000005571c2aa60 ffffff8008f0d000 0000000000077b20
[    7.384051] be80: ffffff8008f84120 ffffff8008f4b7af ffffff8008f4d2c0 0000000000001388
[    7.391803] bea0: 0000000000001dd8 0000000000000000 0000000000000000 0000002700000026
[    7.399555] bec0: 0000000000000011 000000000000000b 0000000000000005 0000007f7bc01918
[    7.407307] bee0: 0000000000000000 0000000000000005 0000000000000000 60ceffffffffffff
[    7.415060] bf00: ffffffffffffffff ffffffffffffffff 0000000000000111 0000000000000038
[    7.422812] bf20: 0101010101010101 0000000000000001 0000000000000000 ffffffffffff0000
[    7.430565] bf40: 0000007f7bc43000 0000007f7ba626b8 0000007f7bb24ab0 0000007f7bc132d8
[    7.438317] bf60: 0000005565850710 0000005571c2a8a0 0000000000000000 0000007f7bc01918
[    7.446069] bf80: 0000005571c2a920 0000000000020000 0000000000000000 0000000000000000
[    7.453821] bfa0: 0000005571c29330 0000000000000000 0000000000000000 0000007ff3bc1e80
[    7.461575] bfc0: 0000007f7bbfa1ac 0000007ff3bc1e80 0000007f7bb24ad4 0000000080000000
[    7.469327] bfe0: 0000000000000005 0000000000000111 f712e45f3fdb5baf 5d70fcf3d73b5fa3
[    7.477075] Call trace:
[    7.479494] Exception stack(0xffffffc07953bb80 to 0xffffffc07953bca0)
[    7.485871] bb80: ffffffc07953be70 0000000000004000 ffffffc07953bd40 ffffff8008336fac
[    7.493624] bba0: 0000000000400000 00000000024000c0 ffffffc975853300 00c8000000000713
[    7.501376] bbc0: ffffff80086bb000 0000000000002361 0000000000004000 0000000000000000
[    7.509128] bbe0: ffffffc07953bc60 ffffff80081885d8 ffffffc07953bca0 ffffff8008187fb8
[    7.516880] bc00: 0000000000000003 ffffffc975853480 00000000ffffffff 00000000024002c0
[    7.524631] bc20: 0000000000004000 0000000000000000 000000000000229e 0000000000000004
[    7.532383] bc40: 0000000000000000 0000000000000040 000000000000003f 0000000000000000
[    7.540135] bc60: 0000000000004000 0000000000000000 0000000000000010 ffffffc97fed46a8
[    7.547888] bc80: 0000000000006fff ffffffbdc3e55340 ffffff0000000000 ffffffffffffffff
[    7.555646] [<ffffff8008336fac>] __efistub_memset+0x1ac/0x200
[    7.561334] [<ffffff8008121de0>] SyS_finit_module+0xb0/0xc0
[    7.566852] [<ffffff8008085d30>] el0_svc_naked+0x24/0x28
[    7.572112] Code: 91010108 54ffff4a 8b040108 cb050042 (d50b7428)
[    7.578196] ---[ end trace 13bd770b734da68a ]---

--
Catalin

#15 lore raw

PATCH 1/1 arm64: allow the module region to be randomized independently

In reply to Catalin Marinas
Quoted 15 lines · Catalin Marinas
Quoted 6 lines · Ard Biesheuvel

This adds the option to randomize the module region independently from the core kernel, and enables it by default. This makes it less likely that the location of core kernel data structures can be determined by an adversary, but causes all function calls from modules into the core kernel to be resolved via entries in the module PLTs.

Quoted 7 lines
---
 arch/arm64/Kconfig              | 15 ++++++++
 arch/arm64/include/asm/module.h |  6 ++++
 arch/arm64/kernel/kaslr.c       | 36 +++++++++++++++-----
 arch/arm64/kernel/module.c      |  9 ++---
 4 files changed, 50 insertions(+), 16 deletions(-)
Quoted 4 lines

With this patch I get an unhandled paging request, coming from kernel/module.c:2982 (the memset). The PC is wrongly attributed but it's in arch/arm64/lib/memset.S:

This was due to a thinko in kaslr_early_init(). Folding the following hunk will fix it (or I can resend the patch if you prefer)

-------8<----------

arch/arm64/kernel/kaslr.c +6 -0
index f8ad30160026..d7eba99ab140 100644
@@ -82,6 +82,12 @@ u64 __init kaslr_early_init(u64 dt_phys)
        int size;
 
        /*
+        * Set a reasonable default for module_alloc_base in case
+        * we end up running with KASLR disabled.
+        */
+       module_alloc_base = (u64)_etext - MODULES_VSIZE;
+
+       /*
         * Try to map the FDT early. If this fails, we simply bail,
         * and proceed with KASLR disabled. We will make another
         * attempt@mapping the FDT in setup_machine()

-------8<----------

maintainer
#16 lore raw

PATCH 1/1 arm64: allow the module region to be randomized independently

Quoted 33 lines · Ard Biesheuvel
Quoted 15 lines · Catalin Marinas
Quoted 6 lines · Ard Biesheuvel

This adds the option to randomize the module region independently from the core kernel, and enables it by default. This makes it less likely that the location of core kernel data structures can be determined by an adversary, but causes all function calls from modules into the core kernel to be resolved via entries in the module PLTs.

Quoted 7 lines
---
 arch/arm64/Kconfig              | 15 ++++++++
 arch/arm64/include/asm/module.h |  6 ++++
 arch/arm64/kernel/kaslr.c       | 36 +++++++++++++++-----
 arch/arm64/kernel/module.c      |  9 ++---
 4 files changed, 50 insertions(+), 16 deletions(-)
Quoted 4 lines

With this patch I get an unhandled paging request, coming from kernel/module.c:2982 (the memset). The PC is wrongly attributed but it's in arch/arm64/lib/memset.S:

This was due to a thinko in kaslr_early_init(). Folding the following hunk will fix it (or I can resend the patch if you prefer)

-------8<----------

arch/arm64/kernel/kaslr.c +0 -0
index f8ad30160026..d7eba99ab140 100644
@@ -82,6 +82,12 @@ u64 __init kaslr_early_init(u64 dt_phys)
        int size;
 
        /*

arch/arm64/kernel/kaslr.c u64 __init kaslr_early_init(u64 dt_phys)

Quoted 10 lines
arch/arm64/kernel/kaslr.c +6 -1
+        * Set a reasonable default for module_alloc_base in case
+        * we end up running with KASLR disabled.
+        */
+       module_alloc_base = (u64)_etext - MODULES_VSIZE;
+
+       /*
         * Try to map the FDT early. If this fails, we simply bail,
         * and proceed with KASLR disabled. We will make another
         * attempt at mapping the FDT in setup_machine()
-------8<----------

I'll fold the hunk in and give it a try. Thanks.

Signature · 1 line
Catalin
#17 lore raw

PATCH 1/8 arm64: add support for module PLTs

In reply to Ard Biesheuvel

Hi Ard,

Quoted 40 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

---

In this version, I removed the distinction between relocations against .init executable sections and ordinary executable sections. The reason is that it is hardly worth the trouble, given that .init.text usually does not contain that many far branches, and this version now only reserves PLT entry space for jump and call relocations against undefined symbols (since symbols defined in the same module can be assumed to be within +/- 128 MB)

For example, the mac80211.ko module (which is fairly sizable at ~400 KB) built with -mcmodel=large gives the following relocation counts:

relocs branches unique !local
.text 3925 3347 518 219

  .init.text           11          8         7          1
  .exit.text            4          4         4          1

.text.unlikely 81 67 36 17

('unique' means branches to unique type/symbol/addend combos, of which !local is the subset referring to undefined symbols)

IOW, we are only emitting a single PLT entry for the .init sections, and we are better off just adding it to the core PLT section instead.

---
 arch/arm64/Kconfig              |   9 +
 arch/arm64/Makefile             |   6 +-
 arch/arm64/include/asm/module.h |  11 ++
 arch/arm64/kernel/Makefile      |   1 +
 arch/arm64/kernel/module-plts.c | 201 ++++++++++++++++++++
 arch/arm64/kernel/module.c      |  12 ++
 arch/arm64/kernel/module.lds    |   3 +
 7 files changed, 242 insertions(+), 1 deletion(-)

[...]

Quoted 4 lines
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+	/*
+	 * A program that conforms to the AArch64 Procedure Call Standard
+	 * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+	 * IP1 (x17) may be inserted at any branch instruction that is
+	 * exposed to a relocation that supports long branches. Since that
+	 * is exactly what we are dealing with here, we are free to use x16
+	 * as a scratch register in the PLT veneers.
+	 */
+	__le32	mov0;	/* movn	x16, #0x....			*/
+	__le32	mov1;	/* movk	x16, #0x...., lsl #16		*/
+	__le32	mov2;	/* movk	x16, #0x...., lsl #32		*/
+	__le32	br;	/* br	x16				*/
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

The out-of-line code is compiled with magic GCC options to force the explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

Will

#18 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 65 lines · Will Deacon

Hi Ard,

Quoted 40 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

---

In this version, I removed the distinction between relocations against .init executable sections and ordinary executable sections. The reason is that it is hardly worth the trouble, given that .init.text usually does not contain that many far branches, and this version now only reserves PLT entry space for jump and call relocations against undefined symbols (since symbols defined in the same module can be assumed to be within +/- 128 MB)

For example, the mac80211.ko module (which is fairly sizable at ~400 KB) built with -mcmodel=large gives the following relocation counts:

relocs branches unique !local
.text 3925 3347 518 219

  .init.text           11          8         7          1
  .exit.text            4          4         4          1

.text.unlikely 81 67 36 17

('unique' means branches to unique type/symbol/addend combos, of which !local is the subset referring to undefined symbols)

IOW, we are only emitting a single PLT entry for the .init sections, and we are better off just adding it to the core PLT section instead.

---
 arch/arm64/Kconfig              |   9 +
 arch/arm64/Makefile             |   6 +-
 arch/arm64/include/asm/module.h |  11 ++
 arch/arm64/kernel/Makefile      |   1 +
 arch/arm64/kernel/module-plts.c | 201 ++++++++++++++++++++
 arch/arm64/kernel/module.c      |  12 ++
 arch/arm64/kernel/module.lds    |   3 +
 7 files changed, 242 insertions(+), 1 deletion(-)

[...]

Quoted 4 lines
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+     /*
+      * A program that conforms to the AArch64 Procedure Call Standard
+      * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+      * IP1 (x17) may be inserted at any branch instruction that is
+      * exposed to a relocation that supports long branches. Since that
+      * is exactly what we are dealing with here, we are free to use x16
+      * as a scratch register in the PLT veneers.
+      */
+     __le32  mov0;   /* movn x16, #0x....                    */
+     __le32  mov1;   /* movk x16, #0x...., lsl #16           */
+     __le32  mov2;   /* movk x16, #0x...., lsl #32           */
+     __le32  br;     /* br   x16                             */
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

Quoted 1 line

The out-of-line code is compiled with magic GCC options

Which options are those exactly?

Quoted 9 lines

to force the
explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

If you violate the AAPCS64 ABI, then obviously the claim above does not hold.

#19 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 76 lines · Ard Biesheuvel
Quoted 65 lines · Will Deacon

Hi Ard,

Quoted 40 lines · Ard Biesheuvel

This adds support for emitting PLTs at module load time for relative branches that are out of range. This is a prerequisite for KASLR, which may place the kernel and the modules anywhere in the vmalloc area, making it more likely that branch target offsets exceed the maximum range of +/- 128 MB.

---

In this version, I removed the distinction between relocations against .init executable sections and ordinary executable sections. The reason is that it is hardly worth the trouble, given that .init.text usually does not contain that many far branches, and this version now only reserves PLT entry space for jump and call relocations against undefined symbols (since symbols defined in the same module can be assumed to be within +/- 128 MB)

For example, the mac80211.ko module (which is fairly sizable at ~400 KB) built with -mcmodel=large gives the following relocation counts:

relocs branches unique !local
.text 3925 3347 518 219

  .init.text           11          8         7          1
  .exit.text            4          4         4          1

.text.unlikely 81 67 36 17

('unique' means branches to unique type/symbol/addend combos, of which !local is the subset referring to undefined symbols)

IOW, we are only emitting a single PLT entry for the .init sections, and we are better off just adding it to the core PLT section instead.

---
 arch/arm64/Kconfig              |   9 +
 arch/arm64/Makefile             |   6 +-
 arch/arm64/include/asm/module.h |  11 ++
 arch/arm64/kernel/Makefile      |   1 +
 arch/arm64/kernel/module-plts.c | 201 ++++++++++++++++++++
 arch/arm64/kernel/module.c      |  12 ++
 arch/arm64/kernel/module.lds    |   3 +
 7 files changed, 242 insertions(+), 1 deletion(-)

[...]

Quoted 4 lines
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+     /*
+      * A program that conforms to the AArch64 Procedure Call Standard
+      * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+      * IP1 (x17) may be inserted at any branch instruction that is
+      * exposed to a relocation that supports long branches. Since that
+      * is exactly what we are dealing with here, we are free to use x16
+      * as a scratch register in the PLT veneers.
+      */
+     __le32  mov0;   /* movn x16, #0x....                    */
+     __le32  mov1;   /* movk x16, #0x...., lsl #16           */
+     __le32  mov2;   /* movk x16, #0x...., lsl #32           */
+     __le32  br;     /* br   x16                             */
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

Quoted 1 line

The out-of-line code is compiled with magic GCC options

Which options are those exactly?

Quoted 6 lines

to force the
explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

Quoted 5 lines
Quoted 3 lines

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

If you violate the AAPCS64 ABI, then obviously the claim above does not hold.

IOW, yes, x16 will be corrupted unexpectedly if it you are expecting it to be preserved across a bl or b instruction that is exposed to a jump/call relocation

#20 lore raw

PATCH 1/8 arm64: add support for module PLTs

In reply to Ard Biesheuvel
Quoted 21 lines · Ard Biesheuvel
Quoted 20 lines · Will Deacon
Quoted 4 lines · Ard Biesheuvel
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+     /*
+      * A program that conforms to the AArch64 Procedure Call Standard
+      * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+      * IP1 (x17) may be inserted at any branch instruction that is
+      * exposed to a relocation that supports long branches. Since that
+      * is exactly what we are dealing with here, we are free to use x16
+      * as a scratch register in the PLT veneers.
+      */
+     __le32  mov0;   /* movn x16, #0x....                    */
+     __le32  mov1;   /* movk x16, #0x...., lsl #16           */
+     __le32  mov2;   /* movk x16, #0x...., lsl #32           */
+     __le32  br;     /* br   x16                             */
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

Quoted 3 lines
Quoted 1 line

The out-of-line code is compiled with magic GCC options

Which options are those exactly?

# Tell the compiler to treat all general purpose registers as # callee-saved, which allows for efficient runtime patching of the bl

# instruction in the caller with an atomic instruction when supported by
# the CPU. Result and argument registers are handled correctly, based on
# the function prototype.
lib-$(CONFIG_ARM64_LSE_ATOMICS) += atomic_ll_sc.o
CFLAGS_atomic_ll_sc.o	:= -fcall-used-x0 -ffixed-x1 -ffixed-x2		\
		   -ffixed-x3 -ffixed-x4 -ffixed-x5 -ffixed-x6		\
		   -ffixed-x7 -fcall-saved-x8 -fcall-saved-x9		\
		   -fcall-saved-x10 -fcall-saved-x11 -fcall-saved-x12	\
		   -fcall-saved-x13 -fcall-saved-x14 -fcall-saved-x15	\
		   -fcall-saved-x16 -fcall-saved-x17 -fcall-saved-x18
Quoted 11 lines
Quoted 9 lines

to force the
explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

If you violate the AAPCS64 ABI, then obviously the claim above does not hold.

Indeed, but this is what mainline is doing today and I'm not keen on breaking it. One way to fix it would be to generate a different type of plt for branches to the atomic functions that would use the stack instead of x16.

Will

#21 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 28 lines · Will Deacon
Quoted 21 lines · Ard Biesheuvel
Quoted 20 lines · Will Deacon
Quoted 4 lines · Ard Biesheuvel
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+     /*
+      * A program that conforms to the AArch64 Procedure Call Standard
+      * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+      * IP1 (x17) may be inserted at any branch instruction that is
+      * exposed to a relocation that supports long branches. Since that
+      * is exactly what we are dealing with here, we are free to use x16
+      * as a scratch register in the PLT veneers.
+      */
+     __le32  mov0;   /* movn x16, #0x....                    */
+     __le32  mov1;   /* movk x16, #0x...., lsl #16           */
+     __le32  mov2;   /* movk x16, #0x...., lsl #32           */
+     __le32  br;     /* br   x16                             */
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

Quoted 3 lines
Quoted 1 line

The out-of-line code is compiled with magic GCC options

Which options are those exactly?

# Tell the compiler to treat all general purpose registers as # callee-saved, which allows for efficient runtime patching of the bl

Quoted 11 lines
# instruction in the caller with an atomic instruction when supported by
# the CPU. Result and argument registers are handled correctly, based on
# the function prototype.
lib-$(CONFIG_ARM64_LSE_ATOMICS) += atomic_ll_sc.o
CFLAGS_atomic_ll_sc.o   := -fcall-used-x0 -ffixed-x1 -ffixed-x2         \
                   -ffixed-x3 -ffixed-x4 -ffixed-x5 -ffixed-x6          \
                   -ffixed-x7 -fcall-saved-x8 -fcall-saved-x9           \
                   -fcall-saved-x10 -fcall-saved-x11 -fcall-saved-x12   \
                   -fcall-saved-x13 -fcall-saved-x14 -fcall-saved-x15   \
                   -fcall-saved-x16 -fcall-saved-x17 -fcall-saved-x18

Yikes. Is that safe?

Quoted 10 lines
Quoted 10 lines
Quoted 9 lines

to force the
explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

Quoted 7 lines
Quoted 1 line

If you violate the AAPCS64 ABI, then obviously the claim above does not hold.

Indeed, but this is what mainline is doing today and I'm not keen on breaking it. One way to fix it would be to generate a different type of plt for branches to the atomic functions that would use the stack instead of x16.

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

#22 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 41 lines · Ard Biesheuvel
Quoted 28 lines · Will Deacon
Quoted 21 lines · Ard Biesheuvel
Quoted 20 lines · Will Deacon
Quoted 4 lines · Ard Biesheuvel
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+     /*
+      * A program that conforms to the AArch64 Procedure Call Standard
+      * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+      * IP1 (x17) may be inserted at any branch instruction that is
+      * exposed to a relocation that supports long branches. Since that
+      * is exactly what we are dealing with here, we are free to use x16
+      * as a scratch register in the PLT veneers.
+      */
+     __le32  mov0;   /* movn x16, #0x....                    */
+     __le32  mov1;   /* movk x16, #0x...., lsl #16           */
+     __le32  mov2;   /* movk x16, #0x...., lsl #32           */
+     __le32  br;     /* br   x16                             */
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

Quoted 3 lines
Quoted 1 line

The out-of-line code is compiled with magic GCC options

Which options are those exactly?

# Tell the compiler to treat all general purpose registers as # callee-saved, which allows for efficient runtime patching of the bl

Quoted 11 lines
# instruction in the caller with an atomic instruction when supported by
# the CPU. Result and argument registers are handled correctly, based on
# the function prototype.
lib-$(CONFIG_ARM64_LSE_ATOMICS) += atomic_ll_sc.o
CFLAGS_atomic_ll_sc.o   := -fcall-used-x0 -ffixed-x1 -ffixed-x2         \
                   -ffixed-x3 -ffixed-x4 -ffixed-x5 -ffixed-x6          \
                   -ffixed-x7 -fcall-saved-x8 -fcall-saved-x9           \
                   -fcall-saved-x10 -fcall-saved-x11 -fcall-saved-x12   \
                   -fcall-saved-x13 -fcall-saved-x14 -fcall-saved-x15   \
                   -fcall-saved-x16 -fcall-saved-x17 -fcall-saved-x18
Quoted 1 line

Yikes. Is that safe?

It seems to work, and x86 uses a similar trick for it's hweight code.

Quoted 18 lines
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines

to force the
explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

Quoted 7 lines
Quoted 1 line

If you violate the AAPCS64 ABI, then obviously the claim above does not hold.

Indeed, but this is what mainline is doing today and I'm not keen on breaking it. One way to fix it would be to generate a different type of plt for branches to the atomic functions that would use the stack instead of x16.

Quoted 3 lines

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

I'll try hacking something together...

Will

#23 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 68 lines · Will Deacon
Quoted 41 lines · Ard Biesheuvel
Quoted 28 lines · Will Deacon
Quoted 21 lines · Ard Biesheuvel
Quoted 20 lines · Will Deacon
Quoted 4 lines · Ard Biesheuvel
arch/arm64/kernel/module-plts.c new file +4 -0
+struct plt_entry {
+     /*
+      * A program that conforms to the AArch64 Procedure Call Standard
+      * (AAPCS64) must assume that a veneer that alters IP0 (x16) and/or

arch/arm64/kernel/module-plts.c line 27

Quoted 10 lines
arch/arm64/kernel/module-plts.c new file +10 -0
+      * IP1 (x17) may be inserted at any branch instruction that is
+      * exposed to a relocation that supports long branches. Since that
+      * is exactly what we are dealing with here, we are free to use x16
+      * as a scratch register in the PLT veneers.
+      */
+     __le32  mov0;   /* movn x16, #0x....                    */
+     __le32  mov1;   /* movk x16, #0x...., lsl #16           */
+     __le32  mov2;   /* movk x16, #0x...., lsl #32           */
+     __le32  br;     /* br   x16                             */
+};

I'm worried about this code when CONFIG_ARM64_LSE_ATOMICS=y, but we don't detect them on the CPU at runtime. In this case, all atomic operations are moved out-of-line and called using a bl instruction from inline asm.

Quoted 3 lines
Quoted 1 line

The out-of-line code is compiled with magic GCC options

Which options are those exactly?

# Tell the compiler to treat all general purpose registers as # callee-saved, which allows for efficient runtime patching of the bl

Quoted 11 lines
# instruction in the caller with an atomic instruction when supported by
# the CPU. Result and argument registers are handled correctly, based on
# the function prototype.
lib-$(CONFIG_ARM64_LSE_ATOMICS) += atomic_ll_sc.o
CFLAGS_atomic_ll_sc.o   := -fcall-used-x0 -ffixed-x1 -ffixed-x2         \
                   -ffixed-x3 -ffixed-x4 -ffixed-x5 -ffixed-x6          \
                   -ffixed-x7 -fcall-saved-x8 -fcall-saved-x9           \
                   -fcall-saved-x10 -fcall-saved-x11 -fcall-saved-x12   \
                   -fcall-saved-x13 -fcall-saved-x14 -fcall-saved-x15   \
                   -fcall-saved-x16 -fcall-saved-x17 -fcall-saved-x18
Quoted 1 line

Yikes. Is that safe?

It seems to work, and x86 uses a similar trick for it's hweight code.

Quoted 18 lines
Quoted 10 lines
Quoted 10 lines
Quoted 9 lines

to force the
explicit save/restore of all used registers (see arch/arm64/lib/Makefile), otherwise we'd have to clutter the inline asm with constraints that wouldn't be needed had we managed to patch the bl with an LSE atomic instruction.

If you're emitting a PLT, couldn't we end up with silent corruption of x16 for modules using out-of-line atomics like this?

Quoted 7 lines
Quoted 1 line

If you violate the AAPCS64 ABI, then obviously the claim above does not hold.

Indeed, but this is what mainline is doing today and I'm not keen on breaking it. One way to fix it would be to generate a different type of plt for branches to the atomic functions that would use the stack instead of x16.

Quoted 3 lines

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

Quoted 6 lines

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

I'll try hacking something together...

That would be more correct, indeed. Also, the PLT can only use br <reg> so it will always enter the callee with the preserved value stacked, and so the callee needs to be modified in any case.

#24 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 10 lines · Ard Biesheuvel
Quoted 9 lines · Will Deacon
Quoted 3 lines · Ard Biesheuvel

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

Quoted 6 lines
Quoted 2 lines

I'll try hacking something together...

That would be more correct, indeed. Also, the PLT can only use br <reg> so it will always enter the callee with the preserved value stacked, and so the callee needs to be modified in any case.

Wait -- why does the callee need to be modified here? It has no idea about whether or not it was invoked via a PLT.

Also, can I rely on the PLT not clobbering the condition code?

Will

#25 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 14 lines · Will Deacon
Quoted 13 lines · Ard Biesheuvel
Quoted 11 lines · Will Deacon
Quoted 3 lines · Ard Biesheuvel

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

I'll try hacking something together...

Quoted 7 lines
Quoted 3 lines

That would be more correct, indeed. Also, the PLT can only use br <reg> so it will always enter the callee with the preserved value stacked, and so the callee needs to be modified in any case.

Wait -- why does the callee need to be modified here? It has no idea about whether or not it was invoked via a PLT.

No, it doesn't, and that is exactly the problem: the PLT must end in a br <reg> instruction because that is the only branch instruction with unlimited range. That means you will always enter the callee with its address in some register rather than the value that should have been preserved. I don't see a way to manage that from the callee

Quoted 2 lines

Also, can I rely on the PLT not clobbering the condition code?

For my particular implementation, yes.

#26 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 23 lines · Ard Biesheuvel
Quoted 14 lines · Will Deacon
Quoted 13 lines · Ard Biesheuvel
Quoted 11 lines · Will Deacon
Quoted 3 lines · Ard Biesheuvel

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

I'll try hacking something together...

Quoted 7 lines
Quoted 3 lines

That would be more correct, indeed. Also, the PLT can only use br <reg> so it will always enter the callee with the preserved value stacked, and so the callee needs to be modified in any case.

Wait -- why does the callee need to be modified here? It has no idea about whether or not it was invoked via a PLT.

Quoted 6 lines

No, it doesn't, and that is exactly the problem: the PLT must end in a br <reg> instruction because that is the only branch instruction with unlimited range. That means you will always enter the callee with its address in some register rather than the value that should have been preserved. I don't see a way to manage that from the callee

*caller! dammit

Quoted 4 lines
Quoted 2 lines

Also, can I rely on the PLT not clobbering the condition code?

For my particular implementation, yes.

#27 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 24 lines · Ard Biesheuvel
Quoted 23 lines · Ard Biesheuvel
Quoted 21 lines · Will Deacon
Quoted 10 lines · Ard Biesheuvel
Quoted 9 lines · Will Deacon
Quoted 3 lines · Ard Biesheuvel

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

Quoted 6 lines
Quoted 2 lines

I'll try hacking something together...

That would be more correct, indeed. Also, the PLT can only use br <reg> so it will always enter the callee with the preserved value stacked, and so the callee needs to be modified in any case.

Wait -- why does the callee need to be modified here? It has no idea about whether or not it was invoked via a PLT.

Quoted 8 lines
Quoted 6 lines

No, it doesn't, and that is exactly the problem: the PLT must end in a br <reg> instruction because that is the only branch instruction with unlimited range. That means you will always enter the callee with its address in some register rather than the value that should have been preserved. I don't see a way to manage that from the callee

*caller! dammit

The caller can do:

save x16, 17
bl plt
restore x16, x17

the plt will do:

get_addr_of_callee_into_x16_and_clobber_x17_or_something br callee

then the callee will be compiled with all those weird options, but *not* the ones specifying x16 and x17. That means it can happily use those guys as scratch, because the caller will take care of them.

Will

#28 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 32 lines · Will Deacon
Quoted 31 lines · Ard Biesheuvel
Quoted 29 lines · Ard Biesheuvel
Quoted 14 lines · Will Deacon
Quoted 13 lines · Ard Biesheuvel
Quoted 11 lines · Will Deacon
Quoted 3 lines · Ard Biesheuvel

AFAIK, gcc never uses x18 (the platform register) so we may be able to use that instead. We'd need confirmation from the toolchain guys, though ...

In fact, a better thing to do is probably for the atomic code to save/restore those register explicitly and then remove them from the cflags above.

I'll try hacking something together...

Quoted 7 lines
Quoted 3 lines

That would be more correct, indeed. Also, the PLT can only use br <reg> so it will always enter the callee with the preserved value stacked, and so the callee needs to be modified in any case.

Wait -- why does the callee need to be modified here? It has no idea about whether or not it was invoked via a PLT.

No, it doesn't, and that is exactly the problem: the PLT must end in a br <reg> instruction because that is the only branch instruction with unlimited range. That means you will always enter the callee with its address in some register rather than the value that should have been preserved. I don't see a way to manage that from the callee

Quoted 8 lines
Quoted 1 line

*caller! dammit

The caller can do:

save x16, 17
bl plt
restore x16, x17

This is __LL_SC_CALL, right? So you just fold a stp/ldp in there?

Quoted 9 lines

the plt will do:

get_addr_of_callee_into_x16_and_clobber_x17_or_something br callee

then the callee will be compiled with all those weird options, but *not* the ones specifying x16 and x17. That means it can happily use those guys as scratch, because the caller will take care of them.

Yes, that makes sense. But if you don't relax that restriction, you only need the alternative __LL_SC_CALL for modules.

#29 lore raw

PATCH 1/8 arm64: add support for module PLTs

Quoted 6 lines · Ard Biesheuvel
Quoted 5 lines · Will Deacon

the plt will do:

get_addr_of_callee_into_x16_and_clobber_x17_or_something br callee

Quoted 7 lines
Quoted 4 lines

then the callee will be compiled with all those weird options, but *not* the ones specifying x16 and x17. That means it can happily use those guys as scratch, because the caller will take care of them.

Yes, that makes sense. But if you don't relax that restriction, you only need the alternative __LL_SC_CALL for modules.

Totally untested patch below, but I'll give it a whirl tomorrow.

It looks a bit mental.

Will

--->8

arch/arm64/include/asm/atomic_lse.h +25 -19
index 197e06afbf71..5b595b32ab40 100644
@@ -33,6 +33,8 @@ static inline void atomic_andnot(int i, atomic_t *v)
 	register atomic_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(andnot),
+	"	nop\n"
+	"	nop\n"
 	"	stclr	%w[i], %[v]\n")
 	: [i] "+r" (w0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -45,6 +47,8 @@ static inline void atomic_or(int i, atomic_t *v)
 	register atomic_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(or),
+	"	nop\n"
+	"	nop\n"
 	"	stset	%w[i], %[v]\n")
 	: [i] "+r" (w0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -57,6 +61,8 @@ static inline void atomic_xor(int i, atomic_t *v)
 	register atomic_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(xor),
+	"	nop\n"
+	"	nop\n"
 	"	steor	%w[i], %[v]\n")
 	: [i] "+r" (w0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -69,6 +75,8 @@ static inline void atomic_add(int i, atomic_t *v)
 	register atomic_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC(add),
+	"	nop\n"
+	"	nop\n"
 	"	stadd	%w[i], %[v]\n")
 	: [i] "+r" (w0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -83,9 +91,9 @@ static inline int atomic_add_return##name(int i, atomic_t *v)		\
 									\
 	asm volatile(ARM64_LSE_ATOMIC_INSN(				\
 	/* LL/SC */							\
-	"	nop\n"							\
 	__LL_SC_ATOMIC(add_return##name),				\
 	/* LSE atomics */						\
+	"	nop\n"							\
 	"	ldadd" #mb "	%w[i], w30, %[v]\n"			\
 	"	add	%w[i], %w[i], w30")				\
 	: [i] "+r" (w0), [v] "+Q" (v->counter)				\
@@ -109,9 +117,9 @@ static inline void atomic_and(int i, atomic_t *v)
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(
 	/* LL/SC */
-	"	nop\n"
 	__LL_SC_ATOMIC(and),
 	/* LSE atomics */
+	"	nop\n"
 	"	mvn	%w[i], %w[i]\n"
 	"	stclr	%w[i], %[v]")
 	: [i] "+r" (w0), [v] "+Q" (v->counter)
@@ -126,9 +134,9 @@ static inline void atomic_sub(int i, atomic_t *v)
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(
 	/* LL/SC */
-	"	nop\n"
 	__LL_SC_ATOMIC(sub),
 	/* LSE atomics */
+	"	nop\n"
 	"	neg	%w[i], %w[i]\n"
 	"	stadd	%w[i], %[v]")
 	: [i] "+r" (w0), [v] "+Q" (v->counter)
@@ -144,9 +152,7 @@ static inline int atomic_sub_return##name(int i, atomic_t *v)		\
 									\
 	asm volatile(ARM64_LSE_ATOMIC_INSN(				\
 	/* LL/SC */							\
-	"	nop\n"							\
-	__LL_SC_ATOMIC(sub_return##name)				\
-	"	nop",							\
+	__LL_SC_ATOMIC(sub_return##name),				\
 	/* LSE atomics */						\
 	"	neg	%w[i], %w[i]\n"					\
 	"	ldadd" #mb "	%w[i], w30, %[v]\n"			\
@@ -174,6 +180,8 @@ static inline void atomic64_andnot(long i, atomic64_t *v)
 	register atomic64_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC64(andnot),
+	"	nop\n"
+	"	nop\n"
 	"	stclr	%[i], %[v]\n")
 	: [i] "+r" (x0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -186,6 +194,8 @@ static inline void atomic64_or(long i, atomic64_t *v)
 	register atomic64_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC64(or),
+	"	nop\n"
+	"	nop\n"
 	"	stset	%[i], %[v]\n")
 	: [i] "+r" (x0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -198,6 +208,8 @@ static inline void atomic64_xor(long i, atomic64_t *v)
 	register atomic64_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC64(xor),
+	"	nop\n"
+	"	nop\n"
 	"	steor	%[i], %[v]\n")
 	: [i] "+r" (x0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -210,6 +222,8 @@ static inline void atomic64_add(long i, atomic64_t *v)
 	register atomic64_t *x1 asm ("x1") = v;
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(__LL_SC_ATOMIC64(add),
+	"	nop\n"
+	"	nop\n"
 	"	stadd	%[i], %[v]\n")
 	: [i] "+r" (x0), [v] "+Q" (v->counter)
 	: "r" (x1)
@@ -224,9 +238,9 @@ static inline long atomic64_add_return##name(long i, atomic64_t *v)	\
 									\
 	asm volatile(ARM64_LSE_ATOMIC_INSN(				\
 	/* LL/SC */							\
-	"	nop\n"							\
 	__LL_SC_ATOMIC64(add_return##name),				\
 	/* LSE atomics */						\
+	"	nop\n"							\
 	"	ldadd" #mb "	%[i], x30, %[v]\n"			\
 	"	add	%[i], %[i], x30")				\
 	: [i] "+r" (x0), [v] "+Q" (v->counter)				\
@@ -250,9 +264,9 @@ static inline void atomic64_and(long i, atomic64_t *v)
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(
 	/* LL/SC */
-	"	nop\n"
 	__LL_SC_ATOMIC64(and),
 	/* LSE atomics */
+	"	nop\n"
 	"	mvn	%[i], %[i]\n"
 	"	stclr	%[i], %[v]")
 	: [i] "+r" (x0), [v] "+Q" (v->counter)
@@ -267,9 +281,9 @@ static inline void atomic64_sub(long i, atomic64_t *v)
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(
 	/* LL/SC */
-	"	nop\n"
 	__LL_SC_ATOMIC64(sub),
 	/* LSE atomics */
+	"	nop\n"
 	"	neg	%[i], %[i]\n"
 	"	stadd	%[i], %[v]")
 	: [i] "+r" (x0), [v] "+Q" (v->counter)
@@ -285,9 +299,7 @@ static inline long atomic64_sub_return##name(long i, atomic64_t *v)	\
 									\
 	asm volatile(ARM64_LSE_ATOMIC_INSN(				\
 	/* LL/SC */							\
-	"	nop\n"							\
-	__LL_SC_ATOMIC64(sub_return##name)				\
-	"	nop",							\
+	__LL_SC_ATOMIC64(sub_return##name),				\
 	/* LSE atomics */						\
 	"	neg	%[i], %[i]\n"					\
 	"	ldadd" #mb "	%[i], x30, %[v]\n"			\
@@ -312,12 +324,10 @@ static inline long atomic64_dec_if_positive(atomic64_t *v)
 
 	asm volatile(ARM64_LSE_ATOMIC_INSN(
 	/* LL/SC */
-	"	nop\n"
 	__LL_SC_ATOMIC64(dec_if_positive)
 	"	nop\n"
 	"	nop\n"
 	"	nop\n"
-	"	nop\n"
 	"	nop",
 	/* LSE atomics */
 	"1:	ldr	x30, %[v]\n"
@@ -350,9 +360,7 @@ static inline unsigned long __cmpxchg_case_##name(volatile void *ptr,	\
 									\
 	asm volatile(ARM64_LSE_ATOMIC_INSN(				\
 	/* LL/SC */							\
-	"	nop\n"							\
-		__LL_SC_CMPXCHG(name)					\
-	"	nop",							\
+		__LL_SC_CMPXCHG(name),					\
 	/* LSE atomics */						\
 	"	mov	" #w "30, %" #w "[old]\n"			\
 	"	cas" #mb #sz "\t" #w "30, %" #w "[new], %[v]\n"		\
@@ -404,8 +412,6 @@ static inline long __cmpxchg_double##name(unsigned long old1,		\
 	asm volatile(ARM64_LSE_ATOMIC_INSN(				\
 	/* LL/SC */							\
 	"	nop\n"							\
-	"	nop\n"							\
-	"	nop\n"							\
 	__LL_SC_CMPXCHG_DBL(name),					\
 	/* LSE atomics */						\
 	"	casp" #mb "\t%[old1], %[old2], %[new1], %[new2], %[v]\n"\
arch/arm64/include/asm/lse.h +4 -1
index 3de42d68611d..8252dc6b3046 100644
@@ -25,7 +25,10 @@ __asm__(".arch_extension	lse");
 #define __LL_SC_EXPORT(x)	EXPORT_SYMBOL(__LL_SC_PREFIX(x))
 
 /* Macro for constructing calls to out-of-line ll/sc atomics */
-#define __LL_SC_CALL(op)	"bl\t" __stringify(__LL_SC_PREFIX(op)) "\n"
+#define __LL_SC_CALL(op)						\
+	"stp	x16, x17, [sp, #-16]!\n"				\
+	"bl\t" __stringify(__LL_SC_PREFIX(op)) "\n"			\
+	"ldp	x16, x17, [sp], #16\n"
 
 /* In-line patching at runtime */
 #define ARM64_LSE_ATOMIC_INSN(llsc, lse)				\
arch/arm64/lib/Makefile +7 -6
index 1a811ecf71da..c86b7909ef31 100644
@@ -4,15 +4,16 @@ lib-y		:= bitops.o clear_user.o delay.o copy_from_user.o	\
 		   memcmp.o strcmp.o strncmp.o strlen.o strnlen.o	\
 		   strchr.o strrchr.o
 
-# Tell the compiler to treat all general purpose registers as
-# callee-saved, which allows for efficient runtime patching of the bl
-# instruction in the caller with an atomic instruction when supported by
-# the CPU. Result and argument registers are handled correctly, based on
-# the function prototype.
+# Tell the compiler to treat all general purpose registers (with the
+# exception of the IP registers, which are already handled by the caller
+# in case of a PLT) as callee-saved, which allows for efficient runtime
+# patching of the bl instruction in the caller with an atomic instruction
+# when supported by the CPU. Result and argument registers are handled
+# correctly, based on the function prototype.
 lib-$(CONFIG_ARM64_LSE_ATOMICS) += atomic_ll_sc.o
 CFLAGS_atomic_ll_sc.o	:= -fcall-used-x0 -ffixed-x1 -ffixed-x2		\
 		   -ffixed-x3 -ffixed-x4 -ffixed-x5 -ffixed-x6		\
 		   -ffixed-x7 -fcall-saved-x8 -fcall-saved-x9		\
 		   -fcall-saved-x10 -fcall-saved-x11 -fcall-saved-x12	\
 		   -fcall-saved-x13 -fcall-saved-x14 -fcall-saved-x15	\
-		   -fcall-saved-x16 -fcall-saved-x17 -fcall-saved-x18
+		   -fcall-saved-x18
#30 lore raw

PATCH 2/8 arm64: avoid R_AARCH64_ABS64 relocations for Image header fields

In reply to Ard Biesheuvel

Unfortunately, the current way of using the linker to emit build time constants into the Image header will no longer work once we switch to the use of PIE executables. The reason is that such constants are emitted into the binary using R_AARCH64_ABS64 relocations, which are resolved at runtime, not at build time, and the places targeted by those relocations will contain zeroes before that.

So refactor the endian swapping linker script constant generation code so that it emits the upper and lower 32-bit words separately.

---
 arch/arm64/include/asm/assembler.h | 11 +++++++
 arch/arm64/kernel/head.S           |  6 ++--
 arch/arm64/kernel/image.h          | 32 ++++++++++++--------
 3 files changed, 33 insertions(+), 16 deletions(-)
arch/arm64/include/asm/assembler.h +11 -0
index d8bfcc1ce923..70f7b9e04598 100644
@@ -222,4 +222,15 @@ lr	.req	x30		// link register
 	.size	__pi_##x, . - x;	\
 	ENDPROC(x)
 
+	/*
+	 * Emit a 64-bit absolute little endian symbol reference in a way that
+	 * ensures that it will be resolved at build time, even when building a
+	 * PIE binary. This requires cooperation from the linker script, which
+	 * must emit the lo32/hi32 halves individually.
+	 */
+	.macro	le64sym, sym
+	.long	\sym\()_lo32
+	.long	\sym\()_hi32
+	.endm
+
 #endif	/* __ASM_ASSEMBLER_H */
arch/arm64/kernel/head.S +3 -3
index 05b98289093e..f076debf392d 100644
@@ -83,9 +83,9 @@ efi_head:
 	b	stext				// branch to kernel start, magic
 	.long	0				// reserved
 #endif
-	.quad	_kernel_offset_le		// Image load offset from start of RAM, little-endian
-	.quad	_kernel_size_le			// Effective size of kernel image, little-endian
-	.quad	_kernel_flags_le		// Informative flags, little-endian
+	le64sym	_kernel_offset_le		// Image load offset from start of RAM, little-endian
+	le64sym	_kernel_size_le			// Effective size of kernel image, little-endian
+	le64sym	_kernel_flags_le		// Informative flags, little-endian
 	.quad	0				// reserved
 	.quad	0				// reserved
 	.quad	0				// reserved
arch/arm64/kernel/image.h +19 -13
index c9c62cab25a4..db1bf57948f1 100644
@@ -26,21 +26,27 @@
  * There aren't any ELF relocations we can use to endian-swap values known only
  * at link time (e.g. the subtraction of two symbol addresses), so we must get
  * the linker to endian-swap certain values before emitting them.
+ *
+ * Note that, in order for this to work when building the ELF64 PIE executable
+ * (for KASLR), these values should not be referenced via R_AARCH64_ABS64
+ * relocations, since these are fixed up at runtime rather than at build time
+ * when PIE is in effect. So we need to split them up in 32-bit high and low
+ * words.
  */
 #ifdef CONFIG_CPU_BIG_ENDIAN
-#define DATA_LE64(data)					\
-	((((data) & 0x00000000000000ff) << 56) |	\
-	 (((data) & 0x000000000000ff00) << 40) |	\
-	 (((data) & 0x0000000000ff0000) << 24) |	\
-	 (((data) & 0x00000000ff000000) << 8)  |	\
-	 (((data) & 0x000000ff00000000) >> 8)  |	\
-	 (((data) & 0x0000ff0000000000) >> 24) |	\
-	 (((data) & 0x00ff000000000000) >> 40) |	\
-	 (((data) & 0xff00000000000000) >> 56))
+#define DATA_LE32(data)				\
+	((((data) & 0x000000ff) << 24) |	\
+	 (((data) & 0x0000ff00) << 8)  |	\
+	 (((data) & 0x00ff0000) >> 8)  |	\
+	 (((data) & 0xff000000) >> 24))
 #else
-#define DATA_LE64(data) ((data) & 0xffffffffffffffff)
+#define DATA_LE32(data) ((data) & 0xffffffff)
 #endif
 
+#define DEFINE_IMAGE_LE64(sym, data)				\
+	sym##_lo32 = DATA_LE32((data) & 0xffffffff);		\
+	sym##_hi32 = DATA_LE32((data) >> 32)
+
 #ifdef CONFIG_CPU_BIG_ENDIAN
 #define __HEAD_FLAG_BE		1
 #else
@@ -61,9 +67,9 @@
  * endian swapped in head.S, all are done here for consistency.
  */
 #define HEAD_SYMBOLS						\
-	_kernel_size_le		= DATA_LE64(_end - _text);	\
-	_kernel_offset_le	= DATA_LE64(TEXT_OFFSET);	\
-	_kernel_flags_le	= DATA_LE64(__HEAD_FLAGS);
+	DEFINE_IMAGE_LE64(_kernel_size_le, _end - _text);	\
+	DEFINE_IMAGE_LE64(_kernel_offset_le, TEXT_OFFSET);	\
+	DEFINE_IMAGE_LE64(_kernel_flags_le, __HEAD_FLAGS);
 
 #ifdef CONFIG_EFI
 
Signature · 1 line
2.5.0
#31 lore raw

PATCH 3/8 arm64: avoid dynamic relocations in early boot code

In reply to Ard Biesheuvel

Before implementing KASLR for arm64 by building a self-relocating PIE executable, we have to ensure that values we use before the relocation routine is executed are not subject to dynamic relocation themselves. This applies not only to virtual addresses, but also to values that are supplied by the linker at build time and relocated using R_AARCH64_ABS64 relocations.

So instead, use assemble time constants, or force the use of static relocations by folding the constants into the instructions.

---
 arch/arm64/kernel/efi-entry.S |  2 +-
 arch/arm64/kernel/head.S      | 39 +++++++++++++-------
 2 files changed, 27 insertions(+), 14 deletions(-)
arch/arm64/kernel/efi-entry.S +1 -1
index a773db92908b..f82036e02485 100644
@@ -61,7 +61,7 @@ ENTRY(entry)
 	 */
 	mov	x20, x0		// DTB address
 	ldr	x0, [sp, #16]	// relocated _text address
-	ldr	x21, =stext_offset
+	movz	x21, #:abs_g0:stext_offset
 	add	x21, x0, x21
 
 	/*
arch/arm64/kernel/head.S +26 -13
index f076debf392d..4cad8f9f2268 100644
@@ -67,12 +67,11 @@
  * in the entry routines.
  */
 	__HEAD
-
+_head:
 	/*
 	 * DO NOT MODIFY. Image header expected by Linux boot-loaders.
 	 */
 #ifdef CONFIG_EFI
-efi_head:
 	/*
 	 * This add instruction has no meaningful effect except that
 	 * its opcode forms the magic "MZ" signature required by UEFI.
@@ -94,14 +93,14 @@ efi_head:
 	.byte	0x4d
 	.byte	0x64
 #ifdef CONFIG_EFI
-	.long	pe_header - efi_head		// Offset to the PE header.
+	.long	pe_header - _head		// Offset to the PE header.
 #else
 	.word	0				// reserved
 #endif
 
 #ifdef CONFIG_EFI
 	.globl	__efistub_stext_offset
-	.set	__efistub_stext_offset, stext - efi_head
+	.set	__efistub_stext_offset, stext - _head
 	.align 3
 pe_header:
 	.ascii	"PE"
@@ -124,7 +123,7 @@ optional_header:
 	.long	_end - stext			// SizeOfCode
 	.long	0				// SizeOfInitializedData
 	.long	0				// SizeOfUninitializedData
-	.long	__efistub_entry - efi_head	// AddressOfEntryPoint
+	.long	__efistub_entry - _head		// AddressOfEntryPoint
 	.long	__efistub_stext_offset		// BaseOfCode
 
 extra_header_fields:
@@ -139,7 +138,7 @@ extra_header_fields:
 	.short	0				// MinorSubsystemVersion
 	.long	0				// Win32VersionValue
 
-	.long	_end - efi_head			// SizeOfImage
+	.long	_end - _head			// SizeOfImage
 
 	// Everything before the kernel image is considered part of the header
 	.long	__efistub_stext_offset		// SizeOfHeaders
@@ -219,11 +218,13 @@ ENTRY(stext)
 	 * On return, the CPU will be ready for the MMU to be turned on and
 	 * the TCR will have been set.
 	 */
-	ldr	x27, =__mmap_switched		// address to jump to after
+	ldr	x27, 0f				// address to jump to after
 						// MMU has been enabled
 	adr_l	lr, __enable_mmu		// return (PIC) address
 	b	__cpu_setup			// initialise processor
 ENDPROC(stext)
+	.align	3
+0:	.quad	__mmap_switched - (_head - TEXT_OFFSET) + KIMAGE_VADDR
 
 /*
  * Preserve the arguments passed by the bootloader in x0 .. x3
@@ -391,7 +392,8 @@ __create_page_tables:
 	mov	x0, x26				// swapper_pg_dir
 	ldr	x5, =KIMAGE_VADDR
 	create_pgd_entry x0, x5, x3, x6
-	ldr	x6, =KERNEL_END			// __va(KERNEL_END)
+	ldr	w6, kernel_img_size
+	add	x6, x6, x5
 	mov	x3, x24				// phys offset
 	create_block_map x0, x7, x3, x5, x6
 
@@ -408,6 +410,9 @@ __create_page_tables:
 	mov	lr, x27
 	ret
 ENDPROC(__create_page_tables)
+
+kernel_img_size:
+	.long	_end - (_head - TEXT_OFFSET)
 	.ltorg
 
 /*
@@ -415,6 +420,10 @@ ENDPROC(__create_page_tables)
  */
 	.set	initial_sp, init_thread_union + THREAD_START_SP
 __mmap_switched:
+	adr_l	x8, vectors			// load VBAR_EL1 with virtual
+	msr	vbar_el1, x8			// vector table address
+	isb
+
 	// Clear BSS
 	adr_l	x0, __bss_start
 	mov	x1, xzr
@@ -610,13 +619,19 @@ ENTRY(secondary_startup)
 	adrp	x26, swapper_pg_dir
 	bl	__cpu_setup			// initialise processor
 
-	ldr	x21, =secondary_data
-	ldr	x27, =__secondary_switched	// address to jump to after enabling the MMU
+	ldr	x8, =KIMAGE_VADDR
+	ldr	w9, 0f
+	sub	x27, x8, w9, sxtw		// address to jump to after enabling the MMU
 	b	__enable_mmu
 ENDPROC(secondary_startup)
+0:	.long	(_text - TEXT_OFFSET) - __secondary_switched
 
 ENTRY(__secondary_switched)
-	ldr	x0, [x21]			// get secondary_data.stack
+	adr_l	x5, vectors
+	msr	vbar_el1, x5
+	isb
+
+	ldr_l	x0, secondary_data		// get secondary_data.stack
 	mov	sp, x0
 	and	x0, x0, #~(THREAD_SIZE - 1)
 	msr	sp_el0, x0			// save thread_info
@@ -641,8 +656,6 @@ __enable_mmu:
 	ubfx	x2, x1, #ID_AA64MMFR0_TGRAN_SHIFT, 4
 	cmp	x2, #ID_AA64MMFR0_TGRAN_SUPPORTED
 	b.ne	__no_granule_support
-	ldr	x5, =vectors
-	msr	vbar_el1, x5
 	msr	ttbr0_el1, x25			// load TTBR0
 	msr	ttbr1_el1, x26			// load TTBR1
 	isb
Signature · 1 line
2.5.0
#32 lore raw

PATCH 4/8 arm64: make asm/elf.h available to asm files

In reply to Ard Biesheuvel

This reshuffles some code in asm/elf.h and puts a #ifndef __ASSEMBLY__ around its C definitions so that the CPP defines can be used in asm source files as well.

---
 arch/arm64/include/asm/elf.h | 22 ++++++++++++--------
 1 file changed, 13 insertions(+), 9 deletions(-)
arch/arm64/include/asm/elf.h +13 -9
index faad6df49e5b..435f55952e1f 100644
@@ -24,15 +24,6 @@
 #include <asm/ptrace.h>
 #include <asm/user.h>
 
-typedef unsigned long elf_greg_t;
-
-#define ELF_NGREG (sizeof(struct user_pt_regs) / sizeof(elf_greg_t))
-#define ELF_CORE_COPY_REGS(dest, regs)	\
-	*(struct user_pt_regs *)&(dest) = (regs)->user_regs;
-
-typedef elf_greg_t elf_gregset_t[ELF_NGREG];
-typedef struct user_fpsimd_state elf_fpregset_t;
-
 /*
  * AArch64 static relocation types.
  */
@@ -127,6 +118,17 @@ typedef struct user_fpsimd_state elf_fpregset_t;
  */
 #define ELF_ET_DYN_BASE	(2 * TASK_SIZE_64 / 3)
 
+#ifndef __ASSEMBLY__
+
+typedef unsigned long elf_greg_t;
+
+#define ELF_NGREG (sizeof(struct user_pt_regs) / sizeof(elf_greg_t))
+#define ELF_CORE_COPY_REGS(dest, regs)	\
+	*(struct user_pt_regs *)&(dest) = (regs)->user_regs;
+
+typedef elf_greg_t elf_gregset_t[ELF_NGREG];
+typedef struct user_fpsimd_state elf_fpregset_t;
+
 /*
  * When the program starts, a1 contains a pointer to a function to be
  * registered with atexit, as per the SVR4 ABI.  A value of 0 means we have no
@@ -186,4 +188,6 @@ extern int aarch32_setup_vectors_page(struct linux_binprm *bprm,
 
 #endif /* CONFIG_COMPAT */
 
+#endif /* !__ASSEMBLY__ */
+
 #endif
Signature · 1 line
2.5.0
#33 lore raw

PATCH 5/8 scripts/sortextable: add support for ET_DYN binaries

In reply to Ard Biesheuvel

Add support to scripts/sortextable for handling relocatable (PIE) executables, whose ELF type is ET_DYN, not ET_EXEC. Other than adding support for the new type, no changes are needed.

---
 scripts/sortextable.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
scripts/sortextable.c +4 -4
index af247c70fb66..19d83647846c 100644
@@ -266,9 +266,9 @@ do_file(char const *const fname)
 		break;
 	}  /* end switch */
 	if (memcmp(ELFMAG, ehdr->e_ident, SELFMAG) != 0
-	||  r2(&ehdr->e_type) != ET_EXEC
+	||  (r2(&ehdr->e_type) != ET_EXEC && r2(&ehdr->e_type) != ET_DYN)
 	||  ehdr->e_ident[EI_VERSION] != EV_CURRENT) {
-		fprintf(stderr, "unrecognized ET_EXEC file %s\n", fname);
+		fprintf(stderr, "unrecognized ET_EXEC/ET_DYN file %s\n", fname);
 		fail_file();
 	}
 
@@ -304,7 +304,7 @@ do_file(char const *const fname)
 		if (r2(&ehdr->e_ehsize) != sizeof(Elf32_Ehdr)
 		||  r2(&ehdr->e_shentsize) != sizeof(Elf32_Shdr)) {
 			fprintf(stderr,
-				"unrecognized ET_EXEC file: %s\n", fname);
+				"unrecognized ET_EXEC/ET_DYN file: %s\n", fname);
 			fail_file();
 		}
 		do32(ehdr, fname, custom_sort);
@@ -314,7 +314,7 @@ do_file(char const *const fname)
 		if (r2(&ghdr->e_ehsize) != sizeof(Elf64_Ehdr)
 		||  r2(&ghdr->e_shentsize) != sizeof(Elf64_Shdr)) {
 			fprintf(stderr,
-				"unrecognized ET_EXEC file: %s\n", fname);
+				"unrecognized ET_EXEC/ET_DYN file: %s\n", fname);
 			fail_file();
 		}
 		do64(ghdr, fname, custom_sort);
Signature · 1 line
2.5.0
#34 lore raw

PATCH 6/8 arm64: add support for building vmlinux as a relocatable PIE binary

In reply to Ard Biesheuvel

This implements CONFIG_RELOCATABLE, which links the final vmlinux image with a dynamic relocation section, allowing the early boot code to perform a relocation to a different virtual address at runtime.

This is a prerequisite for KASLR (CONFIG_RANDOMIZE_BASE).

---
 arch/arm64/Kconfig              | 11 +++++++
 arch/arm64/Makefile             |  4 +++
 arch/arm64/include/asm/elf.h    |  2 ++
 arch/arm64/kernel/head.S        | 32 ++++++++++++++++++++
 arch/arm64/kernel/vmlinux.lds.S | 16 ++++++++++
 5 files changed, 65 insertions(+)
arch/arm64/Kconfig +11 -0
index 141f65ab0ed5..5e155cc7b49f 100644
@@ -762,6 +762,17 @@ config ARM64_MODULE_PLTS
 	select ARM64_MODULE_CMODEL_LARGE
 	select HAVE_MOD_ARCH_SPECIFIC
 
+config RELOCATABLE
+	bool
+	help
+	  This builds the kernel as a Position Independent Executable (PIE),
+	  which retains all relocation metadata required to relocate the
+	  kernel binary at runtime to a different virtual address than the
+	  address it was linked at.
+	  Since AArch64 uses the RELA relocation format, this requires a
+	  relocation pass at runtime even if the kernel is loaded at the
+	  same address it was linked at.
+
 endmenu
 
 menu "Boot options"
arch/arm64/Makefile +4 -0
index a6bba9623836..6b5508a17495 100644
@@ -15,6 +15,10 @@ CPPFLAGS_vmlinux.lds = -DTEXT_OFFSET=$(TEXT_OFFSET)
 OBJCOPYFLAGS	:=-O binary -R .note -R .note.gnu.build-id -R .comment -S
 GZFLAGS		:=-9
 
+ifneq ($(CONFIG_RELOCATABLE),)
+LDFLAGS_vmlinux		+= -pie
+endif
+
 KBUILD_DEFCONFIG := defconfig
 
 # Check for binutils support for specific extensions
arch/arm64/include/asm/elf.h +2 -0
index 435f55952e1f..24ed037f09fd 100644
@@ -77,6 +77,8 @@
 #define R_AARCH64_MOVW_PREL_G2_NC	292
 #define R_AARCH64_MOVW_PREL_G3		293
 
+#define R_AARCH64_RELATIVE		1027
+
 /*
  * These are used to set parameters in the core dumps.
  */
arch/arm64/kernel/head.S +32 -0
index 4cad8f9f2268..4e69412a7323 100644
@@ -29,6 +29,7 @@
 #include <asm/asm-offsets.h>
 #include <asm/cache.h>
 #include <asm/cputype.h>
+#include <asm/elf.h>
 #include <asm/kernel-pgtable.h>
 #include <asm/memory.h>
 #include <asm/pgtable-hwdef.h>
@@ -432,6 +433,37 @@ __mmap_switched:
 	bl	__pi_memset
 	dsb	ishst				// Make zero page visible to PTW
 
+#ifdef CONFIG_RELOCATABLE
+
+	/*
+	 * Iterate over each entry in the relocation table, and apply the
+	 * relocations in place.
+	 */
+	adr_l	x8, __dynsym_start		// start of symbol table
+	adr_l	x9, __reloc_start		// start of reloc table
+	adr_l	x10, __reloc_end		// end of reloc table
+
+0:	cmp	x9, x10
+	b.hs	2f
+	ldp	x11, x12, [x9], #24
+	ldr	x13, [x9, #-8]
+	cmp	w12, #R_AARCH64_RELATIVE
+	b.ne	1f
+	str	x13, [x11]
+	b	0b
+
+1:	cmp	w12, #R_AARCH64_ABS64
+	b.ne	0b
+	add	x12, x12, x12, lsl #1		// symtab offset: 24x top word
+	add	x12, x8, x12, lsr #(32 - 3)	// ... shifted into bottom word
+	ldr	x15, [x12, #8]			// Elf64_Sym::st_value
+	add	x15, x13, x15
+	str	x15, [x11]
+	b	0b
+
+2:
+#endif
+
 	adr_l	sp, initial_sp, x4
 	mov	x4, sp
 	and	x4, x4, #~(THREAD_SIZE - 1)
arch/arm64/kernel/vmlinux.lds.S +16 -0
index 282e3e64a17e..e3f6cd740ea3 100644
@@ -87,6 +87,7 @@ SECTIONS
 		EXIT_CALL
 		*(.discard)
 		*(.discard.*)
+		*(.interp .dynamic)
 	}
 
 	. = KIMAGE_VADDR + TEXT_OFFSET;
@@ -149,6 +150,21 @@ SECTIONS
 	.altinstr_replacement : {
 		*(.altinstr_replacement)
 	}
+	.rela : ALIGN(8) {
+		__reloc_start = .;
+		*(.rela .rela*)
+		__reloc_end = .;
+	}
+	.dynsym : ALIGN(8) {
+		__dynsym_start = .;
+		*(.dynsym)
+	}
+	.dynstr : {
+		*(.dynstr)
+	}
+	.hash : {
+		*(.hash)
+	}
 
 	. = ALIGN(PAGE_SIZE);
 	__init_end = .;
Signature · 1 line
2.5.0
#35 lore raw

PATCH 7/8 arm64: add support for kernel ASLR

In reply to Ard Biesheuvel

This adds support for KASLR is implemented, based on entropy provided by the bootloader in the /chosen/kaslr-seed DT property. Depending on the size of the address space (VA_BITS) and the page size, the entropy in the virtual displacement is up to 13 bits (16k/2 levels) and up to 25 bits (all 4 levels), with the sidenote that displacements that result in the kernel image straddling a 1GB/32MB/512MB alignment boundary (for 4KB/16KB/64KB granule kernels, respectively) are not allowed, and will be rounded up to an acceptable value.

The module region is randomized by choosing a page aligned 128 MB region inside the interval [_etext - 128 MB, _stext + 128 MB). This gives between 10 and 14 bits of entropy (depending on page size), independently of the kernel randomization, but still guarantees that modules are within the range of relative branch and jump instructions (with the caveat that, since the module region is shared with other uses of the vmalloc area, modules may need to be loaded further away if the module region is exhausted)

---
 arch/arm64/Kconfig              |  14 ++
 arch/arm64/include/asm/memory.h |   5 +-
 arch/arm64/kernel/Makefile      |   1 +
 arch/arm64/kernel/head.S        |  59 +++++++--
 arch/arm64/kernel/kaslr.c       | 139 ++++++++++++++++++++
 arch/arm64/kernel/module.c      |   8 +-
 arch/arm64/kernel/setup.c       |  29 ++++
 arch/arm64/mm/mmu.c             |  33 +++--
 8 files changed, 268 insertions(+), 20 deletions(-)
arch/arm64/Kconfig +14 -0
index 5e155cc7b49f..e3049d5c1246 100644
@@ -773,6 +773,20 @@ config RELOCATABLE
 	  relocation pass at runtime even if the kernel is loaded at the
 	  same address it was linked at.
 
+config RANDOMIZE_BASE
+	bool "Randomize the address of the kernel image"
+	select ARM64_MODULE_PLTS
+	select RELOCATABLE
+	help
+	  Randomizes the virtual address at which the kernel image is
+	  loaded, as a security feature that deters exploit attempts
+	  relying on knowledge of the location of kernel internals.
+
+	  It is the bootloader's job to provide entropy, by passing a
+	  random u64 value in /chosen/kaslr-seed at kernel entry.
+
+	  If unsure, say N.
+
 endmenu
 
 menu "Boot options"
arch/arm64/include/asm/memory.h +4 -1
index 61005e7dd6cb..083361531a61 100644
@@ -52,7 +52,7 @@
 #define KIMAGE_VADDR		(MODULES_END)
 #define MODULES_END		(MODULES_VADDR + MODULES_VSIZE)
 #define MODULES_VADDR		(VA_START + KASAN_SHADOW_SIZE)
-#define MODULES_VSIZE		(SZ_64M)
+#define MODULES_VSIZE		(SZ_128M)
 #define PCI_IO_END		(PAGE_OFFSET - SZ_2M)
 #define PCI_IO_START		(PCI_IO_END - PCI_IO_SIZE)
 #define FIXADDR_TOP		(PCI_IO_START - SZ_2M)
@@ -127,6 +127,9 @@ extern phys_addr_t		memstart_addr;
 /* PHYS_OFFSET - the physical address of the start of memory. */
 #define PHYS_OFFSET		({ memstart_addr; })
 
+/* the virtual base of the kernel image (minus TEXT_OFFSET) */
+extern u64			kimage_vaddr;
+
 /* the offset between the kernel virtual and physical mappings */
 extern u64			kimage_voffset;
 
arch/arm64/kernel/Makefile +1 -0
index e2f0a755beaa..c9aaecddb941 100644
@@ -43,6 +43,7 @@ arm64-obj-$(CONFIG_PCI)			+= pci.o
 arm64-obj-$(CONFIG_ARMV8_DEPRECATED)	+= armv8_deprecated.o
 arm64-obj-$(CONFIG_ACPI)		+= acpi.o
 arm64-obj-$(CONFIG_PARAVIRT)		+= paravirt.o
+arm64-obj-$(CONFIG_RANDOMIZE_BASE)	+= kaslr.o
 
 obj-y					+= $(arm64-obj-y) vdso/
 obj-m					+= $(arm64-obj-m)
arch/arm64/kernel/head.S +51 -8

141 lines not on this page · raw message

arch/arm64/kernel/kaslr.c new file +139 -0

139 lines not on this page · raw message

arch/arm64/kernel/module.c +7 -1
index 84113d3e1df1..54702d456680 100644
@@ -33,8 +33,14 @@
 void *module_alloc(unsigned long size)
 {
 	void *p;
+	u64 base = (u64)_etext - MODULES_VSIZE;
 
-	p = __vmalloc_node_range(size, MODULE_ALIGN, MODULES_VADDR, MODULES_END,
+	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
+		extern u32 module_load_offset;
+		base += module_load_offset;
+	}
+
+	p = __vmalloc_node_range(size, MODULE_ALIGN, base, base + MODULES_VSIZE,
 				GFP_KERNEL, PAGE_KERNEL_EXEC, 0,
 				NUMA_NO_NODE, __builtin_return_address(0));
 
arch/arm64/kernel/setup.c +29 -0
index cfed56f0ad26..42371f69def3 100644
@@ -388,3 +388,32 @@ static int __init topology_init(void)
 	return 0;
 }
 subsys_initcall(topology_init);
+
+/*
+ * Dump out kernel offset information on panic.
+ */
+static int dump_kernel_offset(struct notifier_block *self, unsigned long v,
+			      void *p)
+{
+	u64 const kaslr_offset = kimage_vaddr - KIMAGE_VADDR;
+
+	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && kaslr_offset > 0) {
+		pr_emerg("Kernel Offset: 0x%llx from 0x%lx\n",
+			 kaslr_offset, KIMAGE_VADDR);
+	} else {
+		pr_emerg("Kernel Offset: disabled\n");
+	}
+	return 0;
+}
+
+static struct notifier_block kernel_offset_notifier = {
+	.notifier_call = dump_kernel_offset
+};
+
+static int __init register_kernel_offset_dumper(void)
+{
+	atomic_notifier_chain_register(&panic_notifier_list,
+				       &kernel_offset_notifier);
+	return 0;
+}
+__initcall(register_kernel_offset_dumper);
arch/arm64/mm/mmu.c +23 -10
index 8dda38378959..5d7e0b801ab7 100644
@@ -636,7 +636,8 @@ void __init early_fixmap_init(void)
 	unsigned long addr = FIXADDR_START;
 
 	pgd = pgd_offset_k(addr);
-	if (CONFIG_PGTABLE_LEVELS > 3 && !pgd_none(*pgd)) {
+	if (CONFIG_PGTABLE_LEVELS > 3 &&
+	    !(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa(bm_pud))) {
 		/*
 		 * We only end up here if the kernel mapping and the fixmap
 		 * share the top level pgd entry, which should only happen on
@@ -693,11 +694,10 @@ void __set_fixmap(enum fixed_addresses idx,
 	}
 }
 
-void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+void *__init __fixmap_remap_fdt(phys_addr_t dt_phys, int *size, pgprot_t prot)
 {
 	const u64 dt_virt_base = __fix_to_virt(FIX_FDT);
-	pgprot_t prot = PAGE_KERNEL_RO;
-	int size, offset;
+	int offset;
 	void *dt_virt;
 
 	/*
@@ -736,16 +736,29 @@ void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
 	if (fdt_check_header(dt_virt) != 0)
 		return NULL;
 
-	size = fdt_totalsize(dt_virt);
-	if (size > MAX_FDT_SIZE)
+	*size = fdt_totalsize(dt_virt);
+	if (*size > MAX_FDT_SIZE)
 		return NULL;
 
-	if (offset + size > SWAPPER_BLOCK_SIZE)
-		create_mapping(round_down(dt_phys, SWAPPER_BLOCK_SIZE), dt_virt_base,
-			       round_up(offset + size, SWAPPER_BLOCK_SIZE), prot);
+	if (offset + *size > SWAPPER_BLOCK_SIZE)
+		create_mapping(round_down(dt_phys, SWAPPER_BLOCK_SIZE),
+			       dt_virt_base,
+			       round_up(offset + *size, SWAPPER_BLOCK_SIZE),
+			       prot);
 
-	memblock_reserve(dt_phys, size);
+	return dt_virt;
+}
 
+void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
+{
+	void *dt_virt;
+	int size;
+
+	dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
+	if (!dt_virt)
+		return NULL;
+
+	memblock_reserve(dt_phys, size);
 	return dt_virt;
 }
 
Signature · 1 line
2.5.0
#36 lore raw

PATCH 8/8 arm64: kaslr: randomize the linear region

In reply to Ard Biesheuvel

When KASLR is enabled (CONFIG_RANDOMIZE_BASE=y), and entropy has been provided by the bootloader, randomize the placement of RAM inside the linear region if sufficient space is available. For instance, on a 4KB granule/3 levels kernel, the linear region is 256 GB in size, and we can choose any 1 GB aligned offset that is far enough from the top of the address space to fit the distance between the start of the lowest memblock and the top of the highest memblock.

---
 arch/arm64/kernel/kaslr.c |  4 ++++
 arch/arm64/mm/init.c      | 22 ++++++++++++++++++--
 2 files changed, 24 insertions(+), 2 deletions(-)
arch/arm64/kernel/kaslr.c +4 -0
index b0bf628ba51f..c96f63670537 100644
@@ -21,6 +21,7 @@
 #include <asm/sections.h>
 
 u32 __read_mostly module_load_offset;
+u16 __initdata memstart_offset_seed;
 
 static __init u64 get_kaslr_seed(void *fdt)
 {
@@ -135,5 +136,8 @@ u64 __init kaslr_early_init(u64 dt_phys)
 	module_range = MODULES_VSIZE - (u64)(_etext - _stext);
 	module_load_offset = ((module_range * (u16)seed) >> 16) & PAGE_MASK;
 
+	/* use the top 16 bits to randomize the linear region */
+	memstart_offset_seed = seed >> 48;
+
 	return offset;
 }
arch/arm64/mm/init.c +20 -2
index e8e853a1024c..01fdb3d04bba 100644
@@ -215,6 +215,23 @@ void __init arm64_memblock_init(void)
 	if (memblock_end_of_DRAM() > linear_region_size)
 		memblock_remove(0, memblock_end_of_DRAM() - linear_region_size);
 
+	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
+		extern u16 memstart_offset_seed;
+		u64 range = linear_region_size -
+			    (memblock_end_of_DRAM() - memblock_start_of_DRAM());
+
+		/*
+		 * If the size of the linear region exceeds, by a sufficient
+		 * margin, the size of the region that the available physical
+		 * memory spans, randomize the linear region as well.
+		 */
+		if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
+			range = range / ARM64_MEMSTART_ALIGN + 1;
+			memstart_addr -= ARM64_MEMSTART_ALIGN *
+					 ((range * memstart_offset_seed) >> 16);
+		}
+	}
+
 	if (memory_limit != (phys_addr_t)ULLONG_MAX) {
 		u64 kbase = round_down(__pa(_text), MIN_KIMG_ALIGN);
 		u64 kend = PAGE_ALIGN(__pa(_end));
@@ -399,12 +416,13 @@ void __init mem_init(void)
 #ifdef CONFIG_SPARSEMEM_VMEMMAP
 		  MLG((unsigned long)vmemmap,
 		      (unsigned long)vmemmap + VMEMMAP_SIZE),
-		  MLM((unsigned long)virt_to_page(PAGE_OFFSET),
+		  MLM((unsigned long)phys_to_page(memblock_start_of_DRAM()),
 		      (unsigned long)virt_to_page(high_memory)),
 #endif
 		  MLK(FIXADDR_START, FIXADDR_TOP),
 		  MLM(PCI_IO_START, PCI_IO_END),
-		  MLM(PAGE_OFFSET, (unsigned long)high_memory));
+		  MLM(__phys_to_virt(memblock_start_of_DRAM()),
+		      (unsigned long)high_memory));
 
 #undef MLK
 #undef MLM
Signature · 1 line
2.5.0
#37 lore raw

cover arm64: implement virtual KASLR

In reply to Ard Biesheuvel
Quoted 6 lines · Ard Biesheuvel

At the request of Catalin, this series has been split off from my series 'arm64: implement support for KASLR v4' [1]. This sub-series deals with creating a relocatable binary, and randomizing the virtual placement of the kernel itself, the modules and the linear region based on entropy supplied by the bootloader in the /chosen/kaslr-seed DT property.

Quoted 11 lines

Changes since v4:
- add randomization of the linear region, i.e., if the linear region is substantially larger than the space spanned by RAM, the mapping of RAM is moved to a random offset inside the linear region.
- dropped the CRC check in kaslr_early_init(), since the only code that may modify .data through __fixmap_remap_fdt() is instrumentation that should deal with that correctly (For instance, the branch profiling records taken branches in structs allocated in .data, and these counts will not be reset to 0 between the first and the second call to __mmap_switched())
- add Mark's ack to patch #4

NOTE: I have omitted the relative kallsyms and relative extable patches, since they already queued in akpm's tree. However, while the kallsyms patch is merely an optimization, the extable patches are in fact required for correct operation, since the build time sorting does not work on absolute extables (this is due to the fact that the resolution of the relocations is deferred until runtime, and so the extable addresses are all zero at sorting time)

http://ozlabs.org/~akpm/mmotm/broken-out/extable-add-support-for-relative-extables-to-search-and-sort-routines.patch
http://ozlabs.org/~akpm/mmotm/broken-out/arm64-switch-to-relative-exception-tables.patch

Quoted 62 lines

Patch #1 adds support for resolving cross-module branches via veneers if the module is located too far from the target. This should not be necessary in the common case even with KASRL enabled, but since the space around the kernel image will no longer be strictly reserved for modules, there is a slight chance that the region may be exhausted, in which case modules will be moved further away from the kernel (and each other)

Patches #2 and #3 deal with the fact that relocations of 64-bit values are deferred to runtime when building a PIE executable, and some values are required before the relocation routine executes (either in the code, or as an externally visible constant in the Image header)

Patch #4 rehuffles asm/elf.h so we can use its preprocessor constants in head.S

Patch #5 and #6 implement building vmlinux as a PIE (Position Independent Executable) binary, which is emitted with relocation information that is processed by the program itself in the early boot code.

Patch #7 implements KASLR, i.e., CONFIG_RANDOMIZE_BASE for the core kernel and for the module region.

Patch #8 implements KASLR for the linear region.

[1] http://thread.gmane.org/gmane.linux.kernel/2135931

Ard Biesheuvel (8):
arm64: add support for module PLTs
arm64: avoid R_AARCH64_ABS64 relocations for Image header fields
arm64: avoid dynamic relocations in early boot code
arm64: make asm/elf.h available to asm files scripts/sortextable: add support for ET_DYN binaries
arm64: add support for building vmlinux as a relocatable PIE binary
arm64: add support for kernel ASLR
arm64: kaslr: randomize the linear region

 arch/arm64/Kconfig                 |  34 ++++
 arch/arm64/Makefile                |  10 +-
 arch/arm64/include/asm/assembler.h |  11 ++
 arch/arm64/include/asm/elf.h       |  24 ++-
 arch/arm64/include/asm/memory.h    |   5 +-
 arch/arm64/include/asm/module.h    |  11 ++
 arch/arm64/kernel/Makefile         |   2 +
 arch/arm64/kernel/efi-entry.S      |   2 +-
 arch/arm64/kernel/head.S           | 128 +++++++++++--
 arch/arm64/kernel/image.h          |  32 ++--
 arch/arm64/kernel/kaslr.c          | 143 ++++++++++++++
 arch/arm64/kernel/module-plts.c    | 201 ++++++++++++++++++++
 arch/arm64/kernel/module.c         |  20 +-
 arch/arm64/kernel/module.lds       |   3 +
 arch/arm64/kernel/setup.c          |  29 +++
 arch/arm64/kernel/vmlinux.lds.S    |  16 ++
 arch/arm64/mm/init.c               |  22 ++-
 arch/arm64/mm/mmu.c                |  33 +++-
 scripts/sortextable.c              |   8 +-
 19 files changed, 672 insertions(+), 62 deletions(-)
 create mode 100644 arch/arm64/kernel/kaslr.c
 create mode 100644 arch/arm64/kernel/module-plts.c
 create mode 100644 arch/arm64/kernel/module.lds

--
2.5.0

maintainer
#38 lore raw

cover arm64: implement virtual KASLR

Quoted 27 lines · Ard Biesheuvel
Quoted 6 lines · Ard Biesheuvel

At the request of Catalin, this series has been split off from my series 'arm64: implement support for KASLR v4' [1]. This sub-series deals with creating a relocatable binary, and randomizing the virtual placement of the kernel itself, the modules and the linear region based on entropy supplied by the bootloader in the /chosen/kaslr-seed DT property.

Quoted 11 lines

Changes since v4:
- add randomization of the linear region, i.e., if the linear region is substantially larger than the space spanned by RAM, the mapping of RAM is moved to a random offset inside the linear region.
- dropped the CRC check in kaslr_early_init(), since the only code that may modify .data through __fixmap_remap_fdt() is instrumentation that should deal with that correctly (For instance, the branch profiling records taken branches in structs allocated in .data, and these counts will not be reset to 0 between the first and the second call to __mmap_switched())
- add Mark's ack to patch #4

NOTE: I have omitted the relative kallsyms and relative extable patches, since they already queued in akpm's tree. However, while the kallsyms patch is merely an optimization, the extable patches are in fact required for correct operation, since the build time sorting does not work on absolute extables (this is due to the fact that the resolution of the relocations is deferred until runtime, and so the extable addresses are all zero at sorting time)

Quoted 2 lines

http://ozlabs.org/~akpm/mmotm/broken-out/extable-add-support-for-relative-extables-to-search-and-sort-routines.patch
http://ozlabs.org/~akpm/mmotm/broken-out/arm64-switch-to-relative-exception-tables.patch

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Can we deselect CONFIG_BUILDTIME_EXTABLE_SORT temporarily while we use absolute extable addresses? We patch them at boot-time and sort them at run-time. Once the above patches go in, we can revert to build-time extable sorting.

Signature · 1 line
Catalin
#39 lore raw

cover arm64: implement virtual KASLR

Quoted 28 lines · Catalin Marinas
Quoted 27 lines · Ard Biesheuvel
Quoted 6 lines · Ard Biesheuvel

At the request of Catalin, this series has been split off from my series 'arm64: implement support for KASLR v4' [1]. This sub-series deals with creating a relocatable binary, and randomizing the virtual placement of the kernel itself, the modules and the linear region based on entropy supplied by the bootloader in the /chosen/kaslr-seed DT property.

Quoted 11 lines

Changes since v4:
- add randomization of the linear region, i.e., if the linear region is substantially larger than the space spanned by RAM, the mapping of RAM is moved to a random offset inside the linear region.
- dropped the CRC check in kaslr_early_init(), since the only code that may modify .data through __fixmap_remap_fdt() is instrumentation that should deal with that correctly (For instance, the branch profiling records taken branches in structs allocated in .data, and these counts will not be reset to 0 between the first and the second call to __mmap_switched())
- add Mark's ack to patch #4

NOTE: I have omitted the relative kallsyms and relative extable patches, since they already queued in akpm's tree. However, while the kallsyms patch is merely an optimization, the extable patches are in fact required for correct operation, since the build time sorting does not work on absolute extables (this is due to the fact that the resolution of the relocations is deferred until runtime, and so the extable addresses are all zero at sorting time)

Quoted 7 lines
Quoted 2 lines

http://ozlabs.org/~akpm/mmotm/broken-out/extable-add-support-for-relative-extables-to-search-and-sort-routines.patch
http://ozlabs.org/~akpm/mmotm/broken-out/arm64-switch-to-relative-exception-tables.patch

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

You could always take the series through your tree instead, I guess?

Quoted 5 lines

Can we deselect CONFIG_BUILDTIME_EXTABLE_SORT temporarily while we use absolute extable addresses? We patch them at boot-time and sort them at run-time. Once the above patches go in, we can revert to build-time extable sorting.

I suppose that would be possible, but I simply haven't tried. I noticed that ppc does not use build time extable sorting, that is why they don't need to scripts/extable patch while they do use PIE binaries.

maintainer
#40 lore raw

cover arm64: implement virtual KASLR

Quoted 36 lines · Ard Biesheuvel
Quoted 28 lines · Catalin Marinas
Quoted 27 lines · Ard Biesheuvel
Quoted 6 lines · Ard Biesheuvel

At the request of Catalin, this series has been split off from my series 'arm64: implement support for KASLR v4' [1]. This sub-series deals with creating a relocatable binary, and randomizing the virtual placement of the kernel itself, the modules and the linear region based on entropy supplied by the bootloader in the /chosen/kaslr-seed DT property.

Quoted 11 lines

Changes since v4:
- add randomization of the linear region, i.e., if the linear region is substantially larger than the space spanned by RAM, the mapping of RAM is moved to a random offset inside the linear region.
- dropped the CRC check in kaslr_early_init(), since the only code that may modify .data through __fixmap_remap_fdt() is instrumentation that should deal with that correctly (For instance, the branch profiling records taken branches in structs allocated in .data, and these counts will not be reset to 0 between the first and the second call to __mmap_switched())
- add Mark's ack to patch #4

NOTE: I have omitted the relative kallsyms and relative extable patches, since they already queued in akpm's tree. However, while the kallsyms patch is merely an optimization, the extable patches are in fact required for correct operation, since the build time sorting does not work on absolute extables (this is due to the fact that the resolution of the relocations is deferred until runtime, and so the extable addresses are all zero at sorting time)

Quoted 6 lines
Quoted 2 lines

http://ozlabs.org/~akpm/mmotm/broken-out/extable-add-support-for-relative-extables-to-search-and-sort-routines.patch
http://ozlabs.org/~akpm/mmotm/broken-out/arm64-switch-to-relative-exception-tables.patch

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Quoted 5 lines

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

You could always take the series through your tree instead, I guess?

They all have acks from maintainers, so that's an option.

Quoted 9 lines
Quoted 4 lines

Can we deselect CONFIG_BUILDTIME_EXTABLE_SORT temporarily while we use absolute extable addresses? We patch them at boot-time and sort them at run-time. Once the above patches go in, we can revert to build-time extable sorting.

I suppose that would be possible, but I simply haven't tried. I noticed that ppc does not use build time extable sorting, that is why they don't need to scripts/extable patch while they do use PIE binaries.

I did a quick check with:

arch/arm64/Kconfig +1 -1
@@ -22,7 +22,7 @@ config ARM64
 	select ARM_GIC_V3
 	select ARM_GIC_V3_ITS if PCI_MSI
 	select ARM_PSCI_FW
-	select BUILDTIME_EXTABLE_SORT
+	select BUILDTIME_EXTABLE_SORT if !RELOCATABLE
 	select CLONE_BACKWARDS
 	select COMMON_CLK
 	select CPU_PM if (SUSPEND || CPU_IDLE)

and it seems that extable entries end up in the relocation symbols but I haven't fully checked the patching/sorting yet (it will probably be on Monday).

Signature · 1 line
Catalin
#41 lore raw

cover arm64: implement virtual KASLR

In reply to Ard Biesheuvel
Quoted 10 lines · Ard Biesheuvel
Quoted 3 lines · Catalin Marinas

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

You could always take the series through your tree instead, I guess?

Traditionally akpm will de-duplicate patches he's carrying that appear in another tree. I think it should be okay to carry them in both places. (Though I'm CCing akpm just to see if I'm talking crazy.)

-Kees

Signature · 2 lines
Kees Cook
Chrome OS & Brillo Security
maintainer
#42 lore raw

cover arm64: implement virtual KASLR

Quoted 11 lines · Kees Cook
Quoted 9 lines · Ard Biesheuvel
Quoted 3 lines · Catalin Marinas

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

Quoted 5 lines
Quoted 1 line

You could always take the series through your tree instead, I guess?

Traditionally akpm will de-duplicate patches he's carrying that appear in another tree. I think it should be okay to carry them in both places. (Though I'm CCing akpm just to see if I'm talking crazy.)

For now, I'll merge this series in the arm64 tree and push it to next:

http://lkml.kernel.org/r/1452007180-27411-1-git-send-email-ard.biesheuvel at linaro.org

If there are any objections, I can drop the patches and do the BUILDTIME_EXTABLE_SORT disabling trick until they end up in mainline.

Signature · 1 line
Catalin
#43 lore raw

cover arm64: implement virtual KASLR

Quoted 18 lines · Catalin Marinas
Quoted 11 lines · Kees Cook
Quoted 9 lines · Ard Biesheuvel
Quoted 3 lines · Catalin Marinas

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

Quoted 5 lines
Quoted 1 line

You could always take the series through your tree instead, I guess?

Traditionally akpm will de-duplicate patches he's carrying that appear in another tree. I think it should be okay to carry them in both places. (Though I'm CCing akpm just to see if I'm talking crazy.)

Quoted 7 lines

For now, I'll merge this series in the arm64 tree and push it to next:

http://lkml.kernel.org/r/1452007180-27411-1-git-send-email-ard.biesheuvel at linaro.org

If there are any objections, I can drop the patches and do the BUILDTIME_EXTABLE_SORT disabling trick until they end up in mainline.

Latest version is here:
http://lkml.kernel.org/r/1453892123-17973-1-git-send-email-ard.biesheuvel at linaro.org (only difference is an ack from that Alpha maintainter/supporter to patches #1 and #2)

However, the arm64 patch (#6) now conflicts with futex.h in -rc3 after the PAN fix, not sure how to best address that ...

Signature · 1 line
Ard.
maintainer Applied
#44 lore raw

cover arm64: implement virtual KASLR

Quoted 23 lines · Ard Biesheuvel
Quoted 22 lines · Catalin Marinas
Quoted 11 lines · Kees Cook
Quoted 9 lines · Ard Biesheuvel
Quoted 3 lines · Catalin Marinas

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

Quoted 5 lines
Quoted 1 line

You could always take the series through your tree instead, I guess?

Traditionally akpm will de-duplicate patches he's carrying that appear in another tree. I think it should be okay to carry them in both places. (Though I'm CCing akpm just to see if I'm talking crazy.)

For now, I'll merge this series in the arm64 tree and push it to next:

http://lkml.kernel.org/r/1452007180-27411-1-git-send-email-ard.biesheuvel at linaro.org

Quoted 7 lines
Quoted 2 lines

If there are any objections, I can drop the patches and do the BUILDTIME_EXTABLE_SORT disabling trick until they end up in mainline.

Latest version is here:
http://lkml.kernel.org/r/1453892123-17973-1-git-send-email-ard.biesheuvel at linaro.org (only difference is an ack from that Alpha maintainter/supporter to patches #1 and #2)

I applied the acks manually but I'll double-check to make sure I haven't missed anything.

Quoted 2 lines

However, the arm64 patch (#6) now conflicts with futex.h in -rc3 after the PAN fix, not sure how to best address that ...

I'll have a look.

Signature · 1 line
Catalin
#45 lore raw

cover arm64: implement virtual KASLR

Quoted 32 lines · Catalin Marinas
Quoted 23 lines · Ard Biesheuvel
Quoted 22 lines · Catalin Marinas
Quoted 11 lines · Kees Cook
Quoted 9 lines · Ard Biesheuvel
Quoted 3 lines · Catalin Marinas

I'm still trying to get my head around how we merge those. Since I assume akpm will push them during the merging window, part of your code cannot be tested before.

Actually, my original idea was for akpm to take them as a late merge after rebasing to -rc1, since they touch a variety of architectures, but I am not sure if that came across.

Quoted 5 lines
Quoted 1 line

You could always take the series through your tree instead, I guess?

Traditionally akpm will de-duplicate patches he's carrying that appear in another tree. I think it should be okay to carry them in both places. (Though I'm CCing akpm just to see if I'm talking crazy.)

For now, I'll merge this series in the arm64 tree and push it to next:

http://lkml.kernel.org/r/1452007180-27411-1-git-send-email-ard.biesheuvel at linaro.org

Quoted 7 lines
Quoted 2 lines

If there are any objections, I can drop the patches and do the BUILDTIME_EXTABLE_SORT disabling trick until they end up in mainline.

Latest version is here:
http://lkml.kernel.org/r/1453892123-17973-1-git-send-email-ard.biesheuvel at linaro.org (only difference is an ack from that Alpha maintainter/supporter to patches #1 and #2)

Quoted 8 lines

I applied the acks manually but I'll double-check to make sure I haven't missed anything.

Quoted 2 lines

However, the arm64 patch (#6) now conflicts with futex.h in -rc3 after the PAN fix, not sure how to best address that ...

I'll have a look.

Note that the fix is trivial, but I don't know who should be carrying the fix is all

maintainer
#46 lore raw

cover arm64: implement virtual KASLR

Quoted 9 lines · Ard Biesheuvel
Quoted 5 lines · Catalin Marinas
Quoted 2 lines · Ard Biesheuvel

However, the arm64 patch (#6) now conflicts with futex.h in -rc3 after the PAN fix, not sure how to best address that ...

I'll have a look.

Note that the fix is trivial, but I don't know who should be carrying the fix is all

I'll push the arm64 for-next/core branch (including your series) out against 4.5-rc3, so there won't be any conflict.

Signature · 1 line
Catalin