Commit 60c8a2d4 authored by Ard Biesheuvel's avatar Ard Biesheuvel Committed by Felix Fu
Browse files

ARM: decompressor: add KASLR support

maillist inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/I8KNA9
CVE: NA

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/ardb/linux.git/commit/?h=arm-kaslr-latest&id=b152e5c5054c3937211a541be50d8a7c98a59974



-------------------------------------------------

Update <generated/utsversion.h> depend in OLK-6.6

Add support to the decompressor to load the kernel at a randomized
offset, and invoke the kernel proper while passing on the information
about the offset at which the kernel was loaded.

This implementation will extract some pseudo-randomness from the low
bits of the generic timer (if available), and use CRC-16 to combine
it with the build ID string and the device tree binary (which ideally
has a /chosen/kaslr-seed property, but may also have other properties
that differ between boots). This seed is used to select one of the
candidate offsets in the lowmem region that don't overlap the zImage
itself, the DTB, the initrd and /memreserve/s and/or /reserved-memory
nodes that should be left alone.

When booting via the UEFI stub, it is left up to the firmware to supply
a suitable seed and select an offset.

Cc: Russell King <linux@armlinux.org.uk>
Signed-off-by: default avatarArd Biesheuvel <ard.biesheuvel@linaro.org>
Signed-off-by: default avatarCui GaoSheng <cuigaosheng1@huawei.com>
Signed-off-by: default avatarYe Bin <yebin10@huawei.com>

Conflicts:
    Merge OLK-5.10 fix patch 1cd67b23
    Merge OLK-5.10 fix patch bbfbad16
    Merge OLK-5.10 fix patch a67342ed
    Merge OLK-5.10 fix patch 53a643ec
    Merge OLK-5.10 fix patch 4699c8e4
    Merge OLK-5.10 fix patch 4c19869f

Signed-off-by: default avatarFelix Fu <fuzhen5@huawei.com>
parent f9a36bda
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -84,10 +84,17 @@ compress-$(CONFIG_KERNEL_LZ4) = lz4_with_size

libfdt_objs := fdt_rw.o fdt_ro.o fdt_wip.o fdt.o

ifneq ($(CONFIG_ARM_ATAG_DTB_COMPAT)$(CONFIG_RANDOMIZE_BASE),)
OBJS	+= $(libfdt_objs)
ifeq ($(CONFIG_ARM_ATAG_DTB_COMPAT),y)
CFLAGS_REMOVE_atags_to_fdt.o += -Wframe-larger-than=${CONFIG_FRAME_WARN}
CFLAGS_atags_to_fdt.o += -Wframe-larger-than=1280
OBJS	+= $(libfdt_objs) atags_to_fdt.o
OBJS	+= atags_to_fdt.o
endif
ifeq ($(CONFIG_RANDOMIZE_BASE),y)
OBJS	+= kaslr.o
CFLAGS_kaslr.o := -I $(srctree)/scripts/dtc/libfdt
endif
endif
ifeq ($(CONFIG_USE_OF),y)
OBJS	+= $(libfdt_objs) fdt_check_mem_start.o
+88 −0
Original line number Diff line number Diff line
@@ -174,6 +174,25 @@
#endif
		.endm

		.macro  record_seed
#ifdef CONFIG_RANDOMIZE_BASE
		sub	ip, r1, ip, ror #1	@ poor man's kaslr seed, will
		sub	ip, r2, ip, ror #2	@ be superseded by kaslr-seed
		sub	ip, r3, ip, ror #3	@ from /chosen if present
		sub	ip, r4, ip, ror #5
		sub	ip, r5, ip, ror #8
		sub	ip, r6, ip, ror #13
		sub	ip, r7, ip, ror #21
		sub	ip, r8, ip, ror #3
		sub	ip, r9, ip, ror #24
		sub	ip, r10, ip, ror #27
		sub	ip, r11, ip, ror #19
		sub	ip, r13, ip, ror #14
		sub	ip, r14, ip, ror #2
		str_l	ip, __kaslr_seed, r9
#endif
		.endm

		.section ".start", "ax"
