Commit 5193a33d authored by Ard Biesheuvel's avatar Ard Biesheuvel
Browse files

efi/libstub: Move file I/O support code into separate file



Split off the file I/O support code into a separate source file so
it ends up in a separate object file in the static library, allowing
the linker to omit it if the routines are not used.

Signed-off-by: default avatarArd Biesheuvel <ardb@kernel.org>
parent b8717454
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ OBJECT_FILES_NON_STANDARD := y
KCOV_INSTRUMENT			:= n

lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
				   mem.o random.o randomalloc.o pci.o
				   file.o mem.o random.o randomalloc.o pci.o

# include the stub's generic dependencies from lib/ when building for ARM/arm64
arm-deps-y := fdt_rw.c fdt_ro.c fdt_wip.c fdt.c fdt_empty_tree.c fdt_sw.c
+6 −288
Original line number Diff line number Diff line
@@ -12,23 +12,7 @@

#include "efistub.h"

/*
 * Some firmware implementations have problems reading files in one go.
 * A read chunk size of 1MB seems to work for most platforms.
 *
 * Unfortunately, reading files in chunks triggers *other* bugs on some
 * platforms, so we provide a way to disable this workaround, which can
 * be done by passing "efi=nochunk" on the EFI boot stub command line.
 *
 * If you experience issues with initrd images being corrupt it's worth
 * trying efi=nochunk, but chunking is enabled by default because there
 * are far more machines that require the workaround than those that
 * break with it enabled.
 */
#define EFI_READ_CHUNK_SIZE	(1024 * 1024)

static unsigned long efi_chunk_size = EFI_READ_CHUNK_SIZE;

static bool __efistub_global efi_nochunk;
static bool __efistub_global efi_nokaslr;
static bool __efistub_global efi_quiet;
static bool __efistub_global efi_novamap;
@@ -36,6 +20,10 @@ static bool __efistub_global efi_nosoftreserve;
static bool __efistub_global efi_disable_pci_dma =
					IS_ENABLED(CONFIG_EFI_DISABLE_PCI_DMA);

bool __pure nochunk(void)
{
	return efi_nochunk;
}
bool __pure nokaslr(void)
{
	return efi_nokaslr;
@@ -53,11 +41,6 @@ bool __pure __efi_soft_reserve_enabled(void)
	return !efi_nosoftreserve;
}

struct file_info {
	efi_file_protocol_t *handle;
	u64 size;
};

void efi_printk(char *str)
{
	char *s8;
@@ -75,90 +58,6 @@ void efi_printk(char *str)
	}
}

static efi_status_t efi_file_size(void *__fh, efi_char16_t *filename_16,
				  void **handle, u64 *file_sz)
{
	efi_file_protocol_t *h, *fh = __fh;
	efi_file_info_t *info;
	efi_status_t status;
	efi_guid_t info_guid = EFI_FILE_INFO_ID;
	unsigned long info_sz;

	status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, 0);
	if (status != EFI_SUCCESS) {
		efi_printk("Failed to open file: ");
		efi_char16_printk(filename_16);
		efi_printk("\n");
		return status;
	}

	*handle = h;

	info_sz = 0;
	status = h->get_info(h, &info_guid, &info_sz, NULL);
	if (status != EFI_BUFFER_TOO_SMALL) {
		efi_printk("Failed to get file info size\n");
		return status;
	}

grow:
	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, info_sz,
			     (void **)&info);
	if (status != EFI_SUCCESS) {
		efi_printk("Failed to alloc mem for file info\n");
		return status;
	}

	status = h->get_info(h, &info_guid, &info_sz, info);
	if (status == EFI_BUFFER_TOO_SMALL) {
		efi_bs_call(free_pool, info);
		goto grow;
	}

	*file_sz = info->file_size;
	efi_bs_call(free_pool, info);

	if (status != EFI_SUCCESS)
		efi_printk("Failed to get initrd info\n");

	return status;
}

static efi_status_t efi_file_read(efi_file_protocol_t *handle,
				  unsigned long *size, void *addr)
{
	return handle->read(handle, size, addr);
}

static efi_status_t efi_file_close(efi_file_protocol_t *handle)
{
	return handle->close(handle);
}

