efi/libstub: Avoid legacy decompressor zlib/zstd wrappers

Remove EFI zboot's dependency on the decompression wrappers used by the
legacy decompressor boot code, which can only process the input in one
go, and this will not work for upcoming support for embedded ELF images.
They also do some odd things like providing a barebones malloc()
implementation, which is not needed in a hosted environment such as the
EFI boot services.

So instead, implement GZIP deflate and ZSTD decompression in terms of
the underlying libraries. Support for other compression algoritms has
already been dropped.

Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
This commit is contained in:
Ard Biesheuvel 2024-11-20 20:36:03 +01:00
parent 74d613e046
commit 0dc1754e16
6 changed files with 136 additions and 57 deletions

View file

@ -92,7 +92,12 @@ lib-$(CONFIG_LOONGARCH) += loongarch.o loongarch-stub.o
CFLAGS_arm32-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET)
zboot-obj-$(CONFIG_RISCV) := lib-clz_ctz.o lib-ashldi3.o
zboot-obj-y := zboot-decompress-gzip.o
CFLAGS_zboot-decompress-gzip.o += -I$(srctree)/lib/zlib_inflate
zboot-obj-$(CONFIG_KERNEL_ZSTD) := zboot-decompress-zstd.o lib-xxhash.o
CFLAGS_zboot-decompress-zstd.o += -I$(srctree)/lib/zstd
zboot-obj-$(CONFIG_RISCV) += lib-clz_ctz.o lib-ashldi3.o
lib-$(CONFIG_EFI_ZBOOT) += zboot.o $(zboot-obj-y)
lib-$(CONFIG_UNACCEPTED_MEMORY) += unaccepted_memory.o bitmap.o find.o

View file

@ -1234,4 +1234,7 @@ void process_unaccepted_memory(u64 start, u64 end);
void accept_memory(phys_addr_t start, unsigned long size);
void arch_accept_memory(phys_addr_t start, phys_addr_t end);
efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size);
efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen);
#endif

View file

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <linux/zlib.h>
#include <asm/efi.h>
#include "efistub.h"
#include "inftrees.c"
#include "inffast.c"
#include "inflate.c"
extern unsigned char _gzdata_start[], _gzdata_end[];
extern u32 __aligned(1) payload_size;
static struct z_stream_s stream;
efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size)
{
efi_status_t status;
int rc;
/* skip the 10 byte header, assume no recorded filename */
stream.next_in = _gzdata_start + 10;
stream.avail_in = _gzdata_end - stream.next_in;
status = efi_allocate_pages(zlib_inflate_workspacesize(),
(unsigned long *)&stream.workspace,
ULONG_MAX);
if (status != EFI_SUCCESS)
return status;
rc = zlib_inflateInit2(&stream, -MAX_WBITS);
if (rc != Z_OK) {
efi_err("failed to initialize GZIP decompressor: %d\n", rc);
status = EFI_LOAD_ERROR;
goto out;
}
*alloc_size = payload_size;
return EFI_SUCCESS;
out:
efi_free(zlib_inflate_workspacesize(), (unsigned long)stream.workspace);
return status;
}
efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen)
{
int rc;
stream.next_out = out;
stream.avail_out = outlen;
rc = zlib_inflate(&stream, 0);
zlib_inflateEnd(&stream);
efi_free(zlib_inflate_workspacesize(), (unsigned long)stream.workspace);
if (rc != Z_STREAM_END) {
efi_err("GZIP decompression failed with status %d\n", rc);
return EFI_LOAD_ERROR;
}
efi_cache_sync_image((unsigned long)out, outlen);
return EFI_SUCCESS;
}

View file

@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/efi.h>
#include <linux/zstd.h>
#include <asm/efi.h>
#include "decompress_sources.h"
#include "efistub.h"
extern unsigned char _gzdata_start[], _gzdata_end[];
extern u32 __aligned(1) payload_size;
static size_t wksp_size;
static void *wksp;
efi_status_t efi_zboot_decompress_init(unsigned long *alloc_size)
{
efi_status_t status;
wksp_size = zstd_dctx_workspace_bound();
status = efi_allocate_pages(wksp_size, (unsigned long *)&wksp, ULONG_MAX);
if (status != EFI_SUCCESS)
return status;
*alloc_size = payload_size;
return EFI_SUCCESS;
}
efi_status_t efi_zboot_decompress(u8 *out, unsigned long outlen)
{
zstd_dctx *dctx = zstd_init_dctx(wksp, wksp_size);
size_t ret;
int retval;
ret = zstd_decompress_dctx(dctx, out, outlen, _gzdata_start,
_gzdata_end - _gzdata_start - 4);
efi_free(wksp_size, (unsigned long)wksp);
retval = zstd_get_error_code(ret);
if (retval) {
efi_err("ZSTD-decompression failed with status %d\n", retval);
return EFI_LOAD_ERROR;
}
efi_cache_sync_image((unsigned long)out, outlen);
return EFI_SUCCESS;
}