/*
 * sort out different calling conventions
@@ -222,6 +241,7 @@ start:
		__EFI_HEADER
1:
 ARM_BE8(	setend	be		)	@ go BE8 if compiled for BE8
		record_seed
 AR_CLASS(	mrs	r9, cpsr	)
#ifdef CONFIG_ARM_VIRT_EXT
		bl	__hyp_stub_install	@ get into SVC mode, reversibly
@@ -446,6 +466,38 @@ restart: adr r0, LC1
dtb_check_done:
#endif

#ifdef CONFIG_RANDOMIZE_BASE
		ldr	r1, __kaslr_offset	@ check if the kaslr_offset is
		cmp	r1, #0			@ already set
		bne	1f

		stmfd	sp!, {r0-r3, ip, lr}
		adr_l	r2, _text		@ start of zImage
		stmfd	sp!, {r2, r8, r10}	@ pass stack arguments

		ldr_l	r3, __kaslr_seed
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_V6K) || defined(CONFIG_CPU_V7)
		/*
		 * Get some pseudo-entropy from the low bits of the generic
		 * timer if it is implemented.
		 */
		mrc	p15, 0, r1, c0, c1, 1	@ read ID_PFR1 register
		tst	r1, #0x10000		@ have generic timer?
		mrrcne	p15, 1, r3, r1, c14	@ read CNTVCT
#endif
		adr_l	r0, __kaslr_offset	@ pass &__kaslr_offset in r0
		mov	r1, r4			@ pass base address
		mov	r2, r9			@ pass decompressed image size
		eor	r3, r3, r3, ror #16	@ pass pseudorandom seed
		bl	kaslr_early_init
		add	sp, sp, #12
		cmp	r0, #0
		addne	r4, r4, r0		@ add offset to base address
		ldmfd	sp!, {r0-r3, ip, lr}
		bne	restart
1:
#endif

/*
 * Check to see if we will overwrite ourselves.
 *   r4  = final kernel address (possibly with LSB set)
@@ -1439,10 +1491,46 @@ __enter_kernel:
		mov	r0, #0			@ must be 0
		mov	r1, r7			@ restore architecture number
		mov	r2, r8			@ restore atags pointer
#ifdef CONFIG_RANDOMIZE_BASE
		ldr	r3, __kaslr_offset
		add	r4, r4, #4		@ skip first instruction
#endif
 ARM(		mov	pc, r4		)	@ call kernel
 M_CLASS(	add	r4, r4, #1	)	@ enter in Thumb mode for M class
 THUMB(		bx	r4		)	@ entry point is always ARM for A/R classes

#ifdef CONFIG_RANDOMIZE_BASE
		/*
		 * Minimal implementation of CRC-16 that does not use a
		 * lookup table and uses 32-bit wide loads, so it still
		 * performs reasonably well with the D-cache off. Equivalent
		 * to lib/crc16.c for input sizes that are 4 byte multiples.
		 */
ENTRY(__crc16)
		push	{r4, lr}
		ldr	r3, =0xa001     @ CRC-16 polynomial
0:		subs	r2, r2, #4
		popmi	{r4, pc}
		ldr	r4, [r1], #4
#ifdef __ARMEB__
		eor	ip, r4, r4, ror #16     @ endian swap
		bic	ip, ip, #0x00ff0000
		mov	r4, r4, ror #8
		eor	r4, r4, ip, lsr #8
#endif
		eor	r0, r0, r4
		.rept	32
		lsrs	r0, r0, #1
		eorcs	r0, r0, r3
		.endr
		b	0b
ENDPROC(__crc16)

		.align	2
__kaslr_seed:	.long	0
__kaslr_offset:	.long	0
#endif

reloc_code_end:

#ifdef CONFIG_EFI_STUB
+433 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 Linaro Ltd;  <ard.biesheuvel@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 <linux/libfdt_env.h>
#include <libfdt.h>
#include <linux/types.h>
#include <generated/compile.h>
#include <generated/utsrelease.h>
#include <generated/utsversion.h>
#include <linux/pgtable.h>

#include CONFIG_UNCOMPRESS_INCLUDE

struct regions {
	u32 pa_start;
	u32 pa_end;
	u32 image_size;
	u32 zimage_start;
	u32 zimage_size;
	u32 dtb_start;
	u32 dtb_size;
	u32 initrd_start;
	u32 initrd_size;
	int reserved_mem;
	int reserved_mem_addr_cells;
	int reserved_mem_size_cells;
};

