diff mbox series

[v1,3/4] efi_loader: add an EFI variable with the variable file contents

Message ID 20240406140203.248211-9-ilias.apalodimas@linaro.org
State New
Headers show
Series None | expand

Commit Message

Ilias Apalodimas April 6, 2024, 2:01 p.m. UTC
Previous patches enabled SetVariableRT using a RAM backend.
Although EBBR [0] defines a variable format we can teach userspace tools
and write the altered variables, it's better if we skip the ABI
requirements completely.

So let's add a new variable, in its own namespace called "VarToFile"
which contains a binary dump of the updated RT, BS and, NV variables.

Some adjustments are needed to do that. Currently we discard BS-only
variables in EBS(). We need to preserve those on the OS RAM backend
that exposes the variables. Since BS-only variables can't appear at RT
we need to move the memory masking checks from efi_var_collect() to
efi_get_next_variable_name_mem()/efi_get_variable_mem() and do the
filtering at runtime. We also need to make efi_var_collect() available
at runtime, in order to construct the "VarToFile" buffer with BS, RT &
NV variables.

All users and applications (for linux) have to do when updating a variable
is dd that variable in the file described by "RTStorageVolatile".

Linux efivarfs uses a first 4 bytes of the output to represent attributes
in little-endian format. So, storing variables works like this:

$~ efibootmgr -n 0001
$~ dd if=/sys/firmware/efi/efivars/VarToFile-b2ac5fc9-92b7-4acd-aeac-11e818c3130c of=/boot/efi/ubootefi.var skip=4 bs=1

[0] https://arm-software.github.io/ebbr/index.html#document-chapter5-variable-storage

Suggested-by:Ard Biesheuvel <ardb@kernel.org> # dumping all variables to a variable
Signed-off-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
---
 include/efi_variable.h            |  15 +++-
 lib/efi_loader/efi_boottime.c     |   2 +
 lib/efi_loader/efi_var_common.c   |  43 +++++------
 lib/efi_loader/efi_var_file.c     |   1 -
 lib/efi_loader/efi_var_mem.c      |  90 ++++++++++-------------
 lib/efi_loader/efi_variable.c     | 118 ++++++++++++++++++++++++------
 lib/efi_loader/efi_variable_tee.c |   1 -
 7 files changed, 164 insertions(+), 106 deletions(-)

--
2.37.2
diff mbox series

Patch

diff --git a/include/efi_variable.h b/include/efi_variable.h
index 42a2b7c52bef..8963339b9bb6 100644
--- a/include/efi_variable.h
+++ b/include/efi_variable.h
@@ -271,13 +271,15 @@  const efi_guid_t *efi_auth_var_get_guid(const u16 *name);
  *
  * @variable_name_size:	size of variable_name buffer in bytes
  * @variable_name:	name of uefi variable's name in u16
+ * @mask:		bitmask with required attributes of variables to be collected.
+ *                      variables are only collected if all of the required
  * @vendor:		vendor's guid
  *
  * Return: status code
  */
 efi_status_t __efi_runtime
 efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size, u16 *variable_name,
-			       efi_guid_t *vendor);
+			       efi_guid_t *vendor, u32 mask);
 /**
  * efi_get_variable_mem() - Runtime common code across efi variable
  *                          implementations for GetVariable() from
@@ -289,12 +291,14 @@  efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size, u16 *variable_na
  * @data_size:		size of the buffer to which the variable value is copied
  * @data:		buffer to which the variable value is copied
  * @timep:		authentication time (seconds since start of epoch)
+ * @mask:		bitmask with required attributes of variables to be collected.
+ *                      variables are only collected if all of the required
  * Return:		status code
  */
 efi_status_t __efi_runtime
 efi_get_variable_mem(const u16 *variable_name, const efi_guid_t *vendor,
 		     u32 *attributes, efi_uintn_t *data_size, void *data,
-		     u64 *timep);
+		     u64 *timep, u32 mask);

 /**
  * efi_get_variable_runtime() - runtime implementation of GetVariable()
@@ -334,4 +338,11 @@  efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size,
  */
 void efi_var_buf_update(struct efi_var_file *var_buf);