View file

@ -7,36 +7,6 @@
#include "efistub.h"
static unsigned char zboot_heap[SZ_256K] __aligned(64);
static unsigned long free_mem_ptr, free_mem_end_ptr;
#define STATIC static
#if defined(CONFIG_KERNEL_GZIP)
#include "../../../../lib/decompress_inflate.c"
#elif defined(CONFIG_KERNEL_LZ4)
#include "../../../../lib/decompress_unlz4.c"
#elif defined(CONFIG_KERNEL_LZMA)
#include "../../../../lib/decompress_unlzma.c"
#elif defined(CONFIG_KERNEL_LZO)
#include "../../../../lib/decompress_unlzo.c"
#elif defined(CONFIG_KERNEL_XZ)
#undef memcpy
#define memcpy memcpy
#undef memmove
#define memmove memmove
#include "../../../../lib/decompress_unxz.c"
#elif defined(CONFIG_KERNEL_ZSTD)
#include "../../../../lib/decompress_unzstd.c"
#endif
extern char efi_zboot_header[];
extern char _gzdata_start[], _gzdata_end[];
static void error(char *x)
{
efi_err("EFI decompressor: %s\n", x);
}
static unsigned long alloc_preferred_address(unsigned long alloc_size)
{
#ifdef EFI_KIMG_PREFERRED_ADDRESS
@ -64,22 +34,17 @@ struct screen_info *alloc_screen_info(void)
asmlinkage efi_status_t __efiapi
efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
{
unsigned long compressed_size = _gzdata_end - _gzdata_start;
char *cmdline_ptr __free(efi_pool) = NULL;
unsigned long image_base, alloc_size;
efi_loaded_image_t *image;
efi_status_t status;
char *cmdline_ptr;
int ret;
WRITE_ONCE(efi_system_table, systab);
free_mem_ptr = (unsigned long)&zboot_heap;
free_mem_end_ptr = free_mem_ptr + sizeof(zboot_heap);
status = efi_bs_call(handle_protocol, handle,
&LOADED_IMAGE_PROTOCOL_GUID, (void **)&image);
if (status != EFI_SUCCESS) {
error("Failed to locate parent's loaded image protocol");
efi_err("Failed to locate parent's loaded image protocol\n");
return status;
}
@ -89,9 +54,9 @@ efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
efi_info("Decompressing Linux Kernel...\n");
// SizeOfImage from the compressee's PE/COFF header
alloc_size = round_up(get_unaligned_le32(_gzdata_end - 4),
EFI_ALLOC_ALIGN);
status = efi_zboot_decompress_init(&alloc_size);
if (status != EFI_SUCCESS)
return status;
// If the architecture has a preferred address for the image,
// try that first.
@ -122,26 +87,14 @@ efi_zboot_entry(efi_handle_t handle, efi_system_table_t *systab)
seed, EFI_LOADER_CODE, 0, EFI_ALLOC_LIMIT);
if (status != EFI_SUCCESS) {
efi_err("Failed to allocate memory\n");
goto free_cmdline;
return status;
}
}
// Decompress the payload into the newly allocated buffer.
ret = __decompress(_gzdata_start, compressed_size, NULL, NULL,
(void *)image_base, alloc_size, NULL, error);
if (ret < 0) {
error("Decompression failed");
status = EFI_DEVICE_ERROR;
goto free_image;
}
// Decompress the payload into the newly allocated buffer
status = efi_zboot_decompress((void *)image_base, alloc_size) ?:
efi_stub_common(handle, image, image_base, cmdline_ptr);
efi_cache_sync_image(image_base, alloc_size);
status = efi_stub_common(handle, image, image_base, cmdline_ptr);
free_image:
efi_free(alloc_size, image_base);
free_cmdline:
efi_bs_call(free_pool, cmdline_ptr);
return status;
}

View file

@ -17,6 +17,7 @@ SECTIONS
.rodata : ALIGN(8) {
__efistub__gzdata_start = .;
*(.gzdata)
__efistub_payload_size = . - 4;
__efistub__gzdata_end = .;
*(.rodata* .init.rodata* .srodata*)