extern u32 __crc16(u32 crc, u32 const input[], int byte_count);

static u32 __memparse(const char *val, const char **retptr)
{
	int base = 10;
	u32 ret = 0;

	if (*val == '0') {
		val++;
		if (*val == 'x' || *val == 'X') {
			val++;
			base = 16;
		} else {
			base = 8;
		}
	}

	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;
			fallthrough;
		case 'm':
		case 'M':
			ret <<= 10;
			fallthrough;
		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_reserved_region(const void *fdt, u32 start,
				       u32 end, struct regions *regions)
{
	int subnode, len, i;
	u64 base, size;

	/* check for overlap with /memreserve/ entries */
	for (i = 0; i < fdt_num_mem_rsv(fdt); i++) {
		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 < 0)
		return false;

	/* check for overlap with static reservations in /reserved-memory */
	for (subnode = fdt_first_subnode(fdt, regions->reserved_mem);
	     subnode >= 0;
	     subnode = fdt_next_subnode(fdt, subnode)) {
		const fdt32_t *reg;

		len = 0;
		reg = fdt_getprop(fdt, subnode, "reg", &len);
		while (len >= (regions->reserved_mem_addr_cells +
			       regions->reserved_mem_size_cells)) {

			base = fdt32_to_cpu(reg[0]);
			if (regions->reserved_mem_addr_cells == 2)
				base = (base << 32) | fdt32_to_cpu(reg[1]);

			reg += regions->reserved_mem_addr_cells;
			len -= 4 * regions->reserved_mem_addr_cells;

			size = fdt32_to_cpu(reg[0]);
			if (regions->reserved_mem_size_cells == 2)
				size = (size << 32) | fdt32_to_cpu(reg[1]);

			reg += regions->reserved_mem_size_cells;
			len -= 4 * regions->reserved_mem_size_cells;

			if (base >= regions->pa_end)
				continue;

			if (regions_intersect(start, end, base,
					      min(base + size, (u64)U32_MAX)))
				return true;
		}
	}
	return false;
}

static bool intersects_occupied_region(const void *fdt, u32 start,
				       u32 end, struct regions *regions)
{
	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;

	return intersects_reserved_region(fdt, start, end, regions);
}

static u32 count_suitable_regions(const void *fdt, struct regions *regions,
				  u32 *bitmap)
{
	u32 pa, i = 0, ret = 0;

	for (pa = regions->pa_start; pa < regions->pa_end; pa += SZ_2M, i++) {
		if (!intersects_occupied_region(fdt, pa,
						pa + regions->image_size,
						regions)) {
			ret++;
		} else {
			/* set 'occupied' bit */
			bitmap[i >> 5] |= BIT(i & 0x1f);
		}
	}
	return ret;
}

/* The caller ensures that num is within the range of regions.*/
static u32 get_region_number(u32 num, u32 *bitmap, u32 size)
{
	u32 i, cnt = size * BITS_PER_BYTE * sizeof(u32);

	for (i = 0; i < cnt; i++) {
		if (bitmap[i >> 5] & BIT(i & 0x1f))
			continue;
		if (num-- == 0)
			break;
	}

	return i;
}

static void get_cell_sizes(const void *fdt, int node, int *addr_cells,
			   int *size_cells)
{
	const int *prop;
	int len;

	/*
	 * Retrieve the #address-cells and #size-cells properties
	 * from the 'node', or use the default if not provided.
	 */
	*addr_cells = *size_cells = 1;

	prop = fdt_getprop(fdt, node, "#address-cells", &len);
	if (len == 4)
		*addr_cells = fdt32_to_cpu(*prop);
	prop = fdt_getprop(fdt, node, "#size-cells", &len);
	if (len == 4)
		*size_cells = fdt32_to_cpu(*prop);
}

/*
 * Original method only consider the first memory node in dtb,
 * but there may be more than one memory nodes, we only consider
 * the memory node zImage exists.
 */
static u32 get_memory_end(const void *fdt, u32 zimage_start)
{
	int mem_node, address_cells, size_cells, len;
	const fdt32_t *reg;

	/* 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;

	get_cell_sizes(fdt, 0, &address_cells, &size_cells);

	while (mem_node >= 0) {
		/*
		 * Now find the 'reg' property of the /memory node, and iterate over
		 * the base/size pairs.
		 */
		len = 0;
		reg = fdt_getprop(fdt, mem_node, "reg", &len);
		while (len >= 4 * (address_cells + size_cells)) {
			u64 base, size;
			base = fdt32_to_cpu(reg[0]);
			if (address_cells == 2)
				base = (base << 32) | fdt32_to_cpu(reg[1]);

			reg += address_cells;
			len -= 4 * address_cells;

			size = fdt32_to_cpu(reg[0]);
			if (size_cells == 2)
				size = (size << 32) | fdt32_to_cpu(reg[1]);

			reg += size_cells;
			len -= 4 * size_cells;

			/* Get the base and size of the zimage memory node */
			if (zimage_start >= base && zimage_start < base + size)
				return base + size;
		}
		/* If current memory node is not the one zImage exists, then traverse next memory node. */
		mem_node = fdt_node_offset_by_prop_value(fdt, mem_node, "device_type", "memory", sizeof("memory"));
	}

	return 0;
}

