Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/boot: Calculate decompression size during boot not build

Currently z_extract_offset is calculated in boot/compressed/mkpiggy.c.
This doesn't work well because mkpiggy.c doesn't know the details of the
decompressor in use. As a result, it can only make an estimation, which
has risks:

- output + output_len (VO) could be much bigger than input + input_len
(ZO). In this case, the decompressed kernel plus relocs could overwrite
the decompression code while it is running.

- The head code of ZO could be bigger than z_extract_offset. In this case
an overwrite could happen when the head code is running to move ZO to
the end of buffer. Though currently the size of the head code is very
small it's still a potential risk. Since there is no rule to limit the
size of the head code of ZO, it runs the risk of suddenly becoming a
(hard to find) bug.

Instead, this moves the z_extract_offset calculation into header.S, and
makes adjustments to be sure that the above two cases can never happen,
and further corrects the comments describing the calculations.

Since we have (in the previous patch) made ZO always be located against
the end of decompression buffer, z_extract_offset is only used here to
calculate an appropriate buffer size (INIT_SIZE), and is not longer used
elsewhere. As such, it can be removed from voffset.h.

Additionally clean up #if/#else #define to improve readability.

Signed-off-by: Yinghai Lu <yinghai@kernel.org>
Signed-off-by: Baoquan He <bhe@redhat.com>
[ Rewrote the changelog and comments. ]
Signed-off-by: Kees Cook <keescook@chromium.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Borislav Petkov <bp@alien8.de>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Dave Young <dyoung@redhat.com>
Cc: Denys Vlasenko <dvlasenk@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Vivek Goyal <vgoyal@redhat.com>
Cc: lasse.collin@tukaani.org
Link: http://lkml.kernel.org/r/1461888548-32439-4-git-send-email-keescook@chromium.org
Signed-off-by: Ingo Molnar <mingo@kernel.org>

authored by

Yinghai Lu and committed by
Ingo Molnar
d607251b 974f221c

