lib/decompressors: use real out buf size for gunzip with kernel

When loading x86 64bit kernel above 4GiB with patched grub2, got kernel
gunzip error.

| early console in decompress_kernel
| decompress_kernel:
|       input: [0x807f2143b4-0x807ff61aee]
|      output: [0x807cc00000-0x807f3ea29b] 0x027ea29c: output_len
| boot via startup_64
| KASLR using RDTSC...
|  new output: [0x46fe000000-0x470138cfff] 0x0338d000: output_run_size
|  decompress: [0x46fe000000-0x47007ea29b] <=== [0x807f2143b4-0x807ff61aee]
|
| Decompressing Linux... gz...
|
| uncompression error
|
| -- System halted

the new buffer is at 0x46fe000000ULL, decompressor_gzip is using
0xffffffb901ffffff as out_len.  gunzip in lib/zlib_inflate/inflate.c cap
that len to 0x01ffffff and decompress fails later.

We could hit this problem with crashkernel booting that uses kexec loading
kernel above 4GiB.

We have decompress_* support:
    1. inbuf[]/outbuf[] for kernel preboot.
    2. inbuf[]/flush() for initramfs
    3. fill()/flush() for initrd.
This bug only affect kernel preboot path that use outbuf[].

Add __decompress and take real out_buf_len for gunzip instead of guessing
wrong buf size.

Fixes: 1431574a1c (lib/decompressors: fix "no limit" output buffer length)
Signed-off-by: Yinghai Lu <yinghai@kernel.org>
Cc: Alexandre Courbot <acourbot@nvidia.com>
Cc: Jon Medhurst <tixy@linaro.org>
Cc: Stephen Warren <swarren@wwwdotorg.org>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Yinghai Lu 2015-09-09 15:39:12 -07:00 committed by Linus Torvalds
parent e852d82a5b
commit 2d3862d26e
14 changed files with 70 additions and 27 deletions

View File

@ -57,5 +57,5 @@ extern char * strstr(const char * s1, const char *s2);
int do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x)) int do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{ {
return decompress(input, len, NULL, NULL, output, NULL, error); return __decompress(input, len, NULL, NULL, output, 0, NULL, error);
} }

View File

@ -70,5 +70,5 @@ void decompress_kernel(void)
free_mem_ptr = (unsigned long)&_end; free_mem_ptr = (unsigned long)&_end;
free_mem_end_ptr = free_mem_ptr + HEAP_SIZE; free_mem_end_ptr = free_mem_ptr + HEAP_SIZE;
decompress(input_data, input_len, NULL, NULL, output, NULL, error); __decompress(input_data, input_len, NULL, NULL, output, 0, NULL, error);
} }

View File

@ -86,6 +86,7 @@ decompress_kernel(int mmu_on, unsigned char *zimage_data,
free_mem_end_ptr = free_mem_ptr + BOOT_HEAP_SIZE; free_mem_end_ptr = free_mem_ptr + BOOT_HEAP_SIZE;
puts("\nDecompressing Linux... "); puts("\nDecompressing Linux... ");
decompress(input_data, input_len, NULL, NULL, output_data, NULL, error); __decompress(input_data, input_len, NULL, NULL, output_data, 0,
NULL, error);
puts("done.\nBooting the kernel.\n"); puts("done.\nBooting the kernel.\n");
} }

View File

@ -111,8 +111,8 @@ void decompress_kernel(unsigned long boot_heap_start)
puts("\n"); puts("\n");
/* Decompress the kernel with according algorithm */ /* Decompress the kernel with according algorithm */
decompress((char *)zimage_start, zimage_size, 0, 0, __decompress((char *)zimage_start, zimage_size, 0, 0,
(void *)VMLINUX_LOAD_ADDRESS_ULL, 0, error); (void *)VMLINUX_LOAD_ADDRESS_ULL, 0, 0, error);
/* FIXME: should we flush cache here? */ /* FIXME: should we flush cache here? */
puts("Now, booting the kernel...\n"); puts("Now, booting the kernel...\n");

View File

