Skip to content

Commit a021506

Browse files
keesH. Peter Anvin
authored andcommitted
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: 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>
1 parent c095ba7 commit a021506

File tree

8 files changed

+97
-40
lines changed

8 files changed

+97
-40
lines changed

arch/x86/Kconfig

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,9 +1716,10 @@ config X86_NEED_RELOCS
17161716
depends on X86_32 && RELOCATABLE
17171717

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

1740+
On 32-bit this value must be a multiple of 0x2000. On 64-bit
1741+
this value must be a multiple of 0x200000.
1742+
17391743
Don't change this unless you know what you are doing.
17401744

17411745
config HOTPLUG_CPU

arch/x86/Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ endif
1616
# e.g.: obj-y += foo_$(BITS).o
1717
export BITS
1818

19+
ifdef CONFIG_X86_NEED_RELOCS
20+
LDFLAGS_vmlinux := --emit-relocs
21+
endif
22+
1923
ifeq ($(CONFIG_X86_32),y)
2024
BITS := 32
2125
UTS_MACHINE := i386
@@ -25,10 +29,6 @@ ifeq ($(CONFIG_X86_32),y)
2529
KBUILD_AFLAGS += $(biarch)
2630
KBUILD_CFLAGS += $(biarch)
2731

28-
ifdef CONFIG_RELOCATABLE
29-
LDFLAGS_vmlinux := --emit-relocs
30-
endif
31-
3232
KBUILD_CFLAGS += -msoft-float -mregparm=3 -freg-struct-return
3333

3434
# Never want PIC in a 32-bit kernel, prevent breakage with GCC built

arch/x86/boot/compressed/head_32.S

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,9 @@ relocated:
181181
/*
182182
* Do the decompression, and jump to the new kernel..
183183
*/
184-
leal z_extract_offset_negative(%ebx), %ebp
185184
/* push arguments for decompress_kernel: */
185+
pushl $z_output_len /* decompressed length */
186+
leal z_extract_offset_negative(%ebx), %ebp
186187
pushl %ebp /* output address */
187188
pushl $z_input_len /* input_len */
188189
leal input_data(%ebx), %eax
@@ -191,33 +192,7 @@ relocated:
191192
pushl %eax /* heap area */
192193
pushl %esi /* real mode pointer */
193194
call decompress_kernel
194-
addl $20, %esp
195-
196-
#if CONFIG_RELOCATABLE
197-
/*
198-
* Find the address of the relocations.
199-
*/
200-
leal z_output_len(%ebp), %edi
201-
202-
/*
203-
* Calculate the delta between where vmlinux was compiled to run
204-
* and where it was actually loaded.
205-
*/
206-
movl %ebp, %ebx
207-
subl $LOAD_PHYSICAL_ADDR, %ebx
208-
jz 2f /* Nothing to be done if loaded at compiled addr. */
209-
/*
210-
* Process relocations.
211-
*/
212-
213-
1: subl $4, %edi
214-
movl (%edi), %ecx
215-
testl %ecx, %ecx
216-
jz 2f
217-
addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
218-
jmp 1b
219-
2:
220-
#endif
195+
addl $24, %esp
221196

222197
/*
223198
* Jump to the decompressed kernel.

arch/x86/boot/compressed/head_64.S

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ relocated:
338338
leaq input_data(%rip), %rdx /* input_data */
339339
movl $z_input_len, %ecx /* input_len */
340340
movq %rbp, %r8 /* output target address */
341+
movq $z_output_len, %r9 /* decompressed length */
341342
call decompress_kernel
342343
popq %rsi
343344

arch/x86/boot/compressed/misc.c

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,79 @@ static void error(char *x)
271271
asm("hlt");
272272
}
273273

