]> git.proxmox.com Git - mirror_ubuntu-eoan-kernel.git/commitdiff
x86, relocs: Move ELF relocation handling to C
authorKees Cook <keescook@chromium.org>
Mon, 8 Jul 2013 16:15:17 +0000 (09:15 -0700)
committerH. Peter Anvin <hpa@linux.intel.com>
Thu, 8 Aug 2013 04:00:04 +0000 (21:00 -0700)
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: Kees Cook <keescook@chromium.org>
Link: http://lkml.kernel.org/r/20130708161517.GA4832@www.outflux.net
Acked-by: Zhang Yanfei <zhangyanfei@cn.fujitsu.com>
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
arch/x86/Kconfig
arch/x86/Makefile
arch/x86/boot/compressed/head_32.S
arch/x86/boot/compressed/head_64.S
arch/x86/boot/compressed/misc.c
arch/x86/include/asm/page_32_types.h
arch/x86/include/asm/page_64_types.h
arch/x86/include/asm/page_types.h

index b32ebf92b0ce96b5798bdf2a48043986be6ba6ea..8002668d60a140d8d6e7ab59167643615f66acfe 100644 (file)
@@ -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
index 07639c656fcd89b1212e14b57bfc910549a44407..41250fb33985e179f63b399cfb6a2d10c93a4e29 100644 (file)
@@ -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
index 1e3184f6072f913250b3b18371978a4fd1b3f63d..5d6f6891b1881ee812c060bc730915d73d4d7f65 100644 (file)
@@ -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.
index 06e71c2c16bf012c006fab90caa72bb9d25cb8db..c337422b575de190280334ef5cd61214441644f9 100644 (file)
@@ -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
 
index 0319c88290a52f6ea314dd03838333ef2d18b4b8..434f077d2c4d7b664216e6adaa22ac44ea51786a 100644 (file)
@@ -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;
 }
index ef17af0134750203f803f5a665a86178abfa2107..f48b17df42249e45cca9ef6de99bfd43083507cd 100644 (file)
@@ -15,6 +15,8 @@
  */
 #define __PAGE_OFFSET          _AC(CONFIG_PAGE_OFFSET, UL)
 
+#define __START_KERNEL_map     __PAGE_OFFSET
+
 #define THREAD_SIZE_ORDER      1
 #define THREAD_SIZE            (PAGE_SIZE << THREAD_SIZE_ORDER)
 
index 6c896fbe21db50fca525bb321aceab47172ee9d4..43dcd804ebd5732aea40415e6f4a419e363701c9 100644 (file)
  */
 #define __PAGE_OFFSET           _AC(0xffff880000000000, UL)
 
-#define __PHYSICAL_START       ((CONFIG_PHYSICAL_START +               \
-                                 (CONFIG_PHYSICAL_ALIGN - 1)) &        \
-                                ~(CONFIG_PHYSICAL_ALIGN - 1))
-
-#define __START_KERNEL         (__START_KERNEL_map + __PHYSICAL_START)
 #define __START_KERNEL_map     _AC(0xffffffff80000000, UL)
 
 /* See Documentation/x86/x86_64/mm.txt for a description of the memory map. */
index 54c97879195e7c5137d2a45fbda51c40a1c41be2..f97fbe3abb67f5059d4e6f0a37261d6113df19de 100644 (file)
        (((current->personality & READ_IMPLIES_EXEC) ? VM_EXEC : 0 ) | \
         VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC)
 
+#define __PHYSICAL_START       ALIGN(CONFIG_PHYSICAL_START, \
+                                     CONFIG_PHYSICAL_ALIGN)
+
+#define __START_KERNEL         (__START_KERNEL_map + __PHYSICAL_START)
+
 #ifdef CONFIG_X86_64
 #include <asm/page_64_types.h>
 #else