static efi_status_t efi_open_volume(efi_loaded_image_t *image,
				    efi_file_protocol_t **__fh)
{
	efi_simple_file_system_protocol_t *io;
	efi_file_protocol_t *fh;
	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
	efi_status_t status;
	efi_handle_t handle = image->device_handle;

	status = efi_bs_call(handle_protocol, handle, &fs_proto, (void **)&io);
	if (status != EFI_SUCCESS) {
		efi_printk("Failed to handle fs_proto\n");
		return status;
	}

	status = io->open_volume(io, &fh);
	if (status != EFI_SUCCESS)
		efi_printk("Failed to open volume\n");
	else
		*__fh = fh;

	return status;
}

/*
 * Parse the ASCII string 'cmdline' for EFI options, denoted by the efi=
 * option, e.g. efi=nochunk.
@@ -197,7 +96,7 @@ efi_status_t efi_parse_options(char const *cmdline)
	while (*str && *str != ' ') {
		if (!strncmp(str, "nochunk", 7)) {
			str += strlen("nochunk");
			efi_chunk_size = -1UL;
			efi_nochunk = true;
		}

		if (!strncmp(str, "novamap", 7)) {
@@ -232,187 +131,6 @@ efi_status_t efi_parse_options(char const *cmdline)
	return EFI_SUCCESS;
}

/*
 * Check the cmdline for a LILO-style file= arguments.
 *
 * We only support loading a file from the same filesystem as
 * the kernel image.
 */
efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
				  char *cmd_line, char *option_string,
				  unsigned long max_addr,
				  unsigned long *load_addr,
				  unsigned long *load_size)
{
	struct file_info *files;
	unsigned long file_addr;
	u64 file_size_total;
	efi_file_protocol_t *fh = NULL;
	efi_status_t status;
	int nr_files;
	char *str;
	int i, j, k;

	file_addr = 0;
	file_size_total = 0;

	str = cmd_line;

	j = 0;			/* See close_handles */

	if (!load_addr || !load_size)
		return EFI_INVALID_PARAMETER;

	*load_addr = 0;
	*load_size = 0;

	if (!str || !*str)
		return EFI_SUCCESS;

	for (nr_files = 0; *str; nr_files++) {
		str = strstr(str, option_string);
		if (!str)
			break;

		str += strlen(option_string);

		/* Skip any leading slashes */
		while (*str == '/' || *str == '\\')
			str++;

		while (*str && *str != ' ' && *str != '\n')
			str++;
	}

	if (!nr_files)
		return EFI_SUCCESS;

	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
			     nr_files * sizeof(*files), (void **)&files);
	if (status != EFI_SUCCESS) {
		pr_efi_err("Failed to alloc mem for file handle list\n");
		goto fail;
	}

	str = cmd_line;
	for (i = 0; i < nr_files; i++) {
		struct file_info *file;
		efi_char16_t filename_16[256];
		efi_char16_t *p;

		str = strstr(str, option_string);
		if (!str)
			break;

		str += strlen(option_string);

		file = &files[i];
		p = filename_16;

		/* Skip any leading slashes */
		while (*str == '/' || *str == '\\')
			str++;

		while (*str && *str != ' ' && *str != '\n') {
			if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16))
				break;

			if (*str == '/') {
				*p++ = '\\';
				str++;
			} else {
				*p++ = *str++;
			}
		}

		*p = '\0';

		/* Only open the volume once. */
		if (!i) {
			status = efi_open_volume(image, &fh);
			if (status != EFI_SUCCESS)
				goto free_files;
		}

		status = efi_file_size(fh, filename_16, (void **)&file->handle,
				       &file->size);
		if (status != EFI_SUCCESS)
			goto close_handles;

		file_size_total += file->size;
	}

	if (file_size_total) {
		unsigned long addr;

		/*
		 * Multiple files need to be at consecutive addresses in memory,
		 * so allocate enough memory for all the files.  This is used
		 * for loading multiple files.
		 */
		status = efi_allocate_pages(file_size_total, &file_addr, max_addr);
		if (status != EFI_SUCCESS) {
			pr_efi_err("Failed to alloc highmem for files\n");
			goto close_handles;
		}

		/* We've run out of free low memory. */
		if (file_addr > max_addr) {
			pr_efi_err("We've run out of free low memory\n");
			status = EFI_INVALID_PARAMETER;
			goto free_file_total;
		}

		addr = file_addr;
		for (j = 0; j < nr_files; j++) {
			unsigned long size;

			size = files[j].size;
			while (size) {
				unsigned long chunksize;

				if (IS_ENABLED(CONFIG_X86) && size > efi_chunk_size)
					chunksize = efi_chunk_size;
				else
					chunksize = size;

				status = efi_file_read(files[j].handle,
						       &chunksize,
						       (void *)addr);
				if (status != EFI_SUCCESS) {
					pr_efi_err("Failed to read file\n");
					goto free_file_total;
				}
				addr += chunksize;
				size -= chunksize;
			}

			efi_file_close(files[j].handle);
		}

	}

	efi_bs_call(free_pool, files);

	*load_addr = file_addr;
	*load_size = file_size_total;

	return status;

