[RFC PATCH] ARM: decompressor: implement autonomous KASLR offset calculation
Ard Biesheuvel
ard.biesheuvel at linaro.org
Tue Aug 15 13:15:44 PDT 2017
This enables KASLR for environments that are not KASLR-aware, or only
to a limited extent. The decompressor collects information about the
placement of the zImage, DTB and initrd, and parses the /memory DT
node and the /memreserve/s and /reserved-memory node, and combines this
information to select a suitable KASLR offset, and proceeds to decompress
the kernel at this offset in physical memory. It then invoked the kernel
proper while passing on this information, so that it can be taken into
account to create the virtual mapping.
This code shuffles some registers together to create a poor man's seed,
which will be superseded by the value of /chosen/kaslr-seed if present.
Signed-off-by: Ard Biesheuvel <ard.biesheuvel at linaro.org>
---
This is a followup to, and applies onto my series 'implement KASLR for ARM'
sent out yesterday.
As suggested by Nico, it would be useful if the decompressor can autonomously
enable KASLR randomization, so that is what I tried to implement. I left a
couple of TODOs in there, but the general approach should be visible. It ends
up iterating over the memreserves and /reserved-mem subnodes twice for each
candidate region, once for counting them, and again to retrieve the selection
region. I don't think there's a performance concern here, but there is some
room for optimization.
Comments welcome.
Cc: Arnd Bergmann <arnd at arndb.de>
Cc: Nicolas Pitre <nico at linaro.org>
Cc: Russell King <linux at armlinux.org.uk>
Cc: Kees Cook <keescook at chromium.org>
Cc: Mark Rutland <mark.rutland at arm.com>
arch/arm/boot/compressed/Makefile | 8 +-
arch/arm/boot/compressed/head.S | 29 ++
arch/arm/boot/compressed/kaslr.c | 337 ++++++++++++++++++++
3 files changed, 373 insertions(+), 1 deletion(-)
diff --git a/arch/arm/boot/compressed/Makefile b/arch/arm/boot/compressed/Makefile
index d50430c40045..771b1ba1baa3 100644
--- a/arch/arm/boot/compressed/Makefile
+++ b/arch/arm/boot/compressed/Makefile
@@ -85,8 +85,14 @@ $(addprefix $(obj)/,$(libfdt) $(libfdt_hdrs)): $(obj)/%: $(srctree)/scripts/dtc/
$(addprefix $(obj)/,$(libfdt_objs) atags_to_fdt.o): \
$(addprefix $(obj)/,$(libfdt_hdrs))
+ifneq ($(CONFIG_ARM_ATAG_DTB_COMPAT)$(CONFIG_RANDOMIZE_BASE),)
+OBJS += $(libfdt_objs)
ifeq ($(CONFIG_ARM_ATAG_DTB_COMPAT),y)
-OBJS += $(libfdt_objs) atags_to_fdt.o
+OBJS += atags_to_fdt.o
+endif
+ifeq ($(CONFIG_RANDOMIZE_BASE),y)
+OBJS += kaslr.o
+endif
endif
targets := vmlinux vmlinux.lds piggy_data piggy.o \
diff --git a/arch/arm/boot/compressed/head.S b/arch/arm/boot/compressed/head.S
index 7111a2cbef95..769ed959604d 100644
--- a/arch/arm/boot/compressed/head.S
+++ b/arch/arm/boot/compressed/head.S
@@ -382,6 +382,35 @@ restart: adr r0, LC0
dtb_check_done:
#endif
+#ifdef CONFIG_RANDOMIZE_BASE
+ ldr_l r1, kaslr_offset
+ cmp r1, #0
+ bne 0f @ skip if kaslr_offset > 0
+ stmfd sp!, {r0-r3, ip, lr}
+
+ adr_l r2, _text @ start of zImage
+ stmfd sp!, {r2, r10} @ pass start and size of zImage
+
+ eor r3, r0, r3, ror #1 @ poor man's kaslr seed, will
+ eor r3, r3, r1, ror #2 @ be superseded by kaslr-seed
+ eor r3, r3, r2, ror #3 @ from /chosen if present
+ eor r3, r3, r4, ror #5
+ eor r3, r3, r5, ror #8
+ eor r3, r3, r6, ror #13
+ eor r3, r3, r7, ror #21
+
+ mov r0, r8 @ pass DTB address
+ mov r1, r4 @ pass base address
+ mov r2, r9 @ pass decompressed image size
+ bl kaslr_early_init
+ add sp, sp, #8
+ cmp r0, #0
+ addne r4, r4, r0 @ add offset to base address
+ ldmfd sp!, {r0-r3, ip, lr}
+ bne restart
+0:
+#endif
+
/*
* Check to see if we will overwrite ourselves.
* r4 = final kernel address (possibly with LSB set)
diff --git a/arch/arm/boot/compressed/kaslr.c b/arch/arm/boot/compressed/kaslr.c
new file mode 100644
index 000000000000..a6fd2fefc04a
--- /dev/null
+++ b/arch/arm/boot/compressed/kaslr.c
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2017 Linaro Ltd; <ard.biesheuvel at linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <libfdt.h>
+#include <linux/types.h>
+
+#include <asm/pgtable.h>
+#include <asm/zimage.h>
+
+struct regions {
+ u32 pa_start;
+ u32 pa_end;
+ u32 image_size;
+ u32 zimage_start;
+ u32 zimage_size;
+ u32 initrd_start;
+ u32 initrd_size;
+ u32 dtb_start;
+ u32 dtb_size;
+ int reserved_mem;
+};
+
+static const char default_cmdline[] = CONFIG_CMDLINE;
+
+static const char *get_command_line(const void *fdt, int chosen)
+{
+ const char *prop;
+ int len;
+
+ prop = fdt_getprop(fdt, chosen, "bootargs", &len);
+
+ if (IS_ENABLED(CONFIG_CMDLINE_EXTEND)) {
+ if (!prop)
+ return default_cmdline;
+
+ /* TODO merge with hardcoded cmdline */
+ }
+ return prop;
+}
+
+static u32 __memparse(const char *val, const char **retptr)
+{
+ const char *p = val;
+ u32 ret = 0;
+ int base;
+
+ if (*p == '0') {
+ p++;
+ if (*p == 'x' || *p == 'X') {
+ p++;
+ base = 16;
+ } else {
+ base = 8;
+ }
+ } else {
+ base = 10;
+ }
+
+ while (*val != ',' && *val != ' ' && *val != '\0') {
+ char c = *val++;
+
+ switch (c) {
+ case '0' ... '9':
+ ret = ret * base + (c - '0');
+ continue;
+ case 'a' ... 'f':
+ ret = ret * base + (c - 'a' + 10);
+ continue;
+ case 'A' ... 'F':
+ ret = ret * base + (c - 'A' + 10);
+ continue;
+ case 'g':
+ case 'G':
+ ret <<= 10;
+ case 'm':
+ case 'M':
+ ret <<= 10;
+ case 'k':
+ case 'K':
+ ret <<= 10;
+ break;
+ default:
+ if (retptr)
+ *retptr = NULL;
+ return 0;
+ }
+ }
+ if (retptr)
+ *retptr = val;
+ return ret;
+}
+
+static bool regions_intersect(u32 s1, u32 e1, u32 s2, u32 e2)
+{
+ return e1 >= s2 && e2 >= s1;
+}
+
+static bool intersects_occupied_region(const void *fdt, u32 start,
+ u32 end, struct regions *regions)
+{
+ int i;
+
+ if (regions_intersect(start, end, regions->zimage_start,
+ regions->zimage_start + regions->zimage_size))
+ return true;
+
+ if (regions_intersect(start, end, regions->initrd_start,
+ regions->initrd_start + regions->initrd_size))
+ return true;
+
+ if (regions_intersect(start, end, regions->dtb_start,
+ regions->dtb_start + regions->dtb_size))
+ return true;
+
+ for (i = 0; i < fdt_num_mem_rsv(fdt); i++) {
+ u64 base, size;
+
+ if (fdt_get_mem_rsv(fdt, i, &base, &size) < 0)
+ continue;
+ if (regions_intersect(start, end, base, base + size))
+ return true;
+ }
+
+ if (regions->reserved_mem != -FDT_ERR_NOTFOUND) {
+ int subnode;
+
+ for (subnode = fdt_first_subnode(fdt, regions->reserved_mem);
+ subnode != -FDT_ERR_NOTFOUND;
+ subnode = fdt_next_subnode(fdt, subnode)) {
+ const void *prop;
+
+ prop = fdt_getprop(fdt, subnode, "reg", NULL);
+ if (!prop)
+ continue;
+
+ /* TODO check for overlap */
+ }
+ }
+ return false;
+}
+
+static u32 count_suitable_regions(const void *fdt, struct regions *regions)
+{
+ u32 pa, ret = 0;
+
+ for (pa = regions->pa_start; pa < regions->pa_end; pa += SZ_2M) {
+ if (!intersects_occupied_region(fdt, pa,
+ pa + regions->image_size,
+ regions))
+ ret++;
+ }
+ return ret;
+}
+
+static u32 get_numbered_region(const void *fdt,
+ struct regions *regions,
+ int num)
+{
+ u32 pa;
+
+ for (pa = regions->pa_start; pa < regions->pa_end; pa += SZ_2M) {
+ if (!intersects_occupied_region(fdt, pa,
+ pa + regions->image_size,
+ regions))
+ if (num-- == 0)
+ return pa;
+ }
+ return regions->pa_start; /* should not happen */
+}
+
+static u32 get_memory_end(const void *fdt)
+{
+ int mem_node, address_cells, size_cells, len;
+ const unsigned char *reg;
+ const int *prop;
+ u64 memory_end = 0;
+
+ /* Look for a node called "memory" at the lowest level of the tree */
+ mem_node = fdt_path_offset (fdt, "/memory");
+ if (mem_node <= 0)
+ return 0;
+
+ /*
+ * Retrieve the #address-cells and #size-cells properties
+ * from the root node, or use the default if not provided.
+ */
+ address_cells = 1;
+ size_cells = 1;
+
+ prop = fdt_getprop (fdt, 0, "#address-cells", &len);
+ if (len == 4)
+ address_cells = fdt32_to_cpu (*prop);
+ prop = fdt_getprop (fdt, 0, "#size-cells", &len);
+ if (len == 4)
+ size_cells = fdt32_to_cpu (*prop);
+
+ /*
+ * Now find the 'reg' property of the /memory node, and iterate over
+ * the base/size pairs.
+ */
+ reg = fdt_getprop (fdt, mem_node, "reg", &len);
+ while (len >= 4 * (address_cells + size_cells)) {
+ u64 base, size;
+
+ if (address_cells == 1) {
+ base = fdt32_to_cpu(*(fdt32_t *)reg);
+ reg += 4;
+ len -= 4;
+ } else { /* assume address_cells == 2 */
+ base = fdt64_to_cpu(*(fdt64_t *)reg);
+ reg += 8;
+ len -= 8;
+ }
+ if (size_cells == 1) {
+ size = fdt32_to_cpu(*(fdt32_t *)reg);
+ reg += 4;
+ len -= 4;
+ } else { /* assume size_cells == 2 */
+ size = fdt64_to_cpu(*(fdt64_t *)reg);
+ reg += 8;
+ len -= 8;
+ }
+
+ memory_end = max(memory_end, base + size);
+ }
+ return min(memory_end, (u64)U32_MAX);
+}
+
+u32 kaslr_early_init(const void *fdt, u32 image_base, u32 image_size, u32 seed,
+ u32 zimage_start, u32 zimage_end)
+{
+ struct regions regions;
+ const char *command_line;
+ const void *prop;
+ const char *p;
+ int chosen, len;
+ u32 lowmem_top, num;
+
+ if (fdt_check_header(fdt))
+ return 0;
+
+ regions.pa_start = round_down(image_base, SZ_128M);
+
+ regions.dtb_start = (u32)fdt;
+ regions.dtb_size = fdt_totalsize(fdt);
+
+ regions.zimage_start = zimage_start;
+ regions.zimage_size = zimage_end - zimage_start;
+
+ chosen = fdt_path_offset(fdt, "/chosen");
+ if (chosen == -FDT_ERR_NOTFOUND)
+ return 0;
+
+ /* check for the presence of /chosen/kaslr-seed */
+ prop = fdt_getprop(fdt, chosen, "kaslr-seed", &len);
+ if (prop)
+ seed = *(u32 *)prop;
+
+ if (!IS_ENABLED(CONFIG_CMDLINE_FORCE))
+ command_line = get_command_line(fdt, chosen);
+
+ if (!command_line)
+ command_line = default_cmdline;
+
+ /* check the command line for the presence of 'nokaslr' */
+ p = strstr(command_line, "nokaslr");
+ if (p == command_line || (p > command_line && *(p - 1) == ' '))
+ return 0;
+
+ /* check the command line for the presence of 'vmalloc=' */
+ p = strstr(command_line, "vmalloc=");
+ if (p == command_line || (p > command_line && *(p - 1) == ' '))
+ lowmem_top = VMALLOC_END - __memparse(p + 8, NULL) -
+ VMALLOC_OFFSET;
+ else
+ lowmem_top = VMALLOC_DEFAULT_BASE;
+
+ regions.pa_end = lowmem_top - PAGE_OFFSET + regions.pa_start;
+
+ /* check for initrd on the command line */
+ regions.initrd_start = regions.initrd_size = 0;
+ p = strstr(command_line, "initrd=");
+ if (p == command_line || (p > command_line && *(p - 1) == ' ')) {
+ regions.initrd_start = __memparse(p + 7, &p);
+ if (*p++ == ',')
+ regions.initrd_size = __memparse(p, NULL);
+ if (regions.initrd_size == 0)
+ regions.initrd_start = 0;
+ }
+
+ /* ... or in /chosen */
+ if (regions.initrd_size == 0) {
+ prop = fdt_getprop(fdt, chosen, "linux,initrd-start", &len);
+ if (prop)
+ regions.initrd_start = (len == 4) ?
+ fdt32_to_cpu(*(fdt32_t *)prop) :
+ fdt32_to_cpu(*(fdt64_t *)prop);
+
+ prop = fdt_getprop(fdt, chosen, "linux,initrd-end", &len);
+ if (prop) {
+ regions.initrd_size = (len == 4) ?
+ fdt32_to_cpu(*(fdt32_t *)prop) :
+ fdt32_to_cpu(*(fdt64_t *)prop);
+ regions.initrd_size -= regions.initrd_start;
+ }
+ }
+
+ /* check the memory nodes for the size of the lowmem region */
+ regions.pa_end = min(regions.pa_end, get_memory_end(fdt));
+
+ regions.reserved_mem = fdt_path_offset(fdt, "/reserved-memory");
+ regions.image_size = round_up(image_size, SZ_2M);
+
+ /*
+ * Iterate over the physical memory range covered by the lowmem region
+ * in 2 MB increments, and count each offset at which we don't overlap
+ * with any of the reserved regions for the zImage itself, the DTB,
+ * the initrd and any regions described as reserved in the device tree.
+ * This produces a count, which we will scale by multiplying by a 16-bit
+ * random value and shifting right by 16 places.
+ * Using this random value, we iterate over the physical memory range
+ * again until we counted enough iterations, and return the offset we
+ * ended up at.
+ */
+ num = ((u16)seed * count_suitable_regions(fdt, ®ions)) >> 16;
+
+ kaslr_offset = get_numbered_region(fdt, ®ions, num) -
+ regions.pa_start;
+
+ return kaslr_offset;
+}
--
2.11.0
More information about the linux-arm-kernel
mailing list