1 | ---
|
---|
2 | title: UEFI Variable Policy Whitepaper
|
---|
3 | version: 1.0
|
---|
4 | copyright: Copyright (c) Microsoft Corporation.
|
---|
5 | ---
|
---|
6 |
|
---|
7 | # UEFI Variable Policy
|
---|
8 |
|
---|
9 | ## Summary
|
---|
10 |
|
---|
11 | UEFI Variable Policy spec aims to describe the DXE protocol interface
|
---|
12 | which allows enforcing certain rules on certain UEFI variables. The
|
---|
13 | protocol allows communication with the Variable Policy Engine which
|
---|
14 | performs the policy enforcement.
|
---|
15 |
|
---|
16 | The Variable Policy is comprised of a set of policy entries which
|
---|
17 | describe, per UEFI variable (identified by namespace GUID and variable
|
---|
18 | name) the following rules:
|
---|
19 |
|
---|
20 | - Required variable attributes
|
---|
21 | - Prohibited variable attributes
|
---|
22 | - Minimum variable size
|
---|
23 | - Maximum variable size
|
---|
24 | - Locking:
|
---|
25 | - Locking "immediately"
|
---|
26 | - Locking on creation
|
---|
27 | - Locking based on a state of another variable
|
---|
28 |
|
---|
29 | The spec assumes that the Variable Policy Engine runs in a trusted
|
---|
30 | enclave, potentially off the main CPU that runs UEFI. For that reason,
|
---|
31 | it is assumed that the Variable Policy Engine has no concept of UEFI
|
---|
32 | events, and that the communication from the DXE driver to the trusted
|
---|
33 | enclave is proprietary.
|
---|
34 |
|
---|
35 | At power-on, the Variable Policy Engine is:
|
---|
36 |
|
---|
37 | - Enabled -- present policy entries are evaluated on variable access
|
---|
38 | calls.
|
---|
39 | - Unlocked -- new policy entries can be registered.
|
---|
40 |
|
---|
41 | Policy is expected to be clear on power-on. Policy is volatile and not
|
---|
42 | preserved across system reset.
|
---|
43 |
|
---|
44 | ## DXE Protocol
|
---|
45 |
|
---|
46 | ```h
|
---|
47 | typedef struct {
|
---|
48 | UINT64 Revision;
|
---|
49 | DISABLE_VARIABLE_POLICY DisableVariablePolicy;
|
---|
50 | IS_VARIABLE_POLICY_ENABLED IsVariablePolicyEnabled;
|
---|
51 | REGISTER_VARIABLE_POLICY RegisterVariablePolicy;
|
---|
52 | DUMP_VARIABLE_POLICY DumpVariablePolicy;
|
---|
53 | LOCK_VARIABLE_POLICY LockVariablePolicy;
|
---|
54 | } _VARIABLE_POLICY_PROTOCOL;
|
---|
55 |
|
---|
56 | typedef _VARIABLE_POLICY_PROTOCOL VARIABLE_POLICY_PROTOCOL;
|
---|
57 |
|
---|
58 | extern EFI_GUID gVariablePolicyProtocolGuid;
|
---|
59 | ```
|
---|
60 |
|
---|
61 | ```text
|
---|
62 | ## Include/Protocol/VariablePolicy.h
|
---|
63 | gVariablePolicyProtocolGuid = { 0x81D1675C, 0x86F6, 0x48DF, { 0xBD, 0x95, 0x9A, 0x6E, 0x4F, 0x09, 0x25, 0xC3 } }
|
---|
64 | ```
|
---|
65 |
|
---|
66 | ### DisableVariablePolicy
|
---|
67 |
|
---|
68 | Function prototype:
|
---|
69 |
|
---|
70 | ```c
|
---|
71 | EFI_STATUS
|
---|
72 | EFIAPI
|
---|
73 | DisableVariablePolicy (
|
---|
74 | VOID
|
---|
75 | );
|
---|
76 | ```
|
---|
77 |
|
---|
78 | `DisableVariablePolicy` call disables the Variable Policy Engine, so
|
---|
79 | that the present policy entries are no longer taken into account on
|
---|
80 | variable access calls. This call effectively turns off the variable
|
---|
81 | policy verification for this boot. This also disables UEFI
|
---|
82 | Authenticated Variable protections including Secure Boot.
|
---|
83 | `DisableVariablePolicy` can only be called once during boot. If called
|
---|
84 | more than once, it will return `EFI_ALREADY_STARTED`. Note, this process
|
---|
85 | is irreversible until the next system reset -- there is no
|
---|
86 | "EnablePolicy" protocol function.
|
---|
87 |
|
---|
88 | _IMPORTANT NOTE:_ It is strongly recommended that VariablePolicy *NEVER*
|
---|
89 | be disabled in "normal, production boot conditions". It is expected to always
|
---|
90 | be enforced. The most likely reasons to disable are for Manufacturing and
|
---|
91 | Refurbishing scenarios. If in doubt, leave the `gEfiMdeModulePkgTokenSpaceGuid.PcdAllowVariablePolicyEnforcementDisable`
|
---|
92 | PCD set to `FALSE` and VariablePolicy will always be enabled.
|
---|
93 |
|
---|
94 | ### IsVariablePolicyEnabled
|
---|
95 |
|
---|
96 | Function prototype:
|
---|
97 |
|
---|
98 | ```c
|
---|
99 | EFI_STATUS
|
---|
100 | EFIAPI
|
---|
101 | IsVariablePolicyEnabled (
|
---|
102 | OUT BOOLEAN *State
|
---|
103 | );
|
---|
104 | ```
|
---|
105 |
|
---|
106 | `IsVariablePolicyEnabled` accepts a pointer to a Boolean in which it
|
---|
107 | will store `TRUE` if Variable Policy Engine is enabled, or `FALSE` if
|
---|
108 | Variable Policy Engine is disabled. The function returns `EFI_SUCCESS`.
|
---|
109 |
|
---|
110 | ### RegisterVariablePolicy
|
---|
111 |
|
---|
112 | Function prototype:
|
---|
113 |
|
---|
114 | ```c
|
---|
115 | EFI_STATUS
|
---|
116 | EFIAPI
|
---|
117 | RegisterVariablePolicy (
|
---|
118 | IN CONST VARIABLE_POLICY_ENTRY *PolicyEntry
|
---|
119 | );
|
---|
120 | ```
|
---|
121 |
|
---|
122 | `RegisterVariablePolicy` call accepts a pointer to a policy entry
|
---|
123 | structure and returns the status of policy registration. If the
|
---|
124 | Variable Policy Engine is not locked and the policy structures are
|
---|
125 | valid, the function will return `EFI_SUCCESS`. If the Variable Policy
|
---|
126 | Engine is locked, `RegisterVariablePolicy` call will return
|
---|
127 | `EFI_WRITE_PROTECTED` and will not register the policy entry. Bulk
|
---|
128 | registration is not supported at this time due to the requirements
|
---|
129 | around error handling on each policy registration.
|
---|
130 |
|
---|
131 | Upon successful registration of a policy entry, Variable Policy Engine
|
---|
132 | will then evaluate this entry on subsequent variable access calls (as
|
---|
133 | long as Variable Policy Engine hasn't been disabled).
|
---|
134 |
|
---|
135 | ### DumpVariablePolicy
|
---|
136 |
|
---|
137 | Function prototype:
|
---|
138 |
|
---|
139 | ```c
|
---|
140 | EFI_STATUS
|
---|
141 | EFIAPI
|
---|
142 | DumpVariablePolicy (
|
---|
143 | OUT UINT8 *Policy,
|
---|
144 | IN OUT UINT32 *Size
|
---|
145 | );
|
---|
146 | ```
|
---|
147 |
|
---|
148 | `DumpVariablePolicy` call accepts a pointer to a buffer and a pointer to
|
---|
149 | the size of the buffer as parameters and returns the status of placing
|
---|
150 | the policy into the buffer. On first call to `DumpVariablePolicy` one
|
---|
151 | should pass `NULL` as the buffer and a pointer to 0 as the `Size` variable
|
---|
152 | and `DumpVariablePolicy` will return `EFI_BUFFER_TOO_SMALL` and will
|
---|
153 | populate the `Size` parameter with the size of the needed buffer to
|
---|
154 | store the policy. This way, the caller can allocate the buffer of
|
---|
155 | correct size and call `DumpVariablePolicy` again. The function will
|
---|
156 | populate the buffer with policy and return `EFI_SUCCESS`.
|
---|
157 |
|
---|
158 | ### LockVariablePolicy
|
---|
159 |
|
---|
160 | Function prototype:
|
---|
161 |
|
---|
162 | ```c
|
---|
163 | EFI_STATUS
|
---|
164 | EFIAPI
|
---|
165 | LockVariablePolicy (
|
---|
166 | VOID
|
---|
167 | );
|
---|
168 | ```
|
---|
169 |
|
---|
170 | `LockVariablePolicy` locks the Variable Policy Engine, i.e. prevents any
|
---|
171 | new policy entries from getting registered in this boot
|
---|
172 | (`RegisterVariablePolicy` calls will fail with `EFI_WRITE_PROTECTED`
|
---|
173 | status code returned).
|
---|
174 |
|
---|
175 | ## Policy Structure
|
---|
176 |
|
---|
177 | The structure below is meant for the DXE protocol calling interface,
|
---|
178 | when communicating to the Variable Policy Engine, thus the pragma pack
|
---|
179 | directive. How these policies are stored in memory is up to the
|
---|
180 | implementation.
|
---|
181 |
|
---|
182 | ```c
|
---|
183 | #pragma pack(1)
|
---|
184 | typedef struct {
|
---|
185 | UINT32 Version;
|
---|
186 | UINT16 Size;
|
---|
187 | UINT16 OffsetToName;
|
---|
188 | EFI_GUID Namespace;
|
---|
189 | UINT32 MinSize;
|
---|
190 | UINT32 MaxSize;
|
---|
191 | UINT32 AttributesMustHave;
|
---|
192 | UINT32 AttributesCantHave;
|
---|
193 | UINT8 LockPolicyType;
|
---|
194 | UINT8 Reserved[3];
|
---|
195 | // UINT8 LockPolicy[]; // Variable Length Field
|
---|
196 | // CHAR16 Name[]; // Variable Length Field
|
---|
197 | } VARIABLE_POLICY_ENTRY;
|
---|
198 | ```
|
---|
199 |
|
---|
200 | The struct `VARIABLE_POLICY_ENTRY` above describes the layout for a policy
|
---|
201 | entry. The first element, `Size`, is the size of the policy entry, then
|
---|
202 | followed by `OffsetToName` -- the number of bytes from the beginning of
|
---|
203 | the struct to the name of the UEFI variable targeted by the policy
|
---|
204 | entry. The name can contain wildcards to match more than one variable,
|
---|
205 | more on this in the Wildcards section. The rest of the struct elements
|
---|
206 | are self-explanatory.
|
---|
207 |
|
---|
208 | ```cpp
|
---|
209 | #define VARIABLE_POLICY_TYPE_NO_LOCK 0
|
---|
210 | #define VARIABLE_POLICY_TYPE_LOCK_NOW 1
|
---|
211 | #define VARIABLE_POLICY_TYPE_LOCK_ON_CREATE 2
|
---|
212 | #define VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE 3
|
---|
213 | ```
|
---|
214 |
|
---|
215 | `LockPolicyType` can have the following values:
|
---|
216 |
|
---|
217 | - `VARIABLE_POLICY_TYPE_NO_LOCK` -- means that no variable locking is performed. However,
|
---|
218 | the attribute and size constraints are still enforced. LockPolicy
|
---|
219 | field is size 0.
|
---|
220 | - `VARIABLE_POLICY_TYPE_LOCK_NOW` -- means that the variable starts being locked
|
---|
221 | immediately after policy entry registration. If the variable doesn't
|
---|
222 | exist at this point, being LockedNow means it cannot be created on
|
---|
223 | this boot. LockPolicy field is size 0.
|
---|
224 | - `VARIABLE_POLICY_TYPE_LOCK_ON_CREATE` -- means that the variable starts being locked
|
---|
225 | after it is created. This allows for variable creation and
|
---|
226 | protection after LockVariablePolicy() function has been called. The
|
---|
227 | LockPolicy field is size 0.
|
---|
228 | - `VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE` -- means that the Variable Policy Engine will
|
---|
229 | examine the state/contents of another variable to determine if the
|
---|
230 | variable referenced in the policy entry is locked.
|
---|
231 |
|
---|
232 | ```c
|
---|
233 | typedef struct {
|
---|
234 | EFI_GUID Namespace;
|
---|
235 | UINT8 Value;
|
---|
236 | UINT8 Reserved;
|
---|
237 | // CHAR16 Name[]; // Variable Length Field
|
---|
238 | } VARIABLE_LOCK_ON_VAR_STATE_POLICY;
|
---|
239 | ```
|
---|
240 |
|
---|
241 | If `LockPolicyType` is `VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE`, then the final element in the
|
---|
242 | policy entry struct is of type `VARIABLE_LOCK_ON_VAR_STATE_POLICY`, which
|
---|
243 | lists the namespace GUID, name (no wildcards here), and value of the
|
---|
244 | variable which state determines the locking of the variable referenced
|
---|
245 | in the policy entry. The "locking" variable must be 1 byte in terms of
|
---|
246 | payload size. If the Referenced variable contents match the Value of the
|
---|
247 | `VARIABLE_LOCK_ON_VAR_STATE_POLICY` structure, the lock will be considered
|
---|
248 | active and the target variable will be locked. If the Reference variable
|
---|
249 | does not exist (ie. returns `EFI_NOT_FOUND`), this policy will be
|
---|
250 | considered inactive.
|
---|
251 |
|
---|
252 | ## Variable Name Wildcards
|
---|
253 |
|
---|
254 | Two types of wildcards can be used in the UEFI variable name field in a
|
---|
255 | policy entry:
|
---|
256 |
|
---|
257 | 1. If the Name is a zero-length array (easily checked by comparing
|
---|
258 | fields `Size` and `OffsetToName` -- if they're the same, then the
|
---|
259 | `Name` is zero-length), then all variables in the namespace specified
|
---|
260 | by the provided GUID are targeted by the policy entry.
|
---|
261 | 2. Character "#" in the `Name` corresponds to one numeric character
|
---|
262 | (0-9, A-F, a-f). For example, string "Boot####" in the `Name`
|
---|
263 | field of the policy entry will make it so that the policy entry will
|
---|
264 | target variables named "Boot0001", "Boot0002", etc.
|
---|
265 |
|
---|
266 | Given the above two types of wildcards, one variable can be targeted by
|
---|
267 | more than one policy entry, thus there is a need to establish the
|
---|
268 | precedence rule: a more specific match is applied. When a variable
|
---|
269 | access operation is performed, Variable Policy Engine should first check
|
---|
270 | the variable being accessed against the policy entries without
|
---|
271 | wildcards, then with 1 wildcard, then with 2 wildcards, etc., followed
|
---|
272 | in the end by policy entries that match the whole namespace. One can
|
---|
273 | still imagine a situation where two policy entries with the same number
|
---|
274 | of wildcards match the same variable -- for example, policy entries with
|
---|
275 | Names "Boot00##" and "Boot##01" will both match variable "Boot0001".
|
---|
276 | Such situation can (and should) be avoided by designing mutually
|
---|
277 | exclusive Name strings with wildcards, however, if it occurs, then the
|
---|
278 | policy entry that was registered first will be used. After the most
|
---|
279 | specific match is selected, all other policies are ignored.
|
---|
280 |
|
---|
281 | ## Available Testing
|
---|
282 |
|
---|
283 | This functionality is current supported by two kinds of tests: there is a host-based
|
---|
284 | unit test for the core business logic (this test accompanies the `VariablePolicyLib`
|
---|
285 | implementation that lives in `MdeModulePkg/Library`) and there is a functional test
|
---|
286 | for the protocol and its interfaces (this test lives in the `MdeModulePkg/Test/ShellTest`
|
---|
287 | directory).
|
---|
288 |
|
---|
289 | ### Host-Based Unit Test
|
---|
290 |
|
---|
291 | There is a test that can be run as part of the Host-Based Unit Testing
|
---|
292 | infrastructure provided by EDK2 PyTools (documented elsewhere). It will test
|
---|
293 | all internal guarantees and is where you will find test cases for most of the
|
---|
294 | policy matching and security of the Variable Policy Engine.
|
---|
295 |
|
---|
296 | ### Shell-Based Functional Test
|
---|
297 |
|
---|
298 | This test -- [Variable Policy Functional Unit Test](https://github.com/microsoft/mu_plus/tree/release/202005/UefiTestingPkg/FunctionalSystemTests/VarPolicyUnitTestApp) -- can be built as a
|
---|
299 | UEFI Shell application and run to validate that the Variable Policy Engine
|
---|
300 | is correctly installed and enforcing policies on the target system.
|
---|
301 |
|
---|
302 | NOTE: This test _must_ be run prior to calling `DisableVariablePolicy` for all
|
---|
303 | test cases to pass. For this reason, it is recommended to run this on a test-built
|
---|
304 | FW for complete results, and then again on a production-built FW for release
|
---|
305 | results.
|
---|
306 |
|
---|
307 | ## Use Cases
|
---|
308 |
|
---|
309 | The below examples are hypothetical scenarios based on real-world requirements
|
---|
310 | that demonstrate how Variable Policies could be constructed to solve various
|
---|
311 | problems.
|
---|
312 |
|
---|
313 | ### UEFI Setup Variables (Example 1)
|
---|
314 |
|
---|
315 | Variables containing values of the setup options exposed via UEFI
|
---|
316 | menu (setup variables). These would be locked based on a state of
|
---|
317 | another variable, "ReadyToBoot", which would be set to 1 at the
|
---|
318 | ReadyToBoot event. Thus, the policy for the setup variables would be
|
---|
319 | of type `LockOnVarState`, with the "ReadyToBoot" listed as the name of
|
---|
320 | the variable, appropriate GUID listed as the namespace, and 1 as
|
---|
321 | value. Entry into the trusted UEFI menu app doesn't signal
|
---|
322 | ReadyToBoot, but booting to any device does, and the setup variables
|
---|
323 | are write-protected. The "ReadyToBoot" variable would need to be
|
---|
324 | locked-on-create. *(THIS IS ESSENTIALLY LOCK ON EVENT, BUT SINCE THE
|
---|
325 | POLICY ENGINE IS NOT IN THE UEFI ENVIRONMENT VARIABLES ARE USED)*
|
---|
326 |
|
---|
327 | For example, "AllowPXEBoot" variable locked by "ReadyToBoot" variable.
|
---|
328 |
|
---|
329 | (NOTE: In the below example, the emphasized fields ('Namespace', 'Value', and 'Name')
|
---|
330 | are members of the `VARIABLE_LOCK_ON_VAR_STATE_POLICY` structure.)
|
---|
331 |
|
---|
332 | Size | ...
|
---|
333 | ---- | ---
|
---|
334 | OffsetToName | ...
|
---|
335 | NameSpace | ...
|
---|
336 | MinSize | ...
|
---|
337 | MaxSize | ...
|
---|
338 | AttributesMustHave | ...
|
---|
339 | AttributesCantHave | ...
|
---|
340 | LockPolicyType | `VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE`
|
---|
341 | _Namespace_ | ...
|
---|
342 | _Value_ | 1
|
---|
343 | _Name_ | "ReadyToBoot"
|
---|
344 | //Name | "AllowPXEBoot"
|
---|
345 |
|
---|
346 | ### Manufacturing VPD (Example 2)
|
---|
347 |
|
---|
348 | Manufacturing Variable Provisioning Data (VPD) is stored in
|
---|
349 | variables and is created while in Manufacturing (MFG) Mode. In MFG
|
---|
350 | Mode Variable Policy Engine is disabled, thus these VPD variables
|
---|
351 | can be created. These variables are locked with lock policy type
|
---|
352 | `LockNow`, so that these variables can't be tampered with in Customer
|
---|
353 | Mode. To overwrite or clear VPD, the device would need to MFG mode,
|
---|
354 | which is standard practice for refurbishing/remanufacturing
|
---|
355 | scenarios.
|
---|
356 |
|
---|
357 | Example: "DisplayPanelCalibration" variable...
|
---|
358 |
|
---|
359 | Size | ...
|
---|
360 | ---- | ---
|
---|
361 | OffsetToName | ...
|
---|
362 | NameSpace | ...
|
---|
363 | MinSize | ...
|
---|
364 | MaxSize | ...
|
---|
365 | AttributesMustHave | ...
|
---|
366 | AttributesCantHave | ...
|
---|
367 | LockPolicyType | `VARIABLE_POLICY_TYPE_LOCK_NOW`
|
---|
368 | // Name | "DisplayPanelCalibration"
|
---|
369 |
|
---|
370 | ### 3rd Party Calibration Data (Example 3)
|
---|
371 |
|
---|
372 | Bluetooth pre-pairing variables are locked-on-create because these
|
---|
373 | get created by an OS application when Variable Policy is in effect.
|
---|
374 |
|
---|
375 | Example: "KeyboardBTPairing" variable
|
---|
376 |
|
---|
377 | Size | ...
|
---|
378 | ---- | ---
|
---|
379 | OffsetToName | ...
|
---|
380 | NameSpace | ...
|
---|
381 | MinSize | ...
|
---|
382 | MaxSize | ...
|
---|
383 | AttributesMustHave | ...
|
---|
384 | AttributesCantHave | ...
|
---|
385 | LockPolicyType | `VARIABLE_POLICY_TYPE_LOCK_ON_CREATE`
|
---|
386 | // Name | "KeyboardBTPairing"
|
---|
387 |
|
---|
388 | ### Software-based Variable Policy (Example 4)
|
---|
389 |
|
---|
390 | Example: "Boot####" variables (a name string with wildcards that
|
---|
391 | will match variables "Boot0000" to "BootFFFF") locked by "LockBootOrder"
|
---|
392 | variable.
|
---|
393 |
|
---|
394 | Size | ...
|
---|
395 | ---- | ---
|
---|
396 | OffsetToName | ...
|
---|
397 | NameSpace | ...
|
---|
398 | MinSize | ...
|
---|
399 | MaxSize | ...
|
---|
400 | AttributesMustHave | ...
|
---|
401 | AttributesCantHave | ...
|
---|
402 | LockPolicyType | `VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE`
|
---|
403 | _Namespace_ | ...
|
---|
404 | _Value_ | 1
|
---|
405 | _Name_ | "LockBootOrder"
|
---|
406 | //Name | "Boot####"
|
---|