Skip to content

Variable Policy Lib

Summary

UEFI Variable Policy spec aims to describe the DXE protocol interface which allows enforcing certain rules on certain UEFI variables. The protocol allows communication with the Variable Policy Engine which performs the policy enforcement.

The Variable Policy is comprised of a set of policy entries which describe, per UEFI variable (identified by namespace GUID and variable name) the following rules:

  • Required variable attributes
  • Prohibited variable attributes
  • Minimum variable size
  • Maximum variable size
  • Locking:
  • Locking "immediately"
  • Locking on creation
  • Locking based on a state of another variable

The spec assumes that the Variable Policy Engine runs in a trusted enclave, potentially off the main CPU that runs UEFI. For that reason, it is assumed that the Variable Policy Engine has no concept of UEFI events, and that the communication from the DXE driver to the trusted enclave is proprietary.

At power-on, the Variable Policy Engine is:

  • Enabled -- present policy entries are evaluated on variable access calls.
  • Unlocked -- new policy entries can be registered.

Policy is expected to be clear on power-on. Policy is volatile and not preserved across system reset.

DXE Protocol

typedef struct {
  UINT64                        Revision;
  DISABLE_VARIABLE_POLICY       DisableVariablePolicy;
  IS_VARIABLE_POLICY_ENABLED    IsVariablePolicyEnabled;
  REGISTER_VARIABLE_POLICY      RegisterVariablePolicy;
  DUMP_VARIABLE_POLICY          DumpVariablePolicy;
  LOCK_VARIABLE_POLICY          LockVariablePolicy;
} _VARIABLE_POLICY_PROTOCOL;

typedef _VARIABLE_POLICY_PROTOCOL VARIABLE_POLICY_PROTOCOL;

extern EFI_GUID gVariablePolicyProtocolGuid;
## Include/Protocol/VariablePolicy.h
  gVariablePolicyProtocolGuid = { 0x81D1675C, 0x86F6, 0x48DF, { 0xBD, 0x95, 0x9A, 0x6E, 0x4F, 0x09, 0x25, 0xC3 } }

DisableVariablePolicy

Function prototype:

EFI_STATUS
EFIAPI
DisableVariablePolicy (
  VOID
  );

DisableVariablePolicy call disables the Variable Policy Engine, so that the present policy entries are no longer taken into account on variable access calls. This call effectively turns off the variable policy verification for this boot. This also disables UEFI Authenticated Variable protections including Secure Boot. DisableVariablePolicy can only be called once during boot. If called more than once, it will return EFI_ALREADY_STARTED. Note, this process is irreversible until the next system reset -- there is no "EnablePolicy" protocol function.

IMPORTANT NOTE: It is strongly recommended that VariablePolicy NEVER be disabled in "normal, production boot conditions". It is expected to always be enforced. The most likely reasons to disable are for Manufacturing and Refurbishing scenarios. If in doubt, leave the gEfiMdeModulePkgTokenSpaceGuid.PcdAllowVariablePolicyEnforcementDisable PCD set to FALSE and VariablePolicy will always be enabled.

IsVariablePolicyEnabled

Function prototype:

EFI_STATUS
EFIAPI
IsVariablePolicyEnabled (
  OUT BOOLEAN   *State
  );

IsVariablePolicyEnabled accepts a pointer to a Boolean in which it will store TRUE if Variable Policy Engine is enabled, or FALSE if Variable Policy Engine is disabled. The function returns EFI_SUCCESS.

RegisterVariablePolicy

Function prototype:

EFI_STATUS
EFIAPI
RegisterVariablePolicy (
  IN CONST VARIABLE_POLICY_ENTRY  *PolicyEntry
  );

RegisterVariablePolicy call accepts a pointer to a policy entry structure and returns the status of policy registration. If the Variable Policy Engine is not locked and the policy structures are valid, the function will return EFI_SUCCESS. If the Variable Policy Engine is locked, RegisterVariablePolicy call will return EFI_WRITE_PROTECTED and will not register the policy entry. Bulk registration is not supported at this time due to the requirements around error handling on each policy registration.