@ -167,7 +167,7 @@ unsigned long decompress_kernel(void)
#endif #endif
puts("Uncompressing Linux... "); puts("Uncompressing Linux... ");
decompress(input_data, input_len, NULL, NULL, output, NULL, error); __decompress(input_data, input_len, NULL, NULL, output, 0, NULL, error);
puts("Ok, booting the kernel.\n"); puts("Ok, booting the kernel.\n");
return (unsigned long) output; return (unsigned long) output;
} }

View File

@ -132,7 +132,7 @@ void decompress_kernel(void)
puts("Uncompressing Linux... "); puts("Uncompressing Linux... ");
cache_control(CACHE_ENABLE); cache_control(CACHE_ENABLE);
decompress(input_data, input_len, NULL, NULL, output, NULL, error); __decompress(input_data, input_len, NULL, NULL, output, 0, NULL, error);
cache_control(CACHE_DISABLE); cache_control(CACHE_DISABLE);
puts("Ok, booting the kernel.\n"); puts("Ok, booting the kernel.\n");
} }

View File

@ -119,8 +119,8 @@ unsigned long decompress_kernel(unsigned long output_start,
output_ptr = get_unaligned_le32(tmp); output_ptr = get_unaligned_le32(tmp);
arch_decomp_puts("Uncompressing Linux..."); arch_decomp_puts("Uncompressing Linux...");
decompress(input_data, input_data_end - input_data, NULL, NULL, __decompress(input_data, input_data_end - input_data, NULL, NULL,
output_data, NULL, error); output_data, 0, NULL, error);
arch_decomp_puts(" done, booting the kernel.\n"); arch_decomp_puts(" done, booting the kernel.\n");
return output_ptr; return output_ptr;
} }

View File

