[RFC 11/14] efi_loader: variable: export variables table for runtime access

AKASHI Takahiro
2020-03-17T02:12:44Z

There are platforms on which OS's won't be able to access UEFI variables
at runtime (i.e. after ExitBootServices).
With this patch, all the UEFI variables are exported as a configuration
table so as to enable retrieving variables' values from the table, and
later modifying them via capsule-on-disk if necessary.

The idea is based on Peter's proposal[1] in boot-arch ML.

[1] https://lists.linaro.org/pipermail/boot-architecture/2018-October/000883.html

Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
---
include/efi_loader.h | 7 +++
lib/efi_loader/Kconfig | 10 ++++
lib/efi_loader/efi_boottime.c | 10 ++++
lib/efi_loader/efi_capsule.c | 4 ++
lib/efi_loader/efi_variable.c | 109 ++++++++++++++++++++++++++++++++++
5 files changed, 140 insertions(+)

diff --git a/include/efi_loader.h b/include/efi_loader.h
index 79bdf9586d24..93ed5502821c 100644
--- a/include/efi_loader.h
+++ b/include/efi_loader.h
@@ -183,6 +183,8 @@ extern const efi_guid_t efi_guid_hii_string_protocol;
extern const efi_guid_t efi_guid_capsule_report;
/* GUID of firmware management protocol */
extern const efi_guid_t efi_guid_firmware_management_protocol;
+/* GUID of runtime variable access */
+extern const efi_guid_t efi_guid_variable_storage;

/* GUID of RNG protocol */
extern const efi_guid_t efi_guid_rng_protocol;
@@ -659,6 +661,10 @@ efi_status_t EFIAPI efi_query_variable_info(
u64 *remaining_variable_storage_size,
u64 *maximum_variable_size);