Upon successful registration of a policy entry, Variable Policy Engine will then evaluate this entry on subsequent variable access calls (as long as Variable Policy Engine hasn't been disabled).

DumpVariablePolicy

Function prototype:

EFI_STATUS
EFIAPI
DumpVariablePolicy (
  OUT     UINT8     *Policy,
  IN OUT  UINT32    *Size
  );

DumpVariablePolicy call accepts a pointer to a buffer and a pointer to the size of the buffer as parameters and returns the status of placing the policy into the buffer. On first call to DumpVariablePolicy one should pass NULL as the buffer and a pointer to 0 as the Size variable and DumpVariablePolicy will return EFI_BUFFER_TOO_SMALL and will populate the Size parameter with the size of the needed buffer to store the policy. This way, the caller can allocate the buffer of correct size and call DumpVariablePolicy again. The function will populate the buffer with policy and return EFI_SUCCESS.

LockVariablePolicy

Function prototype:

EFI_STATUS
EFIAPI
LockVariablePolicy (
  VOID
  );

LockVariablePolicy locks the Variable Policy Engine, i.e. prevents any new policy entries from getting registered in this boot (RegisterVariablePolicy calls will fail with EFI_WRITE_PROTECTED status code returned).

Policy Structure

The structure below is meant for the DXE protocol calling interface, when communicating to the Variable Policy Engine, thus the pragma pack directive. How these policies are stored in memory is up to the implementation.

#pragma pack(1)
typedef struct {
  UINT32    Version;
  UINT16    Size;
  UINT16    OffsetToName;
  EFI_GUID  Namespace;
  UINT32    MinSize;
  UINT32    MaxSize;
  UINT32    AttributesMustHave;
  UINT32    AttributesCantHave;
  UINT8     LockPolicyType;
  UINT8     Reserved[3];
  // UINT8  LockPolicy[]; // Variable Length Field
  // CHAR16 Name[];       // Variable Length Field
} VARIABLE_POLICY_ENTRY;

The struct VARIABLE_POLICY_ENTRY above describes the layout for a policy entry. The first element, Size, is the size of the policy entry, then followed by OffsetToName -- the number of bytes from the beginning of the struct to the name of the UEFI variable targeted by the policy entry. The name can contain wildcards to match more than one variable, more on this in the Wildcards section. The rest of the struct elements are self-explanatory.

#define VARIABLE_POLICY_TYPE_NO_LOCK            0
#define VARIABLE_POLICY_TYPE_LOCK_NOW           1
#define VARIABLE_POLICY_TYPE_LOCK_ON_CREATE     2
#define VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE  3

LockPolicyType can have the following values:

  • VARIABLE_POLICY_TYPE_NO_LOCK -- means that no variable locking is performed. However, the attribute and size constraints are still enforced. LockPolicy field is size 0.
  • VARIABLE_POLICY_TYPE_LOCK_NOW -- means that the variable starts being locked immediately after policy entry registration. If the variable doesn't exist at this point, being LockedNow means it cannot be created on this boot. LockPolicy field is size 0.
  • VARIABLE_POLICY_TYPE_LOCK_ON_CREATE -- means that the variable starts being locked after it is created. This allows for variable creation and protection after LockVariablePolicy() function has been called. The LockPolicy field is size 0.
  • VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE -- means that the Variable Policy Engine will examine the state/contents of another variable to determine if the variable referenced in the policy entry is locked.
typedef struct {
  EFI_GUID  Namespace;
  UINT8     Value;
  UINT8     Reserved;
  // CHAR16 Name[];   // Variable Length Field
} VARIABLE_LOCK_ON_VAR_STATE_POLICY;

If LockPolicyType is VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE, then the final element in the policy entry struct is of type VARIABLE_LOCK_ON_VAR_STATE_POLICY, which lists the namespace GUID, name (no wildcards here), and value of the variable which state determines the locking of the variable referenced in the policy entry. The "locking" variable must be 1 byte in terms of payload size. If the Referenced variable contents match the Value of the VARIABLE_LOCK_ON_VAR_STATE_POLICY structure, the lock will be considered active and the target variable will be locked. If the Reference variable does not exist (ie. returns EFI_NOT_FOUND), this policy will be considered inactive.

Variable Name Wildcards

Two types of wildcards can be used in the UEFI variable name field in a policy entry:

  1. If the Name is a zero-length array (easily checked by comparing fields Size and OffsetToName -- if they're the same, then the Name is zero-length), then all variables in the namespace specified by the provided GUID are targeted by the policy entry.
  2. Character "#" in the Name corresponds to one numeric character (0-9, A-F, a-f). For example, string "Boot####" in the Name field of the policy entry will make it so that the policy entry will target variables named "Boot0001", "Boot0002", etc.

Given the above two types of wildcards, one variable can be targeted by more than one policy entry, thus there is a need to establish the precedence rule: a more specific match is applied. When a variable access operation is performed, Variable Policy Engine should first check the variable being accessed against the policy entries without wildcards, then with 1 wildcard, then with 2 wildcards, etc., followed in the end by policy entries that match the whole namespace. One can still imagine a situation where two policy entries with the same number of wildcards match the same variable -- for example, policy entries with Names "Boot00##" and "Boot##01" will both match variable "Boot0001". Such situation can (and should) be avoided by designing mutually exclusive Name strings with wildcards, however, if it occurs, then the policy entry that was registered first will be used. After the most specific match is selected, all other policies are ignored.

Available Testing

This functionality is current supported by two kinds of tests: there is a host-based unit test for the core business logic (this test accompanies the VariablePolicyLib implementation that lives in MdeModulePkg/Library) and there is a functional test for the protocol and its interfaces (this test lives in the MdeModulePkg/Test/ShellTest directory).

Host-Based Unit Test

MU_CHANGE

This test:

MdeModulePkg\Library\VariablePolicyLib\VariablePolicyUnitTest\VariablePolicyUnitTest.inf

can be run as part of the Host-Based Unit Testing infrastructure provided by EDK2 PyTools (documented elsewhere). It will test all internal guarantees and is where you will find test cases for most of the policy matching and security of the Variable Policy Engine.

Shell-Based Functional Test

This test -- Variable Policy Functional Unit Test -- can be built as a UEFI Shell application and run to validate that the Variable Policy Engine is correctly installed and enforcing policies on the target system.

NOTE: This test must be run prior to calling DisableVariablePolicy for all test cases to pass. For this reason, it is recommended to run this on a test-built FW for complete results, and then again on a production-built FW for release results.

Use Cases

The below examples are hypothetical scenarios based on real-world requirements that demonstrate how Variable Policies could be constructed to solve various problems.

UEFI Setup Variables (Example 1)

Variables containing values of the setup options exposed via UEFI menu (setup variables). These would be locked based on a state of another variable, "ReadyToBoot", which would be set to 1 at the ReadyToBoot event. Thus, the policy for the setup variables would be of type LockOnVarState, with the "ReadyToBoot" listed as the name of the variable, appropriate GUID listed as the namespace, and 1 as value. Entry into the trusted UEFI menu app doesn't signal ReadyToBoot, but booting to any device does, and the setup variables are write-protected. The "ReadyToBoot" variable would need to be locked-on-create. (THIS IS ESSENTIALLY LOCK ON EVENT, BUT SINCE THE POLICY ENGINE IS NOT IN THE UEFI ENVIRONMENT VARIABLES ARE USED)

For example, "AllowPXEBoot" variable locked by "ReadyToBoot" variable.

(NOTE: In the below example, the emphasized fields ('Namespace', 'Value', and 'Name') are members of the VARIABLE_LOCK_ON_VAR_STATE_POLICY structure.)

Size ...
OffsetToName ...
NameSpace ...
MinSize ...
MaxSize ...
AttributesMustHave ...
AttributesCantHave ...
LockPolicyType VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE
Namespace ...
Value 1
Name "ReadyToBoot"
//Name "AllowPXEBoot"

Manufacturing VPD (Example 2)

Manufacturing Variable Provisioning Data (VPD) is stored in variables and is created while in Manufacturing (MFG) Mode. In MFG Mode Variable Policy Engine is disabled, thus these VPD variables can be created. These variables are locked with lock policy type LockNow, so that these variables can't be tampered with in Customer Mode. To overwrite or clear VPD, the device would need to MFG mode, which is standard practice for refurbishing/remanufacturing scenarios.

Example: "DisplayPanelCalibration" variable...

Size ...
OffsetToName ...
NameSpace ...
MinSize ...
MaxSize ...
AttributesMustHave ...
AttributesCantHave ...
LockPolicyType VARIABLE_POLICY_TYPE_LOCK_NOW
// Name "DisplayPanelCalibration"

3rd Party Calibration Data (Example 3)

Bluetooth pre-pairing variables are locked-on-create because these get created by an OS application when Variable Policy is in effect.

Example: "KeyboardBTPairing" variable

Size ...
OffsetToName ...
NameSpace ...
MinSize ...
MaxSize ...
AttributesMustHave ...
AttributesCantHave ...
LockPolicyType VARIABLE_POLICY_TYPE_LOCK_ON_CREATE
// Name "KeyboardBTPairing"

Software-based Variable Policy (Example 4)

Example: "Boot####" variables (a name string with wildcards that will match variables "Boot0000" to "BootFFFF") locked by "LockBootOrder" variable.

Size ...
OffsetToName ...
NameSpace ...
MinSize ...
MaxSize ...
AttributesMustHave ...
AttributesCantHave ...
LockPolicyType VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE
Namespace ...
Value 1
Name "LockBootOrder"
//Name "Boot####"