static char *__strstr(const char *s1, const char *s2, int l2)
{
	int l1;

	l1 = strlen(s1);
	while (l1 >= l2) {
		l1--;
		if (!memcmp(s1, s2, l2))
			return (char *)s1;
		s1++;
	}
	return NULL;
}

static const char *get_cmdline_param(const char *cmdline, const char *param,
				     int param_size)
{
	static const char default_cmdline[] = CONFIG_CMDLINE;
	const char *p;

	if (!IS_ENABLED(CONFIG_CMDLINE_FORCE) && cmdline != NULL) {
		p = __strstr(cmdline, param, param_size);
		if (p == cmdline ||
		    (p > cmdline && *(p - 1) == ' '))
			return p;
	}

	if (IS_ENABLED(CONFIG_CMDLINE_FORCE)  ||
	    IS_ENABLED(CONFIG_CMDLINE_EXTEND)) {
		p = __strstr(default_cmdline, param, param_size);
		if (p == default_cmdline ||
		    (p > default_cmdline && *(p - 1) == ' '))
			return p;
	}
	return NULL;
}

u32 kaslr_early_init(u32 *kaslr_offset, u32 image_base, u32 image_size,
		     u32 seed, u32 zimage_start, const void *fdt,
		     u32 zimage_end)
{
	static const char __aligned(4) build_id[] = UTS_VERSION UTS_RELEASE;
	u32 bitmap[(VMALLOC_END - PAGE_OFFSET) / SZ_2M / 32] = {};
	struct regions regions;
	const char *command_line;
	const char *p;
	int chosen, len;
	u32 lowmem_top, count, num, mem_fdt;

	if (IS_ENABLED(CONFIG_EFI_STUB)) {
		extern u32 __efi_kaslr_offset;

		if (__efi_kaslr_offset == U32_MAX)
			return 0;
	}

	if (fdt_check_header(fdt))
		return 0;

	chosen = fdt_path_offset(fdt, "/chosen");
	if (chosen < 0)
		return 0;

	command_line = fdt_getprop(fdt, chosen, "bootargs", &len);

	/* check the command line for the presence of 'nokaslr' */
	p = get_cmdline_param(command_line, "nokaslr", sizeof("nokaslr") - 1);
	if (p != NULL)
		return 0;

	/* check the command line for the presence of 'vmalloc=' */
	p = get_cmdline_param(command_line, "vmalloc=", sizeof("vmalloc=") - 1);
	if (p != NULL)
		lowmem_top = VMALLOC_END - __memparse(p + 8, NULL) -
			     VMALLOC_OFFSET;
	else
		lowmem_top = VMALLOC_DEFAULT_BASE;

	regions.image_size = image_base % SZ_128M + round_up(image_size, SZ_2M);
	regions.pa_start = round_down(image_base, SZ_128M);
	regions.pa_end = lowmem_top - PAGE_OFFSET + regions.pa_start;
	regions.zimage_start = zimage_start;
	regions.zimage_size = zimage_end - zimage_start;
	regions.dtb_start = (u32)fdt;
	regions.dtb_size = fdt_totalsize(fdt);

	/*
	 * Stir up the seed a bit by taking the CRC of the DTB:
	 * hopefully there's a /chosen/kaslr-seed in there.
	 */
	seed = __crc16(seed, fdt, regions.dtb_size);

	/* stir a bit more using data that changes between builds */
	seed = __crc16(seed, (u32 *)build_id, sizeof(build_id));

	/* check for initrd on the command line */
	regions.initrd_start = regions.initrd_size = 0;
	p = get_cmdline_param(command_line, "initrd=", sizeof("initrd=") - 1);
	if (p != NULL) {
		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) {
		const fdt32_t *prop;
		u64 start = 0, end = 0;

		prop = fdt_getprop(fdt, chosen, "linux,initrd-start", &len);
		if (prop) {
			start = fdt32_to_cpu(prop[0]);
			if (len == 8)
				start = (start << 32) | fdt32_to_cpu(prop[1]);
		}

		prop = fdt_getprop(fdt, chosen, "linux,initrd-end", &len);
		if (prop) {
			end = fdt32_to_cpu(prop[0]);
			if (len == 8)
				end = (end << 32) | fdt32_to_cpu(prop[1]);
		}
		if (start != 0 && end != 0 && start < U32_MAX) {
			regions.initrd_start = start;
			regions.initrd_size = min_t(u64, end, U32_MAX) - start;
		}
	}

	/*
	 * check the memory nodes for the size of the lowmem region, traverse
	 * all memory nodes to find the node in which zImage exists, we
	 * randomize kernel only in the one zImage exists.
	 */
	mem_fdt = get_memory_end(fdt, zimage_start);
	if (mem_fdt)
		regions.pa_end = min(regions.pa_end, mem_fdt) - regions.image_size;
	else
		regions.pa_end = regions.pa_end - regions.image_size;

	/* check for a reserved-memory node and record its cell sizes */
	regions.reserved_mem = fdt_path_offset(fdt, "/reserved-memory");
	if (regions.reserved_mem >= 0)
		get_cell_sizes(fdt, regions.reserved_mem,
			       &regions.reserved_mem_addr_cells,
			       &regions.reserved_mem_size_cells);

	/*
	 * 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.
	 * If the region does overlap, set the respective bit in the bitmap[].
	 * Using this random value, we go over the bitmap and count zero bits
	 * until we counted enough iterations, and return the offset we ended
	 * up at.
	 */
	count = count_suitable_regions(fdt, &regions, bitmap);

	num = ((u16)seed * count) >> 16;

	*kaslr_offset = get_region_number(num, bitmap, sizeof(bitmap) / sizeof(u32)) * SZ_2M;

	return *kaslr_offset;
}
+2 −1
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@
 */
