[PATCH 10/16] efi_loader: UEFI variable persistence

Peter Robinson
2021-01-02T22:15:25Z

Hi Heinrich,

In testing this I've noticed one thing, if the ESP partition has a
type of FAT16 (type 6) it doesn't detect the partition, the EFI spec
says the ESP partition can be FAT16 and some devices at least the rpi2
won't boot if the partition ID is set to the efi partition naming, but
it errors with "No EFI system partition" if it's set as such.

Peter

> Persist non-volatile UEFI variables in a file on the EFI system partition.
>
> The file is written:
>
> * whenever a non-volatile UEFI variable is changed after initialization
> of the UEFI sub-system.
> * upon ExitBootServices()
>
> The file is read during the UEFI sub-system initialization to restore
> non-volatile UEFI variables.
>
> Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
> ---
> include/efi_variable.h | 36 +++++
> lib/efi_loader/Kconfig | 8 +
> lib/efi_loader/Makefile | 1 +
> lib/efi_loader/efi_variable.c | 12 +-
> lib/efi_loader/efi_variables_file.c | 235 ++++++++++++++++++++++++++++
> 5 files changed, 291 insertions(+), 1 deletion(-)
> create mode 100644 include/efi_variable.h
> create mode 100644 lib/efi_loader/efi_variables_file.c
>
> diff --git a/include/efi_variable.h b/include/efi_variable.h
> new file mode 100644
> index 0000000000..fb8294fc2e
> --- /dev/null
> +++ b/include/efi_variable.h
> @@ -0,0 +1,36 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * File interface for UEFI variables
> + *
> + * Copyright (c) 2020, Heinrich Schuchardt
> + */
> +
> +#ifndef _EFI_VARIABLE_H
> +#define _EFI_VARIABLE_H
> +
> +#define EFI_VAR_FILE_NAME "ubootefi.var"
> +
> +#define EFI_VAR_BUF_SIZE 0x4000
> +
> +#define EFI_VAR_FILE_MAGIC 0x7261566966456255 /* UbEfiVar */
> +
> +struct efi_var_entry {
> + u32 length;
> + u32 attr;
> + efi_guid_t guid;
> + u16 name[0];
> +};
> +
> +struct efi_var_file {
> + u64 reserved; /* May be overwritten by memory probing */
> + u64 magic;
> + u32 length;
> + u32 crc32;
> + struct efi_var_entry var[0];
> +};
> +
> +efi_status_t efi_var_to_file(void);
> +
> +efi_status_t efi_var_from_file(void);
> +
> +#endif
> diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig
> index e10ca05549..41705fc252 100644
> --- a/lib/efi_loader/Kconfig
> +++ b/lib/efi_loader/Kconfig
> @@ -27,6 +27,14 @@ config EFI_LOADER
>
> if EFI_LOADER
>
> +config EFI_VARIABLE_FILE_STORE
> + bool "Store non-volatile UEFI variables as file"
> + depends on FAT_WRITE
> + default y
> + help
> + Select tis option if you want non-volatile UEFI variables to be stored
> + as file /ubootefi.var on the EFI system partition.
> +
> config EFI_GET_TIME
> bool "GetTime() runtime service"
> depends on DM_RTC
> diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile
> index 9b3b704473..621a767ab3 100644
> --- a/lib/efi_loader/Makefile
> +++ b/lib/efi_loader/Makefile
> @@ -35,6 +35,7 @@ obj-y += efi_runtime.o
> obj-y += efi_setup.o
> obj-$(CONFIG_EFI_UNICODE_COLLATION_PROTOCOL2) += efi_unicode_collation.o
> obj-y += efi_variable.o
> +obj-y += efi_variables_file.o
> obj-y += efi_watchdog.o
> obj-$(CONFIG_LCD) += efi_gop.o
> obj-$(CONFIG_DM_VIDEO) += efi_gop.o
> diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c
> index d99ad6ddae..952a0a0db7 100644
> --- a/lib/efi_loader/efi_variable.c
> +++ b/lib/efi_loader/efi_variable.c
> @@ -7,6 +7,7 @@
>
> #include <common.h>
> #include <efi_loader.h>
> +#include <efi_variable.h>
> #include <env_internal.h>
> #include <hexdump.h>
> #include <malloc.h>
> @@ -604,6 +605,11 @@ efi_status_t efi_set_variable_int(u16 *variable_name,
> if (env_set(native_name, val))
> ret = EFI_DEVICE_ERROR;
>
> + /* Write non-volatile EFI variables to file */
> + if (attributes && EFI_VARIABLE_NON_VOLATILE &&
> + ret == EFI_SUCCESS && efi_obj_list_initialized == EFI_SUCCESS)
> + efi_var_to_file();
> +
> out:
> free(native_name);
> free(val);
> @@ -694,6 +700,10 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor,
> */
> void efi_variables_boot_exit_notify(void)
> {
> + /* Write non-volatile EFI variables to file */
> + efi_var_to_file();
> +
> + /* Switch variable services functions to runtime version */
> efi_runtime_services.get_variable = efi_get_variable_runtime;
> efi_runtime_services.get_next_variable_name =
> efi_get_next_variable_name_runtime;
> @@ -708,5 +718,5 @@ void efi_variables_boot_exit_notify(void)
> */
> efi_status_t efi_init_variables(void)
> {
> - return EFI_SUCCESS;
> + return efi_var_from_file();
> }
> diff --git a/lib/efi_loader/efi_variables_file.c b/lib/efi_loader/efi_variables_file.c
> new file mode 100644
> index 0000000000..4a918d3fde
> --- /dev/null
> +++ b/lib/efi_loader/efi_variables_file.c
> @@ -0,0 +1,235 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * File interface for UEFI variables
> + *
> + * Copyright (c) 2020, Heinrich Schuchardt
> + */
> +
> +#include <common.h>
> +#include <charset.h>
> +#include <fs.h>
> +#include <malloc.h>
> +#include <mapmem.h>
> +#include <efi_loader.h>
> +#include <efi_variable.h>
> +#include <u-boot/crc.h>
> +
> +#define PART_STR_LEN 10
> +
> +/**
> + * efi_set_blk_dev_to_system_partition() - select EFI system partition
> + *
> + * Set the EFI system partition as current block device.
> + *
> + * Return: status code
> + */
> +static efi_status_t __maybe_unused efi_set_blk_dev_to_system_partition(void)
> +{
> + char part_str[PART_STR_LEN];
> + int r;
> +
> + if (!efi_system_partition.if_type)
> + return EFI_NOT_FOUND;
> + snprintf(part_str, PART_STR_LEN, "%u:%u",
> + efi_system_partition.devnum, efi_system_partition.part);
> + r = fs_set_blk_dev(blk_get_if_type_name(efi_system_partition.if_type),
> + part_str, FS_TYPE_ANY);
> + if (r) {
> + printf("Cannot read EFI system partition\n");
> + return EFI_DEVICE_ERROR;
> + }
> + return EFI_SUCCESS;
> +}
> +
> +/**
> + * efi_var_collect() - collect non-volatile variables in buffer
> + *
> + * A buffer is allocated and filled with all non-volatile variables in a
> + * format ready to be written to disk.
> + *
> + * @bufp: pointer to pointer of buffer with collected variables
> + * @lenp: pointer to length of buffer
> + * Return: status code
> + */
> +static efi_status_t __maybe_unused efi_var_collect(struct efi_var_file **bufp,
> + loff_t *lenp)
> +{
> + size_t len = EFI_VAR_BUF_SIZE;
> + struct efi_var_file *buf;
> + struct efi_var_entry *var, *old_var;
> + size_t old_var_name_length = 2;
> +
> + *bufp = NULL; /* Avoid double free() */
> + buf = calloc(1, len);
> + if (!buf)
> + return EFI_OUT_OF_RESOURCES;
> + var = buf->var;
> + old_var = var;
> + for (;;) {
> + efi_uintn_t data_length, var_name_length;
> + u8 *data;
> + efi_status_t ret;
> +
> + if ((uintptr_t)buf + len <=
> + (uintptr_t)var->name + old_var_name_length)
> + return EFI_BUFFER_TOO_SMALL;
> +
> + var_name_length = (uintptr_t)buf + len - (uintptr_t)var->name;
> + memcpy(var->name, old_var->name, old_var_name_length);
> + guidcpy(&var->guid, &old_var->guid);
> + ret = efi_get_next_variable_name_int(
> + &var_name_length, var->name, &var->guid);
> + if (ret == EFI_NOT_FOUND)
> + break;
> + if (ret != EFI_SUCCESS) {
> + free(buf);
> + return ret;
> + }
> + old_var_name_length = var_name_length;
> + old_var = var;
> +
> + data = (u8 *)var->name + old_var_name_length;
> + data_length = (uintptr_t)buf + len - (uintptr_t)data;
> + ret = efi_get_variable_int(var->name, &var->guid,
> + &var->attr, &data_length, data);
> + if (ret != EFI_SUCCESS) {
> + free(buf);
> + return ret;
> + }
> + if (!(var->attr & EFI_VARIABLE_NON_VOLATILE))
> + continue;
> + var->length = data_length;
> + var = (struct efi_var_entry *)
> + ALIGN((uintptr_t)data + data_length, 8);
> + }
> +
> + buf->reserved = 0;
> + buf->magic = EFI_VAR_FILE_MAGIC;
> + len = (uintptr_t)var - (uintptr_t)buf;
> + buf->crc32 = crc32(0, (u8 *)buf->var,
> + len - sizeof(struct efi_var_file));
> + buf->length = len;
> + *bufp = buf;
> + *lenp = len;
> +
> + return EFI_SUCCESS;
> +}
> +
> +/**
> + * efi_var_to_file() - save non-volatile variables as file
> + *
> + * File ubootefi.var is created on the EFI system partion.
> + *
> + * Return: status code
> + */
> +efi_status_t efi_var_to_file(void)
> +{
> +#ifdef CONFIG_EFI_VARIABLE_FILE_STORE
> + efi_status_t ret;
> + struct efi_var_file *buf;
> + loff_t len;
> + loff_t actlen;
> + int r;
> +
> + ret = efi_var_collect(&buf, &len);
> + if (ret != EFI_SUCCESS)
> + goto error;
> +
> + ret = efi_set_blk_dev_to_system_partition();
> + if (ret != EFI_SUCCESS)
> + goto error;
> +
> + r = fs_write(EFI_VAR_FILE_NAME, map_to_sysmem(buf), 0, len, &actlen);
> + if (r || len != actlen)
> + ret = EFI_DEVICE_ERROR;
> +
> +error:
> + if (ret != EFI_SUCCESS)
> + printf("Failed to persist EFI variables\n");
> + free(buf);
> + return ret;
> +#else
> + return EFI_SUCCESS;
> +#endif
> +}
> +
> +/**
> + * efi_var_restore() - restore EFI variables from buffer
> + *
> + * @buf: buffer
> + * Return: status code
> + */
> +static efi_status_t __maybe_unused efi_var_restore(struct efi_var_file *buf)
> +{
> + struct efi_var_entry *var, *last_var;
> + efi_status_t ret;
> +
> + if (buf->reserved || buf->magic != EFI_VAR_FILE_MAGIC ||
> + buf->crc32 != crc32(0, (u8 *)buf->var,
> + buf->length - sizeof(struct efi_var_file))) {
> + printf("Invalid EFI variables file\n");
> + return EFI_INVALID_PARAMETER;
> + }
> +
> + var = buf->var;
> + last_var = (struct efi_var_entry *)((u8 *)buf + buf->length);
> + while (var < last_var) {
> + u16 *data = var->name + u16_strlen(var->name) + 1;
> +
> + if (var->attr & EFI_VARIABLE_NON_VOLATILE && var->length) {
> + ret = efi_set_variable_int(var->name, &var->guid,
> + var->attr, var->length,
> + data);
> + if (ret != EFI_SUCCESS)
> + printf("Failed to set EFI variable %ls\n",
> + var->name);
> + }
> + var = (struct efi_var_entry *)
> + ALIGN((uintptr_t)data + var->length, 8);
> + }
> + return EFI_SUCCESS;
> +}
> +
> +/**
> + * efi_var_from_file() - read variables from file
> + *
> + * File ubootefi.var is read from the EFI system partitions and the variables
> + * stored in the file are created.
> + *
> + * In case the file does not exist yet or a variable cannot be set EFI_SUCCESS
> + * is returned
> + *
> + * Return: status code
> + */
> +efi_status_t efi_var_from_file(void)
> +{
> +#ifdef CONFIG_EFI_VARIABLE_FILE_STORE
> + struct efi_var_file *buf;
> + loff_t len;
> + efi_status_t ret;
> + int r;
> +
> + buf = calloc(1, EFI_VAR_BUF_SIZE);
> + if (!buf) {
> + printf("Out of memory\n");
> + return EFI_OUT_OF_RESOURCES;
> + }
> +
> + ret = efi_set_blk_dev_to_system_partition();
> + if (ret != EFI_SUCCESS) {
> + printf("No EFI system partition\n");
> + goto error;
> + }
> + r = fs_read(EFI_VAR_FILE_NAME, map_to_sysmem(buf), 0, EFI_VAR_BUF_SIZE,
> + &len);
> + if (r || len < sizeof(struct efi_var_file)) {
> + printf("Failed to load EFI variables\n");
> + goto error;
> + }
> + if (buf->length != len || efi_var_restore(buf) != EFI_SUCCESS)
> + printf("Invalid EFI variables file\n");
> +error:
> + free(buf);
> +#endif
> + return EFI_SUCCESS;
> +}
> --
> 2.25.1
>