diff mbox series

[10/16] efi_loader: UEFI variable persistence

Message ID 20200327052800.11022-11-xypron.glpk@gmx.de
State New
Headers show
Series efi_loader: non-volatile and runtime variables | expand

Commit Message

Heinrich Schuchardt March 27, 2020, 5:27 a.m. UTC
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 at 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

--
2.25.1

Comments

Punit Agrawal March 27, 2020, 8:07 a.m. UTC | #1
Heinrich Schuchardt <xypron.glpk at gmx.de> writes:

> 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()

I might be missing something but how does this cope with the ESP being
on a storage medium access to which is owned by the OS at runtime? e.g.,
partition on eMMC or SATA drive.

>
> The file is read during the UEFI sub-system initialization to restore
> non-volatile UEFI variables.
>
> Signed-off-by: Heinrich Schuchardt <xypron.glpk at 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/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

                 this

> +	  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/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
> + */

The return value doesn't seem to be used in this patch. Is it really
needed?

Thanks,
Punit

> +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
> +}
> +

[...]
Heinrich Schuchardt March 27, 2020, 10:30 a.m. UTC | #2
On 3/27/20 9:07 AM, Punit Agrawal wrote:
> Heinrich Schuchardt <xypron.glpk at gmx.de> writes:
>
>> 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()
>
> I might be missing something but how does this cope with the ESP being
> on a storage medium access to which is owned by the OS at runtime? e.g.,
> partition on eMMC or SATA drive.

This development does not guard against manipulation by the OS.

Ilias is cureently working on a solution for ATF based devices that will
provide secure storage for variables.

>
>>
>> The file is read during the UEFI sub-system initialization to restore
>> non-volatile UEFI variables.
>>
>> Signed-off-by: Heinrich Schuchardt <xypron.glpk at 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/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
>
>                   this
>
>> +	  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/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
>> + */
>
> The return value doesn't seem to be used in this patch. Is it really
> needed?

It can be remove.

Thanks for reviewing.

Best regards

Heinrich

>
> Thanks,
> Punit
>
>> +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
>> +}
>> +
>
> [...]
>
Punit Agrawal March 30, 2020, 10:03 a.m. UTC | #3
Heinrich Schuchardt <xypron.glpk at gmx.de> writes:

> On 3/27/20 9:07 AM, Punit Agrawal wrote:
>> Heinrich Schuchardt <xypron.glpk at gmx.de> writes:
>>
>>> 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()
>>
>> I might be missing something but how does this cope with the ESP being
>> on a storage medium access to which is owned by the OS at runtime? e.g.,
>> partition on eMMC or SATA drive.
>
> This development does not guard against manipulation by the OS.
>
> Ilias is cureently working on a solution for ATF based devices that will
> provide secure storage for variables.

Thanks for the clarification.

So the current patches are more RFC material - as it would be worth
seeing the whole picture before things start getting baked in.

I only recently started looking at EFI features in u-boot and am trying
to piece the story together based on the patches in-flight.

Thanks,
Punit

[...]
Peter Robinson Jan. 2, 2021, 10:15 p.m. UTC | #4
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

>
diff mbox series

Patch

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;
+}