free_file_total:
	efi_free(file_size_total, file_addr);

close_handles:
	for (k = j; k < i; k++)
		efi_file_close(files[k].handle);
free_files:
	efi_bs_call(free_pool, files);
fail:
	*load_addr = 0;
	*load_size = 0;

	return status;
}
/*
 * Get the number of UTF-8 bytes corresponding to an UTF-16 character.
 * This overestimates for surrogates, but that is okay.
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
#define __efistub_global
#endif

extern bool __pure nochunk(void);
extern bool __pure nokaslr(void);
extern bool __pure is_quiet(void);
extern bool __pure novamap(void);
+303 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Helper functions used by the EFI stub on multiple
 * architectures. This should be #included by the EFI stub
 * implementation files.
 *
 * Copyright 2011 Intel Corporation; author Matt Fleming
 */

#include <linux/efi.h>
#include <asm/efi.h>

#include "efistub.h"

/*
 * Some firmware implementations have problems reading files in one go.
 * A read chunk size of 1MB seems to work for most platforms.
 *
 * Unfortunately, reading files in chunks triggers *other* bugs on some
 * platforms, so we provide a way to disable this workaround, which can
 * be done by passing "efi=nochunk" on the EFI boot stub command line.
 *
 * If you experience issues with initrd images being corrupt it's worth
 * trying efi=nochunk, but chunking is enabled by default on x86 because
 * there are far more machines that require the workaround than those that
 * break with it enabled.
 */
#define EFI_READ_CHUNK_SIZE	SZ_1M

struct file_info {
	efi_file_protocol_t *handle;
	u64 size;
};

static efi_status_t efi_file_size(void *__fh, efi_char16_t *filename_16,
				  void **handle, u64 *file_sz)
{
	efi_file_protocol_t *h, *fh = __fh;
	efi_file_info_t *info;
	efi_status_t status;
	efi_guid_t info_guid = EFI_FILE_INFO_ID;
	unsigned long info_sz;

	status = fh->open(fh, &h, filename_16, EFI_FILE_MODE_READ, 0);
	if (status != EFI_SUCCESS) {
		efi_printk("Failed to open file: ");
		efi_char16_printk(filename_16);
		efi_printk("\n");
		return status;
	}

	*handle = h;

	info_sz = 0;
	status = h->get_info(h, &info_guid, &info_sz, NULL);
	if (status != EFI_BUFFER_TOO_SMALL) {
		efi_printk("Failed to get file info size\n");
		return status;
	}

grow:
	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, info_sz,
			     (void **)&info);
	if (status != EFI_SUCCESS) {
		efi_printk("Failed to alloc mem for file info\n");
		return status;
	}

	status = h->get_info(h, &info_guid, &info_sz, info);
	if (status == EFI_BUFFER_TOO_SMALL) {
		efi_bs_call(free_pool, info);
		goto grow;
	}

	*file_sz = info->file_size;
	efi_bs_call(free_pool, info);

	if (status != EFI_SUCCESS)
		efi_printk("Failed to get initrd info\n");

	return status;
}

static efi_status_t efi_file_read(efi_file_protocol_t *handle,
				  unsigned long *size, void *addr)
{
	return handle->read(handle, size, addr);
}

static efi_status_t efi_file_close(efi_file_protocol_t *handle)
{
	return handle->close(handle);
}

static efi_status_t efi_open_volume(efi_loaded_image_t *image,
				    efi_file_protocol_t **__fh)
{
	efi_simple_file_system_protocol_t *io;
	efi_file_protocol_t *fh;
	efi_guid_t fs_proto = EFI_FILE_SYSTEM_GUID;
	efi_status_t status;
	efi_handle_t handle = image->device_handle;

	status = efi_bs_call(handle_protocol, handle, &fs_proto, (void **)&io);
	if (status != EFI_SUCCESS) {
		efi_printk("Failed to handle fs_proto\n");
		return status;
	}

	status = io->open_volume(io, &fh);
	if (status != EFI_SUCCESS)
		efi_printk("Failed to open volume\n");
	else
		*__fh = fh;

	return status;
}

