Read and Write UEFI Variables Using Runtime Services
This article provides an overview of how software interacts with
Unified Extensible Firmware Interface (UEFI) NVRAM variables through
UEFI Runtime Services. It explains the core firmware
interfaces—GetVariable and SetVariable—and
demonstrates how both native UEFI applications and operating
system-level applications query, create, update, and delete these
variables across Linux and Windows environments.
The Core UEFI Runtime Services APIs
UEFI exposes its variable interface through the
EFI_RUNTIME_SERVICES table, which remains resident in
memory even after the operating system boots via the
ExitBootServices() transition. Two primary function
pointers handle variable operations:
GetVariable: Retrieves the data payload, size, and attributes of a specified variable name and vendor GUID.SetVariable: Creates a new variable, updates an existing variable's payload, or deletes a variable by passing a data size of zero.
Both functions require an identifying name (a null-terminated UTF-16
string) and a Vendor GUID (EFI_GUID), which prevents
namespace collisions between hardware vendors and OS bootloaders.
Key Variable Attributes
When writing variables, applications must define bitmask attributes that dictate lifecycle and accessibility:
EFI_VARIABLE_NON_VOLATILE(0x00000001): Stores the variable in persistent flash memory across power cycles.EFI_VARIABLE_BOOTSERVICE_ACCESS(0x00000002): Makes the variable accessible during the pre-boot phase.EFI_VARIABLE_RUNTIME_ACCESS(0x00000004): Allows the variable to be read or written by the OS kernel and runtime applications. Without this flag, the variable disappears from view afterExitBootServices().EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS(0x00000020): Requires a cryptographic signature and timestamp to modify, typically used for Secure Boot keys likePK,KEK, anddb.
Native UEFI Applications (C / EDK II)
Within native UEFI executables (such as bootloaders or diagnostics
run from the UEFI Shell), the runtime services table is accessed
globally via the gRT pointer provided by the EDK II
environment.
Reading a Variable
EFI_STATUS Status;
UINTN DataSize = 0;
VOID *Buffer = NULL;
EFI_GUID VendorGuid = SOME_VENDOR_GUID;
// Step 1: Query the required buffer size
Status = gRT->GetVariable(
L"MyVariable",
&VendorGuid,
NULL,
&DataSize,
NULL
);
if (Status == EFI_BUFFER_TOO_SMALL) {
Buffer = AllocatePool(DataSize);
Status = gRT->GetVariable(
L"MyVariable",
&VendorGuid,
NULL,
&DataSize,
Buffer
);
}Writing a Variable
UINT32 Value = 42;
UINT32 Attributes = EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS;
Status = gRT->SetVariable(
L"MyVariable",
&VendorGuid,
Attributes,
sizeof(Value),
&Value
);OS-Level Application Access
User-space applications cannot directly invoke firmware memory addresses due to CPU privilege separation (Ring 3 vs. Ring 0) and virtual memory paging. Instead, operating system kernels provide wrappers around the firmware calls.
Linux: efivarfs and
libefivar
On modern Linux systems, the kernel maps UEFI Runtime Services
through the efivarfs pseudo-filesystem, typically mounted
at /sys/firmware/efi/efivars/.
Variables are represented as files formatted as
[VariableName]-[VendorGUID]. The first 4 bytes of each file
represent a 32-bit bitmask of the variable attributes, followed
immediately by the binary data payload.
- Reading: An application opens the file, reads the first 4 bytes to determine attributes, and reads the remaining bytes to extract the payload.
- Writing: An application writes the 4-byte attribute
header followed by the binary payload to the file. Deletion is handled
using
unlink()or writing a 0-byte payload. - Immutable Protection: By default, the Linux kernel
sets the
chattr +i(immutable) attribute on critical variables to prevent accidental deletion or corruption. Applications must strip this attribute usingioctl(fd, FS_IOC_SETFLAGS, ...)before writing updates.
User-space C applications typically use libefivar to
abstract these low-level file and ioctl operations.
Windows: Win32 Firmware APIs
Windows exposes direct system calls that invoke
GetVariable and SetVariable through the
Windows kernel's Hardware Abstraction Layer (HAL).
User-space applications utilize two primary functions in
kernel32.dll:
GetFirmwareEnvironmentVariableExW()SetFirmwareEnvironmentVariableExW()
Implementation Requirements
- Privileges: The calling process must hold the
SE_SYSTEM_ENVIRONMENT_NAMEprivilege. This requires administrator elevation and explicit privilege token adjustment viaAdjustTokenPrivileges(). - GUID Format: The Vendor GUID must be passed as a
string enclosed in curly braces (e.g.,
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}").
Windows C++ Example
#include <windows.h>
#include <iostream>
void WriteVariable() {
// Elevate privileges to SE_SYSTEM_ENVIRONMENT_NAME before calling
const wchar_t* varName = L"MyVariable";
const wchar_t* varGuid = L"{12345678-1234-1234-1234-123456789abc}";
DWORD data = 1;
DWORD attributes = VARIABLE_ATTRIBUTE_NON_VOLATILE |
VARIABLE_ATTRIBUTE_BOOTSERVICE_ACCESS |
VARIABLE_ATTRIBUTE_RUNTIME_ACCESS;
BOOL success = SetFirmwareEnvironmentVariableExW(
varName,
varGuid,
&data,
sizeof(data),
attributes
);
if (!success) {
std::cerr << "Failed to write: " << GetLastError() << std::endl;
}
}Operational Safeguards and Limitations
- Flash Wear: UEFI variables reside in NOR flash memory. NOR flash supports a limited number of erase cycles (typically 10,000 to 100,000). Applications should minimize write frequency to prevent hardware wear.
- Garbage Collection: Firmware flash storage is small
(often 64 KB to a few megabytes shared across all system variables).
Appending or overwriting variables without firmware garbage collection
can lead to
EFI_OUT_OF_RESOURCESerrors. - Runtime Paging Changes: On x86_64 systems, when an
OS transitions to virtual memory, it invokes
SetVirtualAddressMap(). At this point, the firmware re-maps its pointers, ensuring that calls made via the OS kernel's HAL or drivers remain valid. Applications must always route operations through OS-provided interfaces rather than cached physical pointers.