Sending data from UEFI to OS through UEFI variables

Davy Souza
6 min readMar 23, 2021

Introduction

Sometimes while developing UEFI applications we face the need of transferring data from the UEFI environment to OS. By default, the UEFI environment doesn’t support any filesystem other than FAT-32. On the other hand, the OS filesystem usually is EXT4 (Linux) or NTFS (Windows).

In this article, we will develop a UEFI application to transfer data from UEFI to OS, and a Python application to transfer data back from OS to UEFI environment using UEFI variables.

UEFI Variables

UEFI defines variables through which an OS can interact with the firmware. A UEFI variable is specified with a combination of a GUID of the variable owner, a name, and a value. The GUID prevents name collisions between different vendors.

According to UEFI Specification,

Variables are defined as key/value pairs that consist of identifying information plus attributes (the key) and arbitrary data (the value). Variables are intended for use as a means to store data that is passed between the EFI environment implemented in the platform and EFI OS loaders and other applications that run in the EFI environment.

To create persistent variables between boots, an NVRAM (non-volatile RAM) is used in UEFI to store UEFI variables. Many of these NVRAM variables are architecturally defined, and setting invalid options to NVRAM could cause a machine to not be able to boot.

We can define two types of UEFI variables:

  • UEFI boot variables are used by the boot loader and used by the OS only for early system start-up.
  • UEFI runtime variables allow an OS to manage certain settings of the firmware like the UEFI boot manager or managing the keys for UEFI Secure Boot protocol etc.

Because the bootloader and other drivers are configured to load information from NVRAM, if we can write to some of these NVRAM variables, we will have control over parts of how the system boots.

There are some standard UEFI variables that are used for UEFI Boot Manager that are commonly used:

  • Boot####: A boot load option. #### is a printed hex value.
  • BootOrder: The ordered boot option load list.
  • BootNext: The boot option for the next boot only.
  • BootCurrent: The boot option selected for the current boot.

Last, if you need to secure the data into the variable you may want to look for Authenticated UEFI variables which are pretty similar to an ordinary variable but signing the content inside the variable. An authenticated variable can not be removed without the proper key.

You can check more about Boot Manager and UEFI variables in chapter 3 of UEFI Specification.

UEFI Side

At this point, I’m assuming that you already have the UEFI development environment configured on your system. However, if you need to set up your system for developing a UEFI application, you can check how to do it here.

The first part of our application will be responsible for reading a pre-defined UEFI variable and setting a new message to be read by the OS environment. So, let’s check the code:

We start by defining our variable by creating a name and a GUID. The same name and GUID will be used by the OS application to access the variable.

The implementation has a single function (EntryPoint) that can be split into three parts:

  • Get Variable: At the start of the function, using the name and GUID previously defined we call the GetVariable function of Runtime Services to read the value inside the variable.
  • Print Value: Just for validation purposes, we print the value from the UEFI variable. In our case, the value of the variable will be a UTF-8 string. If it is the first time running the code or if for some reason the variable doesn’t exist, nothing will be printed
  • Set Variable: The third part of the code is about setting a variable. Both creating a new variable or just editing a created one can be done by using the SetVariable function also in Runtime services.

You can delete a UEFI variable by using the SetVariable function passing the buffer size as zero and an empty buffer.

OS Side

The Linux Kernel allows us to access the UEFI variables without the need of accessing UEFI Runtime Services. To do so, we use the UEFI variable filesystem (efivarfs).

The efivarfs filesystem maps the UEFI variables into files in the path /sys/firmware/efi/efivars where it can be accessed as a regular file.

From Linux Kernel documentation,

Due to the presence of numerous firmware bugs where removing non-standard UEFI variables causes the system firmware to fail to POST, efivarfs files that are not well-known standardized variables are created as immutable files. This doesn’t prevent removal — “chattr -i” will work — but it does prevent this kind of failure from being accomplished accidentally.

Given this, the code below is a python implementation, based on the implementation of Finnbarr P. Murphy, of how to read, write or delete a UEFI NVRAM variable through the efivarfs.

At the start of the code, we have some flag and variable definitions (name and GUID). The filename of a UEFI variable at the efivarfs is defined by the concatenation of the variable name and GUID as follows:

  • /sys/firmware/efi/efivars/$(name)-$(guid)

The code is divided into four functions:

  • read_variable: Reads the file of the UEFI variable defined. The first four bytes of a variable are the UEFI attributes defined when the variable is set. The following bytes are the value stored in the variable.
  • write_variable: Writes a file to create a UEFI variable. If the variable already exists, the delete_variable function is called and then, the variable is created. Same as read, the first four bytes of the buffer to be stored are the UEFI attributes, followed by the content of the variable.
  • delete_variable: As deleting any file, we should just pass the file path as the parameter of os.remove function. But since a UEFI variable file is immutable, we need to set the file as mutable to be able of deleting it. Using the ioctl function we get all the file flags, change the value of the immutable attribute, and set flags using ioctl function one more time. After that, the os.remove function should delete the file.

Be careful while deleting variables at this way. As mentioned before, removing non-standard UEFI variables was the cause of firmware failures. I didn’t have any issue removing the created variable but I would recommend you to always remove a UEFI variable using UEFI Runtime Services if it’s possible. Also, do not remove any other variable if you are not sure about what are you doing.

  • main: The main function of the code. Not much implementation over here besides calling a read and write functions.

By running these applications you must see the message that was stored into the UEFI variable:

  • UEFI Application (running on UEFI Shell)
  • OS Application

Conclusion

That is it! Knowing how to use UEFI variables gives us the possibility of sending any kind of data from pre-OS to the OS environment or vice versa. It also allows us to change the boot manager options of a machine.

References

If you like this post you may want to check the repository with the source code. Feel free to send any questions about the topic and subscribe to get more knowledge about UEFI development.

--

--

Davy Souza

Software Development Analyst | Computer Science | Artificial Intelligence | Embedded Software | C#, C, C++, Python