274+
#if CONFIG_X86_NEED_RELOCS
275+
static void handle_relocations(void *output, unsigned long output_len)
276+
{
277+
int *reloc;
278+
unsigned long delta, map, ptr;
279+
unsigned long min_addr = (unsigned long)output;
280+
unsigned long max_addr = min_addr + output_len;
281+
282+
/*
283+
* Calculate the delta between where vmlinux was linked to load
284+
* and where it was actually loaded.
285+
*/
286+
delta = min_addr - LOAD_PHYSICAL_ADDR;
287+
if (!delta) {
288+
debug_putstr("No relocation needed... ");
289+
return;
290+
}
291+
debug_putstr("Performing relocations... ");
292+
293+
/*
294+
* The kernel contains a table of relocation addresses. Those
295+
* addresses have the final load address of the kernel in virtual
296+
* memory. We are currently working in the self map. So we need to
297+
* create an adjustment for kernel memory addresses to the self map.
298+
* This will involve subtracting out the base address of the kernel.
299+
*/
300+
map = delta - __START_KERNEL_map;
301+
302+
/*
303+
* Process relocations: 32 bit relocations first then 64 bit after.
304+
* Two sets of binary relocations are added to the end of the kernel
305+
* before compression. Each relocation table entry is the kernel
306+
* address of the location which needs to be updated stored as a
307+
* 32-bit value which is sign extended to 64 bits.
308+
*
309+
* Format is:
310+
*
311+
* kernel bits...
312+
* 0 - zero terminator for 64 bit relocations
313+
* 64 bit relocation repeated
314+
* 0 - zero terminator for 32 bit relocations
315+
* 32 bit relocation repeated
316+
*
317+
* So we work backwards from the end of the decompressed image.
318+
*/
319+
for (reloc = output + output_len - sizeof(*reloc); *reloc; reloc--) {
320+
int extended = *reloc;
321+
extended += map;
322+
323+
ptr = (unsigned long)extended;
324+
if (ptr < min_addr || ptr > max_addr)
325+
error("32-bit relocation outside of kernel!\n");
326+
327+
*(uint32_t *)ptr += delta;
328+
}
329+
#ifdef CONFIG_X86_64
330+
for (reloc--; *reloc; reloc--) {
331+
long extended = *reloc;
332+
extended += map;
333+
334+
ptr = (unsigned long)extended;
335+
if (ptr < min_addr || ptr > max_addr)
336+
error("64-bit relocation outside of kernel!\n");
337+
338+
*(uint64_t *)ptr += delta;
339+
}
340+
#endif
341+
}
342+
#else
343+
static inline void handle_relocations(void *output, unsigned long output_len)
344+
{ }
345+
#endif
346+
274347
static void parse_elf(void *output)
275348
{
276349
#ifdef CONFIG_X86_64
@@ -325,7 +398,8 @@ static void parse_elf(void *output)
325398
asmlinkage void decompress_kernel(void *rmode, memptr heap,
326399
unsigned char *input_data,
327400
unsigned long input_len,
328-
unsigned char *output)
401+
unsigned char *output,
402+
unsigned long output_len)
329403
{
330404
real_mode = rmode;
331405

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

arch/x86/include/asm/page_32_types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
#define __PAGE_OFFSET _AC(CONFIG_PAGE_OFFSET, UL)
1717

18+
#define __START_KERNEL_map __PAGE_OFFSET
19+
1820
#define THREAD_SIZE_ORDER 1
1921
#define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)
2022

arch/x86/include/asm/page_64_types.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@
3232
*/
3333
#define __PAGE_OFFSET _AC(0xffff880000000000, UL)
3434

35-
#define __PHYSICAL_START ((CONFIG_PHYSICAL_START + \
36-
(CONFIG_PHYSICAL_ALIGN - 1)) & \
37-
~(CONFIG_PHYSICAL_ALIGN - 1))
38-
39-
#define __START_KERNEL (__START_KERNEL_map + __PHYSICAL_START)
4035
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
4136

4237
/* See Documentation/x86/x86_64/mm.txt for a description of the memory map. */

arch/x86/include/asm/page_types.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
(((current->personality & READ_IMPLIES_EXEC) ? VM_EXEC : 0 ) | \
3434
VM_READ | VM_WRITE | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC)
3535

36+
#define __PHYSICAL_START ALIGN(CONFIG_PHYSICAL_START, \
37+
CONFIG_PHYSICAL_ALIGN)
38+
39+
#define __START_KERNEL (__START_KERNEL_map + __PHYSICAL_START)
40+
3641
#ifdef CONFIG_X86_64
3742
#include <asm/page_64_types.h>
3843
#else

0 commit comments

Comments
 (0)