/*
 * Check the cmdline for a LILO-style file= arguments.
 *
 * We only support loading a file from the same filesystem as
 * the kernel image.
 */
efi_status_t handle_cmdline_files(efi_loaded_image_t *image,
				  char *cmd_line, char *option_string,
				  unsigned long max_addr,
				  unsigned long *load_addr,
				  unsigned long *load_size)
{
	unsigned long efi_chunk_size = ULONG_MAX;
	struct file_info *files;
	unsigned long file_addr;
	u64 file_size_total;
	efi_file_protocol_t *fh = NULL;
	efi_status_t status;
	int nr_files;
	char *str;
	int i, j, k;

	if (IS_ENABLED(CONFIG_X86) && !nochunk())
		efi_chunk_size = EFI_READ_CHUNK_SIZE;

	file_addr = 0;
	file_size_total = 0;

	str = cmd_line;

	j = 0;			/* See close_handles */

	if (!load_addr || !load_size)
		return EFI_INVALID_PARAMETER;

	*load_addr = 0;
	*load_size = 0;

	if (!str || !*str)
		return EFI_SUCCESS;

	for (nr_files = 0; *str; nr_files++) {
		str = strstr(str, option_string);
		if (!str)
			break;

		str += strlen(option_string);

		/* Skip any leading slashes */
		while (*str == '/' || *str == '\\')
			str++;

		while (*str && *str != ' ' && *str != '\n')
			str++;
	}

	if (!nr_files)
		return EFI_SUCCESS;

	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
			     nr_files * sizeof(*files), (void **)&files);
	if (status != EFI_SUCCESS) {
		pr_efi_err("Failed to alloc mem for file handle list\n");
		goto fail;
	}

	str = cmd_line;
	for (i = 0; i < nr_files; i++) {
		struct file_info *file;
		efi_char16_t filename_16[256];
		efi_char16_t *p;

		str = strstr(str, option_string);
		if (!str)
			break;

		str += strlen(option_string);

		file = &files[i];
		p = filename_16;

		/* Skip any leading slashes */
		while (*str == '/' || *str == '\\')
			str++;

		while (*str && *str != ' ' && *str != '\n') {
			if ((u8 *)p >= (u8 *)filename_16 + sizeof(filename_16))
				break;

			if (*str == '/') {
				*p++ = '\\';
				str++;
			} else {
				*p++ = *str++;
			}
		}

		*p = '\0';

		/* Only open the volume once. */
		if (!i) {
			status = efi_open_volume(image, &fh);
			if (status != EFI_SUCCESS)
				goto free_files;
		}

		status = efi_file_size(fh, filename_16, (void **)&file->handle,
				       &file->size);
		if (status != EFI_SUCCESS)
			goto close_handles;

		file_size_total += file->size;
	}

	if (file_size_total) {
		unsigned long addr;

		/*
		 * Multiple files need to be at consecutive addresses in memory,
		 * so allocate enough memory for all the files.  This is used
		 * for loading multiple files.
		 */
		status = efi_allocate_pages(file_size_total, &file_addr, max_addr);
		if (status != EFI_SUCCESS) {
			pr_efi_err("Failed to alloc highmem for files\n");
			goto close_handles;
		}

		/* We've run out of free low memory. */
		if (file_addr > max_addr) {
			pr_efi_err("We've run out of free low memory\n");
			status = EFI_INVALID_PARAMETER;
			goto free_file_total;
		}

		addr = file_addr;
		for (j = 0; j < nr_files; j++) {
			unsigned long size;

			size = files[j].size;
			while (size) {
				unsigned long chunksize;

				if (size > efi_chunk_size)
					chunksize = efi_chunk_size;
				else
					chunksize = size;

				status = efi_file_read(files[j].handle,
						       &chunksize,
						       (void *)addr);
				if (status != EFI_SUCCESS) {
					pr_efi_err("Failed to read file\n");
					goto free_file_total;
				}
				addr += chunksize;
				size -= chunksize;
			}

			efi_file_close(files[j].handle);
		}

	}

	efi_bs_call(free_pool, files);

	*load_addr = file_addr;
	*load_size = file_size_total;

	return status;

free_file_total:
	efi_free(file_size_total, file_addr);

close_handles:
	for (k = j; k < i; k++)
		efi_file_close(files[k].handle);
free_files:
	efi_bs_call(free_pool, files);
fail:
	*load_addr = 0;
	*load_size = 0;

	return status;
}