+#ifdef CONFIG_EFI_VARIABLE_EXPORT
+efi_status_t efi_install_variables_table(void);
+#endif
+
/*
* See section 3.1.3 in the v2.7 UEFI spec for more details on
* the layout of EFI_LOAD_OPTION. In short it is:
@@ -683,6 +689,7 @@ struct efi_load_option {
void efi_deserialize_load_option(struct efi_load_option *lo, u8 *data);
unsigned long efi_serialize_load_option(struct efi_load_option *lo, u8 **data);
efi_status_t efi_bootmgr_load(efi_handle_t *handle);
+efi_status_t efi_install_variables_table(void);

/* Capsule update */
efi_status_t EFIAPI efi_update_capsule(
diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig
index 616e2acbe102..edb8d19059ea 100644
--- a/lib/efi_loader/Kconfig
+++ b/lib/efi_loader/Kconfig
@@ -41,6 +41,16 @@ config EFI_SET_TIME
Provide the SetTime() runtime service at boottime. This service
can be used by an EFI application to adjust the real time clock.

+config EFI_VARIABLE_EXPORT
+ bool "Export variables table as configuration table"
+ depends on !EFI_VARIABLE_RUNTIME_ACCESS
+ depends on EFI_CAPSULE_UPDATE_VARIABLE
+ default y
+ help
+ Select this option if you want to export UEFI variables as
+ configuration table so that OS can still get UEFI variable's
+ value.
+
config EFI_DEVICE_PATH_TO_TEXT
bool "Device path to text protocol"
default y
diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c
index c2a789b4f910..406644e4da67 100644
--- a/lib/efi_loader/efi_boottime.c
+++ b/lib/efi_loader/efi_boottime.c
@@ -2898,6 +2898,16 @@ efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle,
if (ret != EFI_SUCCESS)
return EFI_EXIT(EFI_INVALID_PARAMETER);

+#ifdef CONFIG_EFI_VARIABLE_EXPORT
+ /* Install variables database as a configuration table */
+ ret = efi_install_variables_table();
+ if (ret != EFI_SUCCESS)
+ return EFI_EXIT(ret);
+#endif
+ /*
+ * TODO: remove a table after image is terminated.
+ */
+
image_obj->exit_data_size = exit_data_size;
image_obj->exit_data = exit_data;

diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c
index 1293270aea95..6b737aec1b28 100644
--- a/lib/efi_loader/efi_capsule.c
+++ b/lib/efi_loader/efi_capsule.c
@@ -18,7 +18,11 @@ static const efi_guid_t efi_guid_firmware_management_capsule_id =
EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID;
const efi_guid_t efi_guid_firmware_management_protocol =
EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID;
+#ifdef CONFIG_EFI_VARIABLE_EXPORT
+const efi_guid_t efi_guid_variable_storage = EFI_VARIABLE_STORAGE_GUID;
+#else
static const efi_guid_t efi_guid_variable_storage = EFI_VARIABLE_STORAGE_GUID;
+#endif

/* for file system access */
static struct efi_file_handle *bootdev_root;
diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c
index c316bdfec0e4..270e5211f633 100644
--- a/lib/efi_loader/efi_variable.c
+++ b/lib/efi_loader/efi_variable.c
@@ -641,3 +641,112 @@ efi_status_t efi_init_variables(void)
{
return EFI_SUCCESS;
}
+
+#ifdef CONFIG_EFI_VARIABLE_EXPORT
+/**
+ * efi_install_variables_table() - install variables table
+ *
+ * This function adds a configuration table that contains all the cache
+ * data of UEFI variables for runtime access.
+ *
+ * Return: status code
+ */
+efi_status_t efi_install_variables_table(void)
+{
+ u16 var_name16[64];
+ efi_uintn_t var_name16_len;
+ efi_guid_t guid;
+ u32 attributes;
+ struct efi_capsule_header capsule;
+ u8 *data = NULL, *p;
+ efi_uintn_t capsule_size;
+ struct efi_ebbr_variable variable;
+ u8 *var_buf;
+ efi_uintn_t var_size, var_max_size;
+ efi_status_t ret;
+
+ capsule.capsule_guid = efi_guid_variable_storage;
+ capsule.header_size = sizeof(capsule);
+ capsule.flags = CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE;
+ capsule_size = sizeof(capsule);
+
+ /* Determine size of capsule */
+ var_name16[0] = 0;
+ var_name16_len = 64;
+ var_buf = NULL;
+ var_max_size = 0;
+ for (;;) {
+ ret = EFI_CALL(efi_get_next_variable_name(&var_name16_len,
+ var_name16,
+ &guid));
+ if (ret == EFI_NOT_FOUND)
+ break;
+ else if (ret != EFI_SUCCESS)
+ goto out;
+
+ var_size = 0;
+ ret = EFI_CALL(efi_get_variable(var_name16, &guid, &attributes,
+ &var_size, var_buf));
+ if (ret != EFI_BUFFER_TOO_SMALL)
+ goto out;
+
+ capsule_size += sizeof(variable) + var_size;
+ if (var_size > var_max_size)
+ var_max_size = var_size;
+ }
+ capsule.capsule_image_size = capsule_size;
+
+ ret = efi_allocate_pool(EFI_RUNTIME_SERVICES_DATA,
+ capsule_size, (void **)&data);
+ if (ret != EFI_SUCCESS)
+ return EFI_OUT_OF_RESOURCES;
+
+ var_buf = malloc(var_max_size);
+ if (!var_buf) {
+ ret = EFI_OUT_OF_RESOURCES;
+ goto out;
+ }
+
+ /* Copy data */
+ p = data;
+ memcpy(p, &capsule, sizeof(capsule));
+ p += sizeof(capsule);
+
+ var_name16[0] = 0;
+ for (;;) {
+ ret = EFI_CALL(efi_get_next_variable_name(&var_name16_len,
+ var_name16, &guid));
+ if (ret == EFI_NOT_FOUND)
+ break;
+ else if (ret != EFI_SUCCESS)
+ goto out;
+
+ var_size = var_max_size;
+ ret = EFI_CALL(efi_get_variable(var_name16, &guid, &attributes,
+ &var_size, var_buf));
+ if (ret != EFI_SUCCESS)
+ /* should not happen */
+ goto out;
+
+ u16_strcpy(variable.variable_name, var_name16);
+ variable.vendor_guid = guid;
+ variable.attributes = attributes;
+ variable.data_size = var_size;
+
+ memcpy(p, &variable, sizeof(variable));
+ p += sizeof(variable);
+ memcpy(p, var_buf, var_size);
+ p += var_size;
+ }
+
+ ret = EFI_CALL(efi_install_configuration_table(
+ &efi_guid_variable_storage, data));
+
+out:
+ free(var_buf);
+ if (ret != EFI_SUCCESS && data)
+ efi_free_pool(data);
+
+ return ret;
+}
+#endif /* CONFIG_EFI_VARIABLE_EXPORT */
--
2.25.1