From patchwork Tue Jul 7 03:11:53 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Heinrich Schuchardt X-Patchwork-Id: 240858 List-Id: U-Boot discussion From: xypron.glpk at gmx.de (Heinrich Schuchardt) Date: Tue, 7 Jul 2020 05:11:53 +0200 Subject: [PATCH v2 10/17] efi_loader: UEFI variable persistence In-Reply-To: <20200707031200.65511-1-xypron.glpk@gmx.de> References: <20200707031200.65511-1-xypron.glpk@gmx.de> Message-ID: <20200707031200.65511-11-xypron.glpk@gmx.de> 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. The file is read during the UEFI sub-system initialization to restore non-volatile UEFI variables. Signed-off-by: Heinrich Schuchardt --- include/efi_variable.h | 62 +++++++++ lib/efi_loader/Kconfig | 8 ++ lib/efi_loader/Makefile | 1 + lib/efi_loader/efi_var_file.c | 239 ++++++++++++++++++++++++++++++++++ lib/efi_loader/efi_variable.c | 10 +- 5 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 lib/efi_loader/efi_var_file.c -- 2.27.0 diff --git a/include/efi_variable.h b/include/efi_variable.h index 3ba274fce1..01054209c4 100644 --- a/include/efi_variable.h +++ b/include/efi_variable.h @@ -79,4 +79,66 @@ efi_status_t efi_query_variable_info_int(u32 attributes, u64 *remaining_variable_storage_size, u64 *maximum_variable_size); +#define EFI_VAR_FILE_NAME "ubootefi.var" + +#define EFI_VAR_BUF_SIZE 0x4000 + +#define EFI_VAR_FILE_MAGIC 0x0161566966456255 /* UbEfiVa, version 1 */ + +/** + * struct efi_var_entry - UEFI variable file entry + * + * @length: length of enty, multiple of 8 + * @attr: variable attributes + * @time: authentication time (seconds since start of epoch) + * @guid: vendor GUID + * @name: UTF16 variable name + */ +struct efi_var_entry { + u32 length; + u32 attr; + u64 time; + efi_guid_t guid; + u16 name[]; +}; + +/** + * struct efi_var_file - file for storing UEFI variables + * + * @reserved: unused, may be overwritten by memory probing + * @magic: identifies file format + * @length: length including header + * @crc32: CRC32 without header + * @var: variables + */ +struct efi_var_file { + u64 reserved; + u64 magic; + u32 length; + u32 crc32; + struct efi_var_entry var[]; +}; + +/** + * 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); + +/** + * 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); + #endif diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig index 6c9df3a767..4324694d48 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 7eddd7ef37..c87b82db32 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -40,6 +40,7 @@ ifeq ($(CONFIG_EFI_MM_COMM_TEE),y) obj-y += efi_variable_tee.o else obj-y += efi_variable.o +obj-y += efi_var_file.o endif obj-y += efi_watchdog.o obj-$(CONFIG_LCD) += efi_gop.o diff --git a/lib/efi_loader/efi_var_file.c b/lib/efi_loader/efi_var_file.c new file mode 100644 index 0000000000..b1b7532495 --- /dev/null +++ b/lib/efi_loader/efi_var_file.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * File interface for UEFI variables + * + * Copyright (c) 2020, Heinrich Schuchardt + */ + +#define LOG_CATEGORY LOGC_EFI + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) { + log_err("No EFI system partition\n"); + return EFI_DEVICE_ERROR; + } + 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) { + log_err("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, + &var->time); + 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) + log_err("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))) { + log_err("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, true); + if (ret != EFI_SUCCESS) + log_err("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) { + log_err("Out of memory\n"); + return EFI_OUT_OF_RESOURCES; + } + + ret = efi_set_blk_dev_to_system_partition(); + if (ret != EFI_SUCCESS) + 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)) { + log_err("Failed to load EFI variables\n"); + goto error; + } + if (buf->length != len || efi_var_restore(buf) != EFI_SUCCESS) + log_err("Invalid EFI variables file\n"); +error: + free(buf); +#endif + return EFI_SUCCESS; +} diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c index f8a50bb1be..f2f787fc8d 100644 --- a/lib/efi_loader/efi_variable.c +++ b/lib/efi_loader/efi_variable.c @@ -988,6 +988,11 @@ out: ret = EFI_SUCCESS; } + /* 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(); + err: free(native_name); free(old_data); @@ -1083,6 +1088,7 @@ efi_set_variable_runtime(u16 *variable_name, const efi_guid_t *vendor, */ void efi_variables_boot_exit_notify(void) { + /* 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; @@ -1103,6 +1109,8 @@ efi_status_t efi_init_variables(void) efi_status_t ret; ret = efi_init_secure_state(); + if (ret != EFI_SUCCESS) + return ret; - return ret; + return efi_var_from_file(); }