efi/libstub: Clean up command line parsing routine
We currently parse the command non-destructively, to avoid having to allocate memory for a copy before passing it to the standard parsing routines that are used by the core kernel, and which modify the input to delineate the parsed tokens with NUL characters. Instead, we call strstr() and strncmp() to go over the input multiple times, and match prefixes rather than tokens, which implies that we would match, e.g., 'nokaslrfoo' in the stub and disable KASLR, while the kernel would disregard the option and run with KASLR enabled. In order to avoid having to reason about whether and how this behavior may be abused, let's clean up the parsing routines, and rebuild them on top of the existing helpers. Signed-off-by: Ard Biesheuvel <ardb@kernel.org>
This commit is contained in:
parent
31f5e54665
commit
91d150c0cc
|
@ -47,6 +47,7 @@ __efistub_stext = stext;
|
|||
__efistub__end = _end;
|
||||
__efistub__edata = _edata;
|
||||
__efistub_screen_info = screen_info;
|
||||
__efistub__ctype = _ctype;
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ OBJECT_FILES_NON_STANDARD := y
|
|||
KCOV_INSTRUMENT := n
|
||||
|
||||
lib-y := efi-stub-helper.o gop.o secureboot.o tpm.o \
|
||||
file.o mem.o random.o randomalloc.o pci.o
|
||||
file.o mem.o random.o randomalloc.o pci.o \
|
||||
skip_spaces.o lib-cmdline.o lib-ctype.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
|
||||
|
|
|
@ -68,66 +68,39 @@ void efi_printk(char *str)
|
|||
*/
|
||||
efi_status_t efi_parse_options(char const *cmdline)
|
||||
{
|
||||
char *str;
|
||||
size_t len = strlen(cmdline) + 1;
|
||||
efi_status_t status;
|
||||
char *str, *buf;
|
||||
|
||||
str = strstr(cmdline, "nokaslr");
|
||||
if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
|
||||
efi_nokaslr = true;
|
||||
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, len, (void **)&buf);
|
||||
if (status != EFI_SUCCESS)
|
||||
return status;
|
||||
|
||||
str = strstr(cmdline, "quiet");
|
||||
if (str == cmdline || (str && str > cmdline && *(str - 1) == ' '))
|
||||
efi_quiet = true;
|
||||
str = skip_spaces(memcpy(buf, cmdline, len));
|
||||
|
||||
/*
|
||||
* If no EFI parameters were specified on the cmdline we've got
|
||||
* nothing to do.
|
||||
*/
|
||||
str = strstr(cmdline, "efi=");
|
||||
if (!str)
|
||||
return EFI_SUCCESS;
|
||||
while (*str) {
|
||||
char *param, *val;
|
||||
|
||||
/* Skip ahead to first argument */
|
||||
str += strlen("efi=");
|
||||
str = next_arg(str, ¶m, &val);
|
||||
|
||||
/*
|
||||
* Remember, because efi= is also used by the kernel we need to
|
||||
* skip over arguments we don't understand.
|
||||
*/
|
||||
while (*str && *str != ' ') {
|
||||
if (!strncmp(str, "nochunk", 7)) {
|
||||
str += strlen("nochunk");
|
||||
efi_nochunk = true;
|
||||
if (!strcmp(param, "nokaslr")) {
|
||||
efi_nokaslr = true;
|
||||
} else if (!strcmp(param, "quiet")) {
|
||||
efi_quiet = true;
|
||||
} else if (!strcmp(param, "efi") && val) {
|
||||
efi_nochunk = parse_option_str(val, "nochunk");
|
||||
efi_novamap = parse_option_str(val, "novamap");
|
||||
|
||||
efi_nosoftreserve = IS_ENABLED(CONFIG_EFI_SOFT_RESERVE) &&
|
||||
parse_option_str(val, "nosoftreserve");
|
||||
|
||||
if (parse_option_str(val, "disable_early_pci_dma"))
|
||||
efi_disable_pci_dma = true;
|
||||
if (parse_option_str(val, "no_disable_early_pci_dma"))
|
||||
efi_disable_pci_dma = false;
|
||||
}
|
||||
|
||||
if (!strncmp(str, "novamap", 7)) {
|
||||
str += strlen("novamap");
|
||||
efi_novamap = true;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_EFI_SOFT_RESERVE) &&
|
||||
!strncmp(str, "nosoftreserve", 7)) {
|
||||
str += strlen("nosoftreserve");
|
||||
efi_nosoftreserve = true;
|
||||
}
|
||||
|
||||
if (!strncmp(str, "disable_early_pci_dma", 21)) {
|
||||
str += strlen("disable_early_pci_dma");
|
||||
efi_disable_pci_dma = true;
|
||||
}
|
||||
|
||||
if (!strncmp(str, "no_disable_early_pci_dma", 24)) {
|
||||
str += strlen("no_disable_early_pci_dma");
|
||||
efi_disable_pci_dma = false;
|
||||
}
|
||||
|
||||
/* Group words together, delimited by "," */
|
||||
while (*str && *str != ' ' && *str != ',')
|
||||
str++;
|
||||
|
||||
if (*str == ',')
|
||||
str++;
|
||||
}
|
||||
|
||||
efi_bs_call(free_pool, buf);
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
11
drivers/firmware/efi/libstub/skip_spaces.c
Normal file
11
drivers/firmware/efi/libstub/skip_spaces.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
char *skip_spaces(const char *str)
|
||||
{
|
||||
while (isspace(*str))
|
||||
++str;
|
||||
return (char *)str;
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
* Copyright (C) 1991, 1992 Linus Torvalds
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
|
@ -56,3 +57,58 @@ int strncmp(const char *cs, const char *ct, size_t count)
|
|||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Works only for digits and letters, but small and fast */
|
||||
#define TOLOWER(x) ((x) | 0x20)
|
||||
|
||||
static unsigned int simple_guess_base(const char *cp)
|
||||
{
|
||||
if (cp[0] == '0') {
|
||||
if (TOLOWER(cp[1]) == 'x' && isxdigit(cp[2]))
|
||||
return 16;
|
||||
else
|
||||
return 8;
|
||||
} else {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* simple_strtoull - convert a string to an unsigned long long
|
||||
* @cp: The start of the string
|
||||
* @endp: A pointer to the end of the parsed string will be placed here
|
||||
* @base: The number base to use
|
||||
*/
|
||||
|
||||
unsigned long long simple_strtoull(const char *cp, char **endp, unsigned int base)
|
||||
{
|
||||
unsigned long long result = 0;
|
||||
|
||||
if (!base)
|
||||
base = simple_guess_base(cp);
|
||||
|
||||
if (base == 16 && cp[0] == '0' && TOLOWER(cp[1]) == 'x')
|
||||
cp += 2;
|
||||
|
||||
while (isxdigit(*cp)) {
|
||||
unsigned int value;
|
||||
|
||||
value = isdigit(*cp) ? *cp - '0' : TOLOWER(*cp) - 'a' + 10;
|
||||
if (value >= base)
|
||||
break;
|
||||
result = result * base + value;
|
||||
cp++;
|
||||
}
|
||||
if (endp)
|
||||
*endp = (char *)cp;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
long simple_strtol(const char *cp, char **endp, unsigned int base)
|
||||
{
|
||||
if (*cp == '-')
|
||||
return -simple_strtoull(cp + 1, endp, base);
|
||||
|
||||
return simple_strtoull(cp, endp, base);
|
||||
}
|
||||
|
|
|
@ -726,7 +726,7 @@ struct boot_params *efi_main(efi_handle_t handle,
|
|||
hdr->code32_start = (u32)bzimage_addr;
|
||||
|
||||
/*
|
||||
* make_boot_params() may have been called before efi_main(), in which
|
||||
* efi_pe_entry() may have been called before efi_main(), in which
|
||||
* case this is the second time we parse the cmdline. This is ok,
|
||||
* parsing the cmdline multiple times does not have side-effects.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user