+30 -28
+1 -1
arch/x86/boot/Makefile
··· 95 95 $(obj)/voffset.h: vmlinux FORCE 96 96 $(call if_changed,voffset) 97 97 98 - sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [ABCDGRSTVW] \(startup_32\|startup_64\|efi32_stub_entry\|efi64_stub_entry\|efi_pe_entry\|input_data\|_end\|z_.*\)$$/\#define ZO_\2 0x\1/p' 98 + sed-zoffset := -e 's/^\([0-9a-fA-F]*\) [ABCDGRSTVW] \(startup_32\|startup_64\|efi32_stub_entry\|efi64_stub_entry\|efi_pe_entry\|input_data\|_end\|_ehead\|_text\|z_.*\)$$/\#define ZO_\2 0x\1/p' 99 99 100 100 quiet_cmd_zoffset = ZOFFSET $@ 101 101 cmd_zoffset = $(NM) $< | sed -n $(sed-zoffset) > $@
+4 -17
arch/x86/boot/compressed/mkpiggy.c
··· 18 18 * 19 19 * H. Peter Anvin <hpa@linux.intel.com> 20 20 * 21 - * ----------------------------------------------------------------------- */ 22 - 23 - /* 24 - * Compute the desired load offset from a compressed program; outputs 25 - * a small assembly wrapper with the appropriate symbols defined. 21 + * ----------------------------------------------------------------------- 22 + * 23 + * Outputs a small assembly wrapper with the appropriate symbols defined. 24 + * 26 25 */ 27 26 28 27 #include <stdlib.h> ··· 34 35 { 35 36 uint32_t olen; 36 37 long ilen; 37 - unsigned long offs; 38 38 unsigned long run_size; 39 39 FILE *f = NULL; 40 40 int retval = 1; ··· 65 67 ilen = ftell(f); 66 68 olen = get_unaligned_le32(&olen); 67 69 68 - /* 69 - * Now we have the input (compressed) and output (uncompressed) 70 - * sizes, compute the necessary decompression offset... 71 - */ 72 - 73 - offs = (olen > ilen) ? olen - ilen : 0; 74 - offs += olen >> 12; /* Add 8 bytes for each 32K block */ 75 - offs += 64*1024 + 128; /* Add 64K + 128 bytes slack */ 76 - offs = (offs+4095) & ~4095; /* Round to a 4K boundary */ 77 70 run_size = atoi(argv[2]); 78 71 79 72 printf(".section \".rodata..compressed\",\"a\",@progbits\n"); ··· 72 83 printf("z_input_len = %lu\n", ilen); 73 84 printf(".globl z_output_len\n"); 74 85 printf("z_output_len = %lu\n", (unsigned long)olen); 75 - printf(".globl z_extract_offset\n"); 76 - printf("z_extract_offset = 0x%lx\n", offs); 77 86 printf(".globl z_run_size\n"); 78 87 printf("z_run_size = %lu\n", run_size); 79 88
+25 -10
arch/x86/boot/header.S
··· 508 508 # To avoid problems with the compressed data's meta information an extra 18 509 509 # bytes are needed. Leading to the formula: 510 510 # 511 - # extra_bytes = (uncompressed_size >> 12) + 32768 + 18 + decompressor_size 511 + # extra_bytes = (uncompressed_size >> 12) + 32768 + 18 512 512 # 513 513 # Adding 8 bytes per 32K is a bit excessive but much easier to calculate. 514 514 # Adding 32768 instead of 32767 just makes for round numbers. 515 - # Adding the decompressor_size is necessary as it musht live after all 516 - # of the data as well. Last I measured the decompressor is about 14K. 517 - # 10K of actual data and 4K of bss. 518 515 # 519 516 # Above analysis is for decompressing gzip compressed kernel only. Up to 520 517 # now 6 different decompressor are supported all together. And among them ··· 521 524 # the description in lib/decompressor_xxx.c for specific information. 522 525 # 523 526 # extra_bytes = (uncompressed_size >> 12) + 65536 + 128 524 - # 525 - # Note that this calculation, which results in z_extract_offset (below), 526 - # is currently generated in compressed/mkpiggy.c 527 527 528 - #define ZO_INIT_SIZE (ZO__end - ZO_startup_32 + ZO_z_extract_offset) 528 + #define ZO_z_extra_bytes ((ZO_z_output_len >> 12) + 65536 + 128) 529 + #if ZO_z_output_len > ZO_z_input_len 530 + # define ZO_z_extract_offset (ZO_z_output_len + ZO_z_extra_bytes - \ 531 + ZO_z_input_len) 532 + #else 533 + # define ZO_z_extract_offset ZO_z_extra_bytes 534 + #endif 535 + 536 + /* 537 + * The extract_offset has to be bigger than ZO head section. Otherwise when 538 + * the head code is running to move ZO to the end of the buffer, it will 539 + * overwrite the head code itself. 540 + */ 541 + #if (ZO__ehead - ZO_startup_32) > ZO_z_extract_offset 542 + # define ZO_z_min_extract_offset ((ZO__ehead - ZO_startup_32 + 4095) & ~4095) 543 + #else 544 + # define ZO_z_min_extract_offset ((ZO_z_extract_offset + 4095) & ~4095) 545 + #endif 546 + 547 + #define ZO_INIT_SIZE (ZO__end - ZO_startup_32 + ZO_z_min_extract_offset) 548 + 529 549 #define VO_INIT_SIZE (VO__end - VO__text) 530 550 #if ZO_INIT_SIZE > VO_INIT_SIZE 531 - #define INIT_SIZE ZO_INIT_SIZE 551 + # define INIT_SIZE ZO_INIT_SIZE 532 552 #else 533 - #define INIT_SIZE VO_INIT_SIZE 553 + # define INIT_SIZE VO_INIT_SIZE 534 554 #endif 555 + 535 556 init_size: .long INIT_SIZE # kernel initialization size 536 557 handover_offset: .long 0 # Filled in by build.c 537 558