@ -448,7 +448,8 @@ asmlinkage __visible void *decompress_kernel(void *rmode, memptr heap,
#endif #endif
debug_putstr("\nDecompressing Linux... "); debug_putstr("\nDecompressing Linux... ");
decompress(input_data, input_len, NULL, NULL, output, NULL, error); __decompress(input_data, input_len, NULL, NULL, output, output_len,
NULL, error);
parse_elf(output); parse_elf(output);
/* /*
* 32-bit always performs relocations. 64-bit relocations are only * 32-bit always performs relocations. 64-bit relocations are only

View File

@ -743,12 +743,12 @@ STATIC int INIT bunzip2(unsigned char *buf, long len,
} }
#ifdef PREBOOT #ifdef PREBOOT
STATIC int INIT decompress(unsigned char *buf, long len, STATIC int INIT __decompress(unsigned char *buf, long len,
long (*fill)(void*, unsigned long), long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long), long (*flush)(void*, unsigned long),
unsigned char *outbuf, unsigned char *outbuf, long olen,
long *pos, long *pos,
void(*error)(char *x)) void (*error)(char *x))
{ {
return bunzip2(buf, len - 4, fill, flush, outbuf, pos, error); return bunzip2(buf, len - 4, fill, flush, outbuf, pos, error);
} }

View File

@ -1,4 +1,5 @@
#ifdef STATIC #ifdef STATIC
#define PREBOOT
/* Pre-boot environment: included */ /* Pre-boot environment: included */
/* prevent inclusion of _LINUX_KERNEL_H in pre-boot environment: lots /* prevent inclusion of _LINUX_KERNEL_H in pre-boot environment: lots
@ -33,23 +34,23 @@ static long INIT nofill(void *buffer, unsigned long len)
} }
/* Included from initramfs et al code */ /* Included from initramfs et al code */
STATIC int INIT gunzip(unsigned char *buf, long len, STATIC int INIT __gunzip(unsigned char *buf, long len,
long (*fill)(void*, unsigned long), long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long), long (*flush)(void*, unsigned long),
unsigned char *out_buf, unsigned char *out_buf, long out_len,
long *pos, long *pos,
void(*error)(char *x)) { void(*error)(char *x)) {
u8 *zbuf; u8 *zbuf;
struct z_stream_s *strm; struct z_stream_s *strm;
int rc; int rc;
size_t out_len;
rc = -1; rc = -1;
if (flush) { if (flush) {
out_len = 0x8000; /* 32 K */ out_len = 0x8000; /* 32 K */
out_buf = malloc(out_len); out_buf = malloc(out_len);
} else { } else {
out_len = ((size_t)~0) - (size_t)out_buf; /* no limit */ if (!out_len)
out_len = ((size_t)~0) - (size_t)out_buf; /* no limit */
} }
if (!out_buf) { if (!out_buf) {
error("Out of memory while allocating output buffer"); error("Out of memory while allocating output buffer");
@ -181,4 +182,24 @@ STATIC int INIT gunzip(unsigned char *buf, long len,
return rc; /* returns Z_OK (0) if successful */ return rc; /* returns Z_OK (0) if successful */
} }
#define decompress gunzip #ifndef PREBOOT
STATIC int INIT gunzip(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf,
long *pos,
void (*error)(char *x))
{
return __gunzip(buf, len, fill, flush, out_buf, 0, pos, error);
}
#else
STATIC int INIT __decompress(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf, long out_len,
long *pos,
void (*error)(char *x))
{
return __gunzip(buf, len, fill, flush, out_buf, out_len, pos, error);
}
#endif

View File

@ -196,12 +196,12 @@ STATIC inline int INIT unlz4(u8 *input, long in_len,
} }
#ifdef PREBOOT #ifdef PREBOOT
STATIC int INIT decompress(unsigned char *buf, long in_len, STATIC int INIT __decompress(unsigned char *buf, long in_len,
long (*fill)(void*, unsigned long), long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long), long (*flush)(void*, unsigned long),
unsigned char *output, unsigned char *output, long out_len,
long *posp, long *posp,
void(*error)(char *x) void (*error)(char *x)
) )
{ {
return unlz4(buf, in_len - 4, fill, flush, output, posp, error); return unlz4(buf, in_len - 4, fill, flush, output, posp, error);

View File

@ -667,13 +667,12 @@ STATIC inline int INIT unlzma(unsigned char *buf, long in_len,
} }
#ifdef PREBOOT #ifdef PREBOOT
STATIC int INIT decompress(unsigned char *buf, long in_len, STATIC int INIT __decompress(unsigned char *buf, long in_len,
long (*fill)(void*, unsigned long), long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long), long (*flush)(void*, unsigned long),
unsigned char *output, unsigned char *output, long out_len,
long *posp, long *posp,
void(*error)(char *x) void (*error)(char *x))
)
{ {
return unlzma(buf, in_len - 4, fill, flush, output, posp, error); return unlzma(buf, in_len - 4, fill, flush, output, posp, error);
} }

View File

@ -31,6 +31,7 @@
*/ */
#ifdef STATIC #ifdef STATIC
#define PREBOOT
#include "lzo/lzo1x_decompress_safe.c" #include "lzo/lzo1x_decompress_safe.c"
#else #else
#include <linux/decompress/unlzo.h> #include <linux/decompress/unlzo.h>
@ -287,4 +288,14 @@ STATIC int INIT unlzo(u8 *input, long in_len,
return ret; return ret;
} }
#define decompress unlzo #ifdef PREBOOT
STATIC int INIT __decompress(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf, long olen,
long *pos,
void (*error)(char *x))
{
return unlzo(buf, len, fill, flush, out_buf, pos, error);
}
#endif

View File

@ -394,4 +394,14 @@ STATIC int INIT unxz(unsigned char *in, long in_size,
* This macro is used by architecture-specific files to decompress * This macro is used by architecture-specific files to decompress
* the kernel image. * the kernel image.
*/ */
#define decompress unxz #ifdef XZ_PREBOOT
STATIC int INIT __decompress(unsigned char *buf, long len,
long (*fill)(void*, unsigned long),
long (*flush)(void*, unsigned long),
unsigned char *out_buf, long olen,
long *pos,
void (*error)(char *x))
{
return unxz(buf, len, fill, flush, out_buf, pos, error);
}
#endif