#define ARM_ASSERTS							\
	.plt : {							\
		*(.iplt) *(.rel.iplt) *(.iplt) *(.igot.plt)		\
		*(.iplt) * (.rel.iplt) * (.iplt) * (.igot.plt) * (.plt)	\
	}								\
	ASSERT(SIZEOF(.plt) == 0,					\
	       "Unexpected run-time procedure linkages detected!")
@@ -105,6 +105,7 @@
		ARM_STUBS_TEXT						\
		. = ALIGN(4);						\
		*(.got)			/* Global offset table */	\
		*(.got.plt)						\
		ARM_CPU_KEEP(PROC_INFO)

/* Stack unwinding tables */
+14 −1
Original line number Diff line number Diff line
@@ -69,6 +69,10 @@ SECTIONS
#endif
	_etext = .;			/* End of text section */

	.gnu.hash : {
		*(.gnu.hash)
	}

	RO_DATA(PAGE_SIZE)

	. = ALIGN(4);
@@ -116,7 +120,7 @@ SECTIONS
#endif
	.rel.dyn : ALIGN(8) {
		__rel_begin = .;
		*(.rel .rel.* .rel.dyn)
		*(.rel .rel.* .rel.dyn .rel*)
	}
	__rel_end = ADDR(.rel.dyn) + SIZEOF(.rel.dyn);

@@ -149,6 +153,15 @@ SECTIONS

	_sdata = .;
	RW_DATA(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)

	.data.rel.local : {
		*(.data.rel.local)
	}

	.data.rel.ro : {
		*(.data.rel.ro)
	}

	_edata = .;

	BSS_SECTION(0, 0, 0)