mirror of
				https://git.proxmox.com/git/mirror_edk2
				synced 2025-10-31 14:27:52 +00:00 
			
		
		
		
	 355b181f74
			
		
	
	
		355b181f74
		
	
	
	
	
		
			
			https://bugzilla.tianocore.org/show_bug.cgi?id=2522 VariablePolicy is an updated interface to replace VarLock and VarCheckProtocol. Add the VariablePolicyLib library that implements the portable business logic for the VariablePolicy engine. Also add host-based CI test cases for the lib. Cc: Jian J Wang <jian.j.wang@intel.com> Cc: Hao A Wu <hao.a.wu@intel.com> Cc: Liming Gao <liming.gao@intel.com> Cc: Bret Barkelew <brbarkel@microsoft.com> Signed-off-by: Bret Barkelew <brbarkel@microsoft.com> Reviewed-by: Dandan Bi <dandan.bi@intel.com> Acked-by: Jian J Wang <jian.j.wang@intel.com>
		
			
				
	
	
		
			407 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			407 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| ---
 | |
| title:      UEFI Variable Policy Whitepaper
 | |
| version:    1.0
 | |
| copyright:  Copyright (c) Microsoft Corporation.
 | |
| ---
 | |
| 
 | |
| # UEFI Variable Policy
 | |
| 
 | |
| ## 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
 | |
| 
 | |
| ```h
 | |
| 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;
 | |
| ```
 | |
| 
 | |
| ```text
 | |
| ## Include/Protocol/VariablePolicy.h
 | |
|   gVariablePolicyProtocolGuid = { 0x81D1675C, 0x86F6, 0x48DF, { 0xBD, 0x95, 0x9A, 0x6E, 0x4F, 0x09, 0x25, 0xC3 } }
 | |
| ```
 | |
| 
 | |
| ### DisableVariablePolicy
 | |
| 
 | |
| Function prototype:
 | |
| 
 | |
| ```c
 | |
| 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:
 | |
| 
 | |
| ```c
 | |
| 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:
 | |
| 
 | |
| ```c
 | |
| 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:
 | |
| 
 | |
| ```c
 | |
| 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:
 | |
| 
 | |
| ```c
 | |
| 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.
 | |
| 
 | |
| ```c
 | |
| #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.
 | |
| 
 | |
| ```cpp
 | |
| #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.
 | |
| 
 | |
| ```c
 | |
| 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
 | |
| 
 | |
| There is a test that 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](https://github.com/microsoft/mu_plus/tree/release/202005/UefiTestingPkg/FunctionalSystemTests/VarPolicyUnitTestApp) -- 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####"
 |