Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0215061 authored by Kees Cook's avatar Kees Cook Committed by H. Peter Anvin
Browse files

x86, relocs: Move ELF relocation handling to C



Moves the relocation handling into C, after decompression. This requires
that the decompressed size is passed to the decompression routine as
well so that relocations can be found. Only kernels that need relocation
support will use the code (currently just x86_32), but this is laying
the ground work for 64-bit using it in support of KASLR.

Based on work by Neill Clift and Michael Davidson.

Signed-off-by: default avatarKees Cook <keescook@chromium.org>
Link: http://lkml.kernel.org/r/20130708161517.GA4832@www.outflux.net


Acked-by: default avatarZhang Yanfei <zhangyanfei@cn.fujitsu.com>
Signed-off-by: default avatarH. Peter Anvin <hpa@linux.intel.com>
parent c095ba72
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -1716,9 +1716,10 @@ config X86_NEED_RELOCS
	depends on X86_32 && RELOCATABLE

config PHYSICAL_ALIGN
	hex "Alignment value to which kernel should be aligned" if X86_32
	hex "Alignment value to which kernel should be aligned"
	default "0x1000000"
	range 0x2000 0x1000000
	range 0x2000 0x1000000 if X86_32
	range 0x200000 0x1000000 if X86_64
	---help---
	  This value puts the alignment restrictions on physical address
	  where kernel is loaded and run from. Kernel is compiled for an
@@ -1736,6 +1737,9 @@ config PHYSICAL_ALIGN
	  end result is that kernel runs from a physical address meeting
	  above alignment restrictions.

	  On 32-bit this value must be a multiple of 0x2000. On 64-bit
	  this value must be a multiple of 0x200000.

	  Don't change this unless you know what you are doing.

config HOTPLUG_CPU
+4 −4
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@ endif
# e.g.: obj-y += foo_$(BITS).o
export BITS

ifdef CONFIG_X86_NEED_RELOCS
        LDFLAGS_vmlinux := --emit-relocs
endif

ifeq ($(CONFIG_X86_32),y)
        BITS := 32
        UTS_MACHINE := i386
@@ -25,10 +29,6 @@ ifeq ($(CONFIG_X86_32),y)
        KBUILD_AFLAGS += $(biarch)
        KBUILD_CFLAGS += $(biarch)

        ifdef CONFIG_RELOCATABLE
                LDFLAGS_vmlinux := --emit-relocs
        endif

        KBUILD_CFLAGS += -msoft-float -mregparm=3 -freg-struct-return

        # Never want PIC in a 32-bit kernel, prevent breakage with GCC built
+3 −28
Original line number Diff line number Diff line
@@ -181,8 +181,9 @@ relocated:
/*
 * Do the decompression, and jump to the new kernel..
 */
	leal	z_extract_offset_negative(%ebx), %ebp
				/* push arguments for decompress_kernel: */
	pushl	$z_output_len	/* decompressed length */
	leal	z_extract_offset_negative(%ebx), %ebp
	pushl	%ebp		/* output address */
	pushl	$z_input_len	/* input_len */
	leal	input_data(%ebx), %eax
@@ -191,33 +192,7 @@ relocated:
	pushl	%eax		/* heap area */
	pushl	%esi		/* real mode pointer */
	call	decompress_kernel
	addl	$20, %esp

#if CONFIG_RELOCATABLE
/*
 * Find the address of the relocations.
 */
	leal	z_output_len(%ebp), %edi

/*
 * Calculate the delta between where vmlinux was compiled to run
 * and where it was actually loaded.
 */
	movl	%ebp, %ebx
	subl	$LOAD_PHYSICAL_ADDR, %ebx
	jz	2f	/* Nothing to be done if loaded at compiled addr. */
/*
 * Process relocations.
 */

