aboutsummaryrefslogtreecommitdiffstats
path: root/arch/x86/boot
diff options
context:
space:
mode:
authorKees Cook <keescook@chromium.org>2013-07-08 12:15:17 -0400
committerH. Peter Anvin <hpa@linux.intel.com>2013-08-08 00:00:04 -0400
commita02150610776f66b40257624822a879311592bb2 (patch)
treea14370b350bfead0b692d2b6a4b74f426c17024d /arch/x86/boot
parentc095ba7224d8edc71dcef0d655911399a8bd4a3f (diff)
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>
Diffstat (limited to 'arch/x86/boot')
-rw-r--r--arch/x86/boot/compressed/head_32.S31
-rw-r--r--arch/x86/boot/compressed/head_64.S1
-rw-r--r--arch/x86/boot/compressed/misc.c77
3 files changed, 80 insertions, 29 deletions
diff --git a/arch/x86/boot/compressed/head_32.S b/arch/x86/boot/compressed/head_32.S
index 1e3184f6072f..5d6f6891b188 100644
--- a/arch/x86/boot/compressed/head_32.S
+++ b/arch/x86/boot/compressed/head_32.S
@@ -181,8 +181,9 @@ relocated:
181/* 181/*
182 * Do the decompression, and jump to the new kernel.. 182 * Do the decompression, and jump to the new kernel..
183 */ 183 */
184 leal z_extract_offset_negative(%ebx), %ebp
185 /* push arguments for decompress_kernel: */ 184 /* push arguments for decompress_kernel: */
185 pushl $z_output_len /* decompressed length */
186 leal z_extract_offset_negative(%ebx), %ebp
186 pushl %ebp /* output address */ 187 pushl %ebp /* output address */
187 pushl $z_input_len /* input_len */ 188 pushl $z_input_len /* input_len */
188 leal input_data(%ebx), %eax 189 leal input_data(%ebx), %eax
@@ -191,33 +192,7 @@ relocated:
191 pushl %eax /* heap area */ 192 pushl %eax /* heap area */
192 pushl %esi /* real mode pointer */ 193 pushl %esi /* real mode pointer */
193 call decompress_kernel 194 call decompress_kernel
194 addl $20, %esp 195 addl $24, %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
2131: subl $4, %edi
214 movl (%edi), %ecx
215 testl %ecx, %ecx
216 jz 2f
217 addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
218 jmp 1b
2192:
220#endif
221 196
222/* 197/*
223 * Jump to the decompressed kernel. 198 * Jump to the decompressed kernel.
diff --git a/arch/x86/boot/compressed/head_64.S b/arch/x86/boot/compressed/head_64.S
index 06e71c2c16bf..c337422b575d 100644
--- a/arch/x86/boot/compressed/head_64.S
+++ b/arch/x86/boot/compressed/head_64.S
@@ -338,6 +338,7 @@ relocated:
338 leaq input_data(%rip), %rdx /* input_data */ 338 leaq input_data(%rip), %rdx /* input_data */
339 movl $z_input_len, %ecx /* input_len */ 339 movl $z_input_len, %ecx /* input_len */
340 movq %rbp, %r8 /* output target address */ 340 movq %rbp, %r8 /* output target address */
341 movq $z_output_len, %r9 /* decompressed length */
341 call decompress_kernel 342 call decompress_kernel
342 popq %rsi 343 popq %rsi
343 344
diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c
index 0319c88290a5..434f077d2c4d 100644
--- a/arch/x86/boot/compressed/misc.c
+++ b/arch/x86/boot/compressed/misc.c
@@ -271,6 +271,79 @@ static void error(char *x)
271 asm("hlt"); 271 asm("hlt");
272} 272}
273 273
274#if CONFIG_X86_NEED_RELOCS
275static 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
343static inline void handle_relocations(void *output, unsigned long output_len)
344{ }
345#endif
346
274static void parse_elf(void *output) 347static void parse_elf(void *output)
275{ 348{
276#ifdef CONFIG_X86_64 349#ifdef CONFIG_X86_64
@@ -325,7 +398,8 @@ static void parse_elf(void *output)
325asmlinkage void decompress_kernel(void *rmode, memptr heap, 398asmlinkage void decompress_kernel(void *rmode, memptr heap,
326 unsigned char *input_data, 399 unsigned char *input_data,
327 unsigned long input_len, 400 unsigned long input_len,
328 unsigned char *output) 401 unsigned char *output,
402 unsigned long output_len)
329{ 403{
330 real_mode = rmode; 404 real_mode = rmode;
331 405
@@ -365,6 +439,7 @@ asmlinkage void decompress_kernel(void *rmode, memptr heap,
365 debug_putstr("\nDecompressing Linux... "); 439 debug_putstr("\nDecompressing Linux... ");
366 decompress(input_data, input_len, NULL, NULL, output, NULL, error); 440 decompress(input_data, input_len, NULL, NULL, output, NULL, error);
367 parse_elf(output); 441 parse_elf(output);
442 handle_relocations(output, output_len);
368 debug_putstr("done.\nBooting the kernel.\n"); 443 debug_putstr("done.\nBooting the kernel.\n");
369 return; 444 return;
370} 445}