+/**
+ * efi_prealloced_rt_memory() - Get a pointer to preallocated EFI memory
+ *                              available at runtime
+ *
+ * Return: pointer to preallocated runtime usable buffer
+ */
+void __efi_runtime *efi_prealloced_rt_memory(void);
 #endif
diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c
index 1951291747cd..39481c89a688 100644
--- a/lib/efi_loader/efi_boottime.c
+++ b/lib/efi_loader/efi_boottime.c
@@ -97,6 +97,8 @@  const efi_guid_t efi_guid_load_file_protocol = EFI_LOAD_FILE_PROTOCOL_GUID;
 const efi_guid_t efi_guid_load_file2_protocol = EFI_LOAD_FILE2_PROTOCOL_GUID;
 /* GUID of the SMBIOS table */
 const efi_guid_t smbios_guid = SMBIOS_TABLE_GUID;
+/* used by special U-Boot variables during SetVariableRT */
+const efi_guid_t efi_guid_efi_rt_var_file = U_BOOT_EFI_RT_VAR_FILE_GUID;

 static efi_status_t EFIAPI efi_disconnect_controller(
 					efi_handle_t controller_handle,
diff --git a/lib/efi_loader/efi_var_common.c b/lib/efi_loader/efi_var_common.c
index 07b9603d49f3..4abc90e411e7 100644
--- a/lib/efi_loader/efi_var_common.c
+++ b/lib/efi_loader/efi_var_common.c
@@ -182,7 +182,8 @@  efi_get_variable_runtime(u16 *variable_name, const efi_guid_t *guid,
 {
 	efi_status_t ret;

-	ret = efi_get_variable_mem(variable_name, guid, attributes, data_size, data, NULL);
+	ret = efi_get_variable_mem(variable_name, guid, attributes, data_size,
+				   data, NULL, EFI_VARIABLE_RUNTIME_ACCESS);

 	/* Remove EFI_VARIABLE_READ_ONLY flag */
 	if (attributes)
@@ -195,7 +196,8 @@  efi_status_t __efi_runtime EFIAPI
 efi_get_next_variable_name_runtime(efi_uintn_t *variable_name_size,
 				   u16 *variable_name, efi_guid_t *guid)
 {
-	return efi_get_next_variable_name_mem(variable_name_size, variable_name, guid);
+	return efi_get_next_variable_name_mem(variable_name_size, variable_name,
+					      guid, EFI_VARIABLE_RUNTIME_ACCESS);
 }

 /**
@@ -427,18 +429,15 @@  void *efi_get_var(const u16 *name, const efi_guid_t *vendor, efi_uintn_t *size)
  *
  * Return:	Status code
  */
-efi_status_t __maybe_unused efi_var_collect(struct efi_var_file **bufp, loff_t *lenp,
-					    u32 check_attr_mask)
+efi_status_t __efi_runtime
+efi_var_collect(struct efi_var_file **bufp, loff_t *lenp, u32 check_attr_mask)
 {
 	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;
+	buf = (struct efi_var_file *)efi_prealloced_rt_memory();
 	var = buf->var;
 	old_var = var;
 	for (;;) {
@@ -451,32 +450,26 @@  efi_status_t __maybe_unused efi_var_collect(struct efi_var_file **bufp, loff_t *
 			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);
+		efi_memcpy_runtime(var->name, old_var->name, old_var_name_length);
+		efi_memcpy_runtime(&var->guid, &old_var->guid, sizeof(efi_guid_t));
+		ret = efi_get_next_variable_name_mem(&var_name_length, var->name,
+						     &var->guid, check_attr_mask);
 		if (ret == EFI_NOT_FOUND)
 			break;
-		if (ret != EFI_SUCCESS) {
-			free(buf);
+		if (ret != EFI_SUCCESS)
 			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,
+		ret = efi_get_variable_mem(var->name, &var->guid,
 					   &var->attr, &data_length, data,
-					   &var->time);
-		if (ret != EFI_SUCCESS) {
-			free(buf);
+					   &var->time, check_attr_mask);
+		if (ret != EFI_SUCCESS)
 			return ret;
-		}
-		if ((var->attr & check_attr_mask) == check_attr_mask) {
-			var->length = data_length;
-			var = (struct efi_var_entry *)ALIGN((uintptr_t)data + data_length, 8);
-		}
+		var->length = data_length;
+		var = (struct efi_var_entry *)ALIGN((uintptr_t)data + data_length, 8);
 	}

 	buf->reserved = 0;
@@ -490,5 +483,3 @@  efi_status_t __maybe_unused efi_var_collect(struct efi_var_file **bufp, loff_t *

 	return EFI_SUCCESS;
 }
-
-
diff --git a/lib/efi_loader/efi_var_file.c b/lib/efi_loader/efi_var_file.c
index 413e1794e88c..8614e3d34706 100644
--- a/lib/efi_loader/efi_var_file.c
+++ b/lib/efi_loader/efi_var_file.c
@@ -83,7 +83,6 @@  efi_status_t efi_var_to_file(void)
 error:
 	if (ret != EFI_SUCCESS)
 		log_err("Failed to persist EFI variables\n");
-	free(buf);
 	return ret;
 #else
 	return EFI_SUCCESS;
diff --git a/lib/efi_loader/efi_var_mem.c b/lib/efi_loader/efi_var_mem.c
index 6c21cec5d457..a7af0604733e 100644
--- a/lib/efi_loader/efi_var_mem.c
+++ b/lib/efi_loader/efi_var_mem.c
@@ -16,6 +16,7 @@ 
  * relocation during SetVirtualAddressMap().
  */
 static struct efi_var_file __efi_runtime_data *efi_var_buf;
+static void __efi_runtime_data *efi_rt_prealloced;
 static struct efi_var_entry __efi_runtime_data *efi_current_var;

 /**
@@ -184,53 +185,6 @@  u64 __efi_runtime efi_var_mem_free(void)
 	       sizeof(struct efi_var_entry);
 }

-/**
- * efi_var_mem_bs_del() - delete boot service only variables
- */
-static void efi_var_mem_bs_del(void)
-{
-	struct efi_var_entry *var = efi_var_buf->var;
-
-	for (;;) {
-		struct efi_var_entry *last;
-
-		last = (struct efi_var_entry *)
-		       ((uintptr_t)efi_var_buf + efi_var_buf->length);
-		if (var >= last)
-			break;
-		if (var->attr & EFI_VARIABLE_RUNTIME_ACCESS) {
-			u16 *data;
-
-			/* skip variable */
-			for (data = var->name; *data; ++data)
-				;
-			++data;
-			var = (struct efi_var_entry *)
-			      ALIGN((uintptr_t)data + var->length, 8);
-		} else {
-			/* delete variable */
-			efi_var_mem_del(var);
-		}
-	}
-}
-
-/**
- * efi_var_mem_notify_exit_boot_services() - ExitBootService callback
- *
- * @event:	callback event
- * @context:	callback context
- */
-static void EFIAPI
-efi_var_mem_notify_exit_boot_services(struct efi_event *event, void *context)
-{
-	EFI_ENTRY("%p, %p", event, context);
-
-	/* Delete boot service only variables */
-	efi_var_mem_bs_del();
-
-	EFI_EXIT(EFI_SUCCESS);
-}
-
 /**
  * efi_var_mem_notify_exit_boot_services() - SetVirtualMemoryMap callback
  *
@@ -241,6 +195,7 @@  static void EFIAPI __efi_runtime
 efi_var_mem_notify_virtual_address_map(struct efi_event *event, void *context)
 {
 	efi_convert_pointer(0, (void **)&efi_var_buf);
+	efi_convert_pointer(0, (void **)&efi_rt_prealloced);
 	efi_current_var = NULL;
 }

@@ -261,13 +216,21 @@  efi_status_t efi_var_mem_init(void)
 	efi_var_buf->magic = EFI_VAR_FILE_MAGIC;
 	efi_var_buf->length = (uintptr_t)efi_var_buf->var -
 			      (uintptr_t)efi_var_buf;
-	/* crc32 for 0 bytes = 0 */

-	ret = efi_create_event(EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_CALLBACK,
-			       efi_var_mem_notify_exit_boot_services, NULL,
-			       NULL, &event);
+	/*
+	 * efi_var_collect() needs to run at runtime and provide us
+	 * copies of variables used for the VarToFile variable.
+	 * Preallocate memory equal to the variable storage and
+	 * preserve it to copy variables around
+	 */
+	ret = efi_allocate_pages(EFI_ALLOCATE_ANY_PAGES,
+				 EFI_RUNTIME_SERVICES_DATA,
+				 efi_size_in_pages(EFI_VAR_BUF_SIZE),
+				 &memory);
 	if (ret != EFI_SUCCESS)
 		return ret;
+	efi_rt_prealloced = (void *)(uintptr_t)memory;
+
 	ret = efi_create_event(EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE, TPL_CALLBACK,
 			       efi_var_mem_notify_virtual_address_map, NULL,
 			       NULL, &event);
@@ -279,7 +242,7 @@  efi_status_t efi_var_mem_init(void)
 efi_status_t __efi_runtime
 efi_get_variable_mem(const u16 *variable_name, const efi_guid_t *vendor,
 		     u32 *attributes, efi_uintn_t *data_size, void *data,
-		     u64 *timep)
+		     u64 *timep, u32 mask)
 {
 	efi_uintn_t old_size;
 	struct efi_var_entry *var;
@@ -291,6 +254,9 @@  efi_get_variable_mem(const u16 *variable_name, const efi_guid_t *vendor,
 	if (!var)
 		return EFI_NOT_FOUND;

+	if (mask && !((var->attr & mask) == mask))
+		return EFI_NOT_FOUND;
+
 	if (attributes)
 		*attributes = var->attr;
 	if (timep)
@@ -315,7 +281,8 @@  efi_get_variable_mem(const u16 *variable_name, const efi_guid_t *vendor,

 efi_status_t __efi_runtime
 efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size,
-			       u16 *variable_name, efi_guid_t *vendor)
+			       u16 *variable_name, efi_guid_t *vendor,
+			       u32 mask)
 {
 	struct efi_var_entry *var;
 	efi_uintn_t len, old_size;
@@ -324,6 +291,7 @@  efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size,
 	if (!variable_name_size || !variable_name || !vendor)
 		return EFI_INVALID_PARAMETER;

+skip:
 	len = *variable_name_size >> 1;
 	if (u16_strnlen(variable_name, len) == len)
 		return EFI_INVALID_PARAMETER;
@@ -347,6 +315,11 @@  efi_get_next_variable_name_mem(efi_uintn_t *variable_name_size,
 	efi_memcpy_runtime(variable_name, var->name, *variable_name_size);
 	efi_memcpy_runtime(vendor, &var->guid, sizeof(efi_guid_t));

+	if (mask && !((var->attr & mask) == mask)) {
+		*variable_name_size = old_size;
+		goto skip;
+	}
+
 	return EFI_SUCCESS;
 }

@@ -354,3 +327,14 @@  void efi_var_buf_update(struct efi_var_file *var_buf)
 {
 	memcpy(efi_var_buf, var_buf, EFI_VAR_BUF_SIZE);
 }
+
+void __efi_runtime *efi_prealloced_rt_memory(void)
+{
+	char *s;
+	int count = EFI_VAR_BUF_SIZE;
+
+	s = (char *)efi_rt_prealloced;
+	while (count--)
+		*s++ = 0;
+	return efi_rt_prealloced;
+}
diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c
index f97c8c57f75c..4f529169ea54 100644
--- a/lib/efi_loader/efi_variable.c
+++ b/lib/efi_loader/efi_variable.c
@@ -22,6 +22,8 @@ 
 #include <u-boot/crc.h>
 #include <asm/sections.h>

+static const efi_guid_t __efi_runtime_data efi_guid_efi_rt_var_file =
+						U_BOOT_EFI_RT_VAR_FILE_GUID;
 #ifdef CONFIG_EFI_SECURE_BOOT

 /**
@@ -208,14 +210,16 @@  efi_get_variable_int(const u16 *variable_name, const efi_guid_t *vendor,
 		     u32 *attributes, efi_uintn_t *data_size, void *data,
 		     u64 *timep)
 {
-	return efi_get_variable_mem(variable_name, vendor, attributes, data_size, data, timep);
+	return efi_get_variable_mem(variable_name, vendor, attributes, data_size,
+				    data, timep, 0);
 }

 efi_status_t __efi_runtime
 efi_get_next_variable_name_int(efi_uintn_t *variable_name_size,
 			       u16 *variable_name, efi_guid_t *vendor)
 {
-	return efi_get_next_variable_name_mem(variable_name_size, variable_name, vendor);
+	return efi_get_next_variable_name_mem(variable_name_size, variable_name,
+					      vendor, 0);
 }

 /**
@@ -479,6 +483,8 @@  if (IS_ENABLED(CONFIG_EFI_RT_VOLATILE_STORE)) {
 	efi_uintn_t ret;
 	bool append, delete;
 	u64 time = 0;
+	struct efi_var_file *buf;
+	loff_t len;

 	/*
 	 * Authenticated variables are not supported the rest of the checks
@@ -520,30 +526,60 @@  if (IS_ENABLED(CONFIG_EFI_RT_VOLATILE_STORE)) {
 			return EFI_NOT_FOUND;
 	}

-	if (delete) {
+	if (!delete) {
+		/*
+		 * We always insert new variabes and delete the old one when
+		 * appending
+		 */
+		len = 2 * (u16_strlen(variable_name) + 1) + data_size +
+			sizeof(struct efi_var_entry);
+		if (var && append)
+			 len += 2 * var->length;
+		/*
+		 * We will copy the variable update into VarToFile,
+		 * account for it twice
+		 */
+		len *= 2;
+		if (len > efi_var_mem_free())
+			return EFI_OUT_OF_RESOURCES;
+		if (append && var) {
+			u16 *old_data = var->name;
+
+			for (; *old_data; ++old_data)
+				;
+			++old_data;
+			ret = efi_var_mem_ins(variable_name, vendor, attributes,
+					      var->length, old_data, data_size,
+					      data, time);
+		} else {
+			ret = efi_var_mem_ins(variable_name, vendor, attributes,
+					      data_size, data, 0, NULL, time);
+		}
+	} else {
 		/* EFI_NOT_FOUND has been handled before */
 		attributes = var->attr;
 		ret = EFI_SUCCESS;
-	} else if (append && var) {
-		u16 *old_data = var->name;
-
-		for (; *old_data; ++old_data)
-			;
-		++old_data;
-		ret = efi_var_mem_ins(variable_name, vendor, attributes,
-				      var->length, old_data, data_size, data,
-				      time);
-	} else {
-		ret = efi_var_mem_ins(variable_name, vendor, attributes,
-				      data_size, data, 0, NULL, time);
 	}
-
 	if (ret != EFI_SUCCESS)
 		return ret;
 	/* We are always inserting new variables, get rid of the old copy */
 	efi_var_mem_del(var);

-	return EFI_SUCCESS;
+	/*
+	 * Create a volatile variable that userspace apps can dd and
+	 * update the file contents
+	 */
+	ret = efi_var_collect(&buf, &len, EFI_VARIABLE_NON_VOLATILE);
+	if (ret != EFI_SUCCESS)
+		return ret;
+	var = efi_var_mem_find(&efi_guid_efi_rt_var_file, u"VarToFile", NULL);
+	if (var)
+		efi_var_mem_del(var);
+
+	ret = efi_var_mem_ins(u"VarToFile", &efi_guid_efi_rt_var_file,
+			      EFI_VARIABLE_RUNTIME_ACCESS, len, buf, 0,
+			      NULL, time);
+	return ret;
 } else

 	return EFI_UNSUPPORTED;
@@ -557,11 +593,11 @@  void efi_variables_boot_exit_notify(void)
 	const efi_guid_t efi_guid_efi_rt_var_file = U_BOOT_EFI_RT_VAR_FILE_GUID;
 	const efi_guid_t rt_prop_guid = EFI_RT_PROPERTIES_TABLE_GUID;
 	efi_status_t ret;
+	struct efi_var_file *buf;
+	loff_t len;
+	bool fail = false;

 	if (IS_ENABLED(CONFIG_EFI_RT_VOLATILE_STORE)) {
-		struct efi_rt_properties_table *rt_prop =
-			efi_get_configuration_table(&rt_prop_guid);
-
 		ret = efi_set_variable_int(u"RTStorageVolatile",
 					   &efi_guid_efi_rt_var_file,
 					   EFI_VARIABLE_BOOTSERVICE_ACCESS |
@@ -569,11 +605,47 @@  void efi_variables_boot_exit_notify(void)
 					   EFI_VARIABLE_READ_ONLY,
 					   sizeof(EFI_VAR_FILE_NAME),
 					   EFI_VAR_FILE_NAME, false);
+		if (ret != EFI_SUCCESS) {
+			fail = true;
+			goto out;
+		}
+
+		ret = efi_var_collect(&buf, &len, EFI_VARIABLE_NON_VOLATILE);
+		if (ret != EFI_SUCCESS) {
+			fail = true;
+			goto out;
+		}
+
+		ret = efi_set_variable_int(u"VarToFile",
+					   &efi_guid_efi_rt_var_file,
+					   EFI_VARIABLE_BOOTSERVICE_ACCESS |
+					   EFI_VARIABLE_RUNTIME_ACCESS,
+					   len,
+					   buf, false);
 		if (ret != EFI_SUCCESS)
-			rt_prop->runtime_services_supported |= ~EFI_RT_SUPPORTED_SET_VARIABLE;
-		else
-			log_err("Can't RTStorage. SetVariableRT won't be available\n");
+			fail = true;
+out:
+		if (fail) {
+			efi_set_variable_int(u"RTStorageVolatile",
+					     &efi_guid_efi_rt_var_file,
+					     EFI_VARIABLE_BOOTSERVICE_ACCESS |
+					     EFI_VARIABLE_RUNTIME_ACCESS |
+					     EFI_VARIABLE_READ_ONLY, 0, 0,
+					     false);
+			efi_set_variable_int(u"VarToFile",
+					     &efi_guid_efi_rt_var_file,
+					     EFI_VARIABLE_BOOTSERVICE_ACCESS |
+					     EFI_VARIABLE_RUNTIME_ACCESS, 0, 0,
+					     false);
+		} else {
+			struct efi_rt_properties_table *rt_prop =
+				efi_get_configuration_table(&rt_prop_guid);
+
+			rt_prop->runtime_services_supported |=
+					EFI_RT_SUPPORTED_SET_VARIABLE;
+		}
 	}
+
 	/* Switch variable services functions to runtime version */
 	efi_runtime_services.get_variable = efi_get_variable_runtime;
 	efi_runtime_services.get_next_variable_name =
diff --git a/lib/efi_loader/efi_variable_tee.c b/lib/efi_loader/efi_variable_tee.c
index dde135fd9f81..9d0e270591ea 100644
--- a/lib/efi_loader/efi_variable_tee.c
+++ b/lib/efi_loader/efi_variable_tee.c
@@ -969,7 +969,6 @@  void efi_variables_boot_exit_notify(void)
 		log_err("Can't populate EFI variables. No runtime variables will be available\n");
 	else
 		efi_var_buf_update(var_buf);
-	free(var_buf);

 	/* Update runtime service table */
 	efi_runtime_services.query_variable_info =