1:	subl	$4, %edi
	movl	(%edi), %ecx
	testl	%ecx, %ecx
	jz	2f
	addl	%ebx, -__PAGE_OFFSET(%ebx, %ecx)
	jmp	1b
2:
#endif
	addl	$24, %esp

/*
 * Jump to the decompressed kernel.
+1 −0
Original line number Diff line number Diff line
@@ -338,6 +338,7 @@ relocated:
	leaq	input_data(%rip), %rdx  /* input_data */
	movl	$z_input_len, %ecx	/* input_len */
	movq	%rbp, %r8		/* output target address */
	movq	$z_output_len, %r9	/* decompressed length */
	call	decompress_kernel
	popq	%rsi

+76 −1
Original line number Diff line number Diff line
@@ -271,6 +271,79 @@ static void error(char *x)
		asm("hlt");
}

#if CONFIG_X86_NEED_RELOCS
static void handle_relocations(void *output, unsigned long output_len)
{
	int *reloc;
	unsigned long delta, map, ptr;
	unsigned long min_addr = (unsigned long)output;
	unsigned long max_addr = min_addr + output_len;

	/*
	 * Calculate the delta between where vmlinux was linked to load
	 * and where it was actually loaded.
	 */
	delta = min_addr - LOAD_PHYSICAL_ADDR;
	if (!delta) {
		debug_putstr("No relocation needed... ");
		return;
	}
	debug_putstr("Performing relocations... ");

	/*
	 * The kernel contains a table of relocation addresses. Those
	 * addresses have the final load address of the kernel in virtual
	 * memory. We are currently working in the self map. So we need to
	 * create an adjustment for kernel memory addresses to the self map.
	 * This will involve subtracting out the base address of the kernel.
	 */
	map = delta - __START_KERNEL_map;

	/*
	 * Process relocations: 32 bit relocations first then 64 bit after.
	 * Two sets of binary relocations are added to the end of the kernel
	 * before compression. Each relocation table entry is the kernel
	 * address of the location which needs to be updated stored as a
	 * 32-bit value which is sign extended to 64 bits.
	 *
	 * Format is:
	 *
	 * kernel bits...
	 * 0 - zero terminator for 64 bit relocations
	 * 64 bit relocation repeated
	 * 0 - zero terminator for 32 bit relocations
	 * 32 bit relocation repeated
	 *
	 * So we work backwards from the end of the decompressed image.
	 */
	for (reloc = output + output_len - sizeof(*reloc); *reloc; reloc--) {
		int extended = *reloc;
		extended += map;

		ptr = (unsigned long)extended;
		if (ptr < min_addr || ptr > max_addr)
			error("32-bit relocation outside of kernel!\n");

		*(uint32_t *)ptr += delta;
	}
#ifdef CONFIG_X86_64
	for (reloc--; *reloc; reloc--) {
		long extended = *reloc;
		extended += map;

		ptr = (unsigned long)extended;
		if (ptr < min_addr || ptr > max_addr)
			error("64-bit relocation outside of kernel!\n");

		*(uint64_t *)ptr += delta;
	}
#endif
}
#else
static inline void handle_relocations(void *output, unsigned long output_len)
{ }
#endif

static void parse_elf(void *output)
{
#ifdef CONFIG_X86_64
@@ -325,7 +398,8 @@ static void parse_elf(void *output)
asmlinkage void decompress_kernel(void *rmode, memptr heap,
				  unsigned char *input_data,
				  unsigned long input_len,
				  unsigned char *output)
				  unsigned char *output,
				  unsigned long output_len)
{
	real_mode = rmode;

@@ -365,6 +439,7 @@ asmlinkage void decompress_kernel(void *rmode, memptr heap,
	debug_putstr("\nDecompressing Linux... ");
	decompress(input_data, input_len, NULL, NULL, output, NULL, error);
	parse_elf(output);
	handle_relocations(output, output_len);
	debug_putstr("done.\nBooting the kernel.\n");
	return;
}
Loading