From 355b181f74050cdf2f09b1755c1a5ee4affb1faf Mon Sep 17 00:00:00 2001 From: Bret Barkelew Date: Mon, 9 Nov 2020 14:45:11 +0800 Subject: [PATCH] MdeModulePkg: Define the VariablePolicyLib 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 Cc: Hao A Wu Cc: Liming Gao Cc: Bret Barkelew Signed-off-by: Bret Barkelew Reviewed-by: Dandan Bi Acked-by: Jian J Wang --- .../Include/Library/VariablePolicyLib.h | 207 +++++ .../Library/VariablePolicyLib/ReadMe.md | 406 +++++++++ .../VariablePolicyExtraInitNull.c | 46 + .../VariablePolicyExtraInitRuntimeDxe.c | 85 ++ .../VariablePolicyLib/VariablePolicyLib.c | 830 ++++++++++++++++++ .../VariablePolicyLib/VariablePolicyLib.inf | 48 + .../VariablePolicyLib/VariablePolicyLib.uni | 12 + .../VariablePolicyLibRuntimeDxe.inf | 51 ++ MdeModulePkg/MdeModulePkg.ci.yaml | 4 +- MdeModulePkg/MdeModulePkg.dec | 3 + MdeModulePkg/MdeModulePkg.dsc | 5 + 11 files changed, 1696 insertions(+), 1 deletion(-) create mode 100644 MdeModulePkg/Include/Library/VariablePolicyLib.h create mode 100644 MdeModulePkg/Library/VariablePolicyLib/ReadMe.md create mode 100644 MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitNull.c create mode 100644 MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitRuntimeDxe.c create mode 100644 MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.c create mode 100644 MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.inf create mode 100644 MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.uni create mode 100644 MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLibRuntimeDxe.inf diff --git a/MdeModulePkg/Include/Library/VariablePolicyLib.h b/MdeModulePkg/Include/Library/VariablePolicyLib.h new file mode 100644 index 0000000000..efd1840112 --- /dev/null +++ b/MdeModulePkg/Include/Library/VariablePolicyLib.h @@ -0,0 +1,207 @@ +/** @file -- VariablePolicyLib.h +Business logic for Variable Policy enforcement. + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _VARIABLE_POLICY_LIB_H_ +#define _VARIABLE_POLICY_LIB_H_ + +#include + +/** + This API function validates and registers a new policy with + the policy enforcement engine. + + @param[in] NewPolicy Pointer to the incoming policy structure. + + @retval EFI_SUCCESS + @retval EFI_INVALID_PARAMETER NewPolicy is NULL or is internally inconsistent. + @retval EFI_ALREADY_STARTED An identical matching policy already exists. + @retval EFI_WRITE_PROTECTED The interface has been locked until the next reboot. + @retval EFI_UNSUPPORTED Policy enforcement has been disabled. No reason to add more policies. + @retval EFI_ABORTED A calculation error has prevented this function from completing. + @retval EFI_OUT_OF_RESOURCES Cannot grow the table to hold any more policies. + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +RegisterVariablePolicy ( + IN CONST VARIABLE_POLICY_ENTRY *NewPolicy + ); + + +/** + This API function checks to see whether the parameters to SetVariable would + be allowed according to the current variable policies. + + @param[in] VariableName Same as EFI_SET_VARIABLE. + @param[in] VendorGuid Same as EFI_SET_VARIABLE. + @param[in] Attributes Same as EFI_SET_VARIABLE. + @param[in] DataSize Same as EFI_SET_VARIABLE. + @param[in] Data Same as EFI_SET_VARIABLE. + + @retval EFI_SUCCESS A matching policy allows this update. + @retval EFI_SUCCESS There are currently no policies that restrict this update. + @retval EFI_SUCCESS The protections have been disable until the next reboot. + @retval EFI_WRITE_PROTECTED Variable is currently locked. + @retval EFI_INVALID_PARAMETER Attributes or size are invalid. + @retval EFI_ABORTED A lock policy exists, but an error prevented evaluation. + @retval EFI_NOT_READY Library has not been initialized. + +**/ +EFI_STATUS +EFIAPI +ValidateSetVariable ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN UINT32 Attributes, + IN UINTN DataSize, + IN VOID *Data + ); + + +/** + This API function disables the variable policy enforcement. If it's + already been called once, will return EFI_ALREADY_STARTED. + + @retval EFI_SUCCESS + @retval EFI_ALREADY_STARTED Has already been called once this boot. + @retval EFI_WRITE_PROTECTED Interface has been locked until reboot. + @retval EFI_WRITE_PROTECTED Interface option is disabled by platform PCD. + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +DisableVariablePolicy ( + VOID + ); + + +/** + This API function will dump the entire contents of the variable policy table. + + Similar to GetVariable, the first call can be made with a 0 size and it will return + the size of the buffer required to hold the entire table. + + @param[out] Policy Pointer to the policy buffer. Can be NULL if Size is 0. + @param[in,out] Size On input, the size of the output buffer. On output, the size + of the data returned. + + @retval EFI_SUCCESS Policy data is in the output buffer and Size has been updated. + @retval EFI_INVALID_PARAMETER Size is NULL, or Size is non-zero and Policy is NULL. + @retval EFI_BUFFER_TOO_SMALL Size is insufficient to hold policy. Size updated with required size. + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +DumpVariablePolicy ( + OUT UINT8 *Policy, + IN OUT UINT32 *Size + ); + + +/** + This API function returns whether or not the policy engine is + currently being enforced. + + @retval TRUE + @retval FALSE + @retval FALSE Library has not yet been initialized. + +**/ +BOOLEAN +EFIAPI +IsVariablePolicyEnabled ( + VOID + ); + + +/** + This API function locks the interface so that no more policy updates + can be performed or changes made to the enforcement until the next boot. + + @retval EFI_SUCCESS + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +LockVariablePolicy ( + VOID + ); + + +/** + This API function returns whether or not the policy interface is locked + for the remainder of the boot. + + @retval TRUE + @retval FALSE + @retval FALSE Library has not yet been initialized. + +**/ +BOOLEAN +EFIAPI +IsVariablePolicyInterfaceLocked ( + VOID + ); + + +/** + This helper function initializes the library and sets + up any required internal structures or handlers. + + Also registers the internal pointer for the GetVariable helper. + + @param[in] GetVariableHelper A function pointer matching the EFI_GET_VARIABLE prototype that will be used to + check policy criteria that involve the existence of other variables. + + @retval EFI_SUCCESS + @retval EFI_ALREADY_STARTED The initialize function has been called more than once without a call to + deinitialize. + +**/ +EFI_STATUS +EFIAPI +InitVariablePolicyLib ( + IN EFI_GET_VARIABLE GetVariableHelper + ); + + +/** + This helper function returns whether or not the library is currently initialized. + + @retval TRUE + @retval FALSE + +**/ +BOOLEAN +EFIAPI +IsVariablePolicyLibInitialized ( + VOID + ); + + +/** + This helper function tears down the library. + + Should generally only be used for test harnesses. + + @retval EFI_SUCCESS + @retval EFI_NOT_READY Deinitialize was called without first calling initialize. + +**/ +EFI_STATUS +EFIAPI +DeinitVariablePolicyLib ( + VOID + ); + + +#endif // _VARIABLE_POLICY_LIB_H_ diff --git a/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md b/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md new file mode 100644 index 0000000000..c2f9850a12 --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md @@ -0,0 +1,406 @@ +--- +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####" diff --git a/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitNull.c b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitNull.c new file mode 100644 index 0000000000..ad2ee0b2fb --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitNull.c @@ -0,0 +1,46 @@ +/** @file -- VariablePolicyExtraInitNull.c +This file contains extra init and deinit routines that don't do anything +extra. + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + + +/** + An extra init hook that enables the RuntimeDxe library instance to + register VirtualAddress change callbacks. Among other things. + + @retval EFI_SUCCESS Everything is good. Continue with init. + @retval Others Uh... don't continue. + +**/ +EFI_STATUS +VariablePolicyExtraInit ( + VOID + ) +{ + // NULL implementation. + return EFI_SUCCESS; +} + + +/** + An extra deinit hook that enables the RuntimeDxe library instance to + register VirtualAddress change callbacks. Among other things. + + @retval EFI_SUCCESS Everything is good. Continue with deinit. + @retval Others Uh... don't continue. + +**/ +EFI_STATUS +VariablePolicyExtraDeinit ( + VOID + ) +{ + // NULL implementation. + return EFI_SUCCESS; +} diff --git a/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitRuntimeDxe.c b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitRuntimeDxe.c new file mode 100644 index 0000000000..3ca87048b1 --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyExtraInitRuntimeDxe.c @@ -0,0 +1,85 @@ +/** @file -- VariablePolicyExtraInitRuntimeDxe.c +This file contains extra init and deinit routines that register and unregister +VariableAddressChange callbacks. + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +extern EFI_GET_VARIABLE mGetVariableHelper; +extern UINT8 *mPolicyTable; +STATIC BOOLEAN mIsVirtualAddrConverted; +STATIC EFI_EVENT mVariablePolicyLibVirtualAddressChangeEvent = NULL; + +/** + For the RuntimeDxe version of this lib, convert internal pointer addresses to virtual addresses. + + @param[in] Event Event whose notification function is being invoked. + @param[in] Context The pointer to the notification function's context, which + is implementation-dependent. +**/ +STATIC +VOID +EFIAPI +VariablePolicyLibVirtualAddressCallback ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + gRT->ConvertPointer (0, (VOID **)&mPolicyTable); + gRT->ConvertPointer (0, (VOID **)&mGetVariableHelper); + mIsVirtualAddrConverted = TRUE; +} + + +/** + An extra init hook that enables the RuntimeDxe library instance to + register VirtualAddress change callbacks. Among other things. + + @retval EFI_SUCCESS Everything is good. Continue with init. + @retval Others Uh... don't continue. + +**/ +EFI_STATUS +VariablePolicyExtraInit ( + VOID + ) +{ + return gBS->CreateEventEx (EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + VariablePolicyLibVirtualAddressCallback, + NULL, + &gEfiEventVirtualAddressChangeGuid, + &mVariablePolicyLibVirtualAddressChangeEvent); +} + + +/** + An extra deinit hook that enables the RuntimeDxe library instance to + register VirtualAddress change callbacks. Among other things. + + @retval EFI_SUCCESS Everything is good. Continue with deinit. + @retval Others Uh... don't continue. + +**/ +EFI_STATUS +VariablePolicyExtraDeinit ( + VOID + ) +{ + EFI_STATUS Status; + + Status = EFI_SUCCESS; + if (mIsVirtualAddrConverted) { + Status = gBS->CloseEvent (mVariablePolicyLibVirtualAddressChangeEvent); + } + else { + Status = EFI_SUCCESS; + } + + return Status; +} diff --git a/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.c b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.c new file mode 100644 index 0000000000..5029ddb96a --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.c @@ -0,0 +1,830 @@ +/** @file -- VariablePolicyLib.c +Business logic for Variable Policy enforcement. + +Copyright (c) Microsoft Corporation. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include +#include +#include +#include +#include + +#include +#include + + +// IMPORTANT NOTE: This library is currently rife with multiple return statements +// for error handling. A refactor should remove these at some point. + +// +// This library was designed with advanced unit-test features. +// This define handles the configuration. +#ifdef INTERNAL_UNIT_TEST +#undef STATIC +#define STATIC // Nothing... +#endif + +// An abstracted GetVariable interface that enables configuration regardless of the environment. +EFI_GET_VARIABLE mGetVariableHelper = NULL; + +// Master switch to lock this entire interface. Does not stop enforcement, +// just prevents the configuration from being changed for the rest of the boot. +STATIC BOOLEAN mInterfaceLocked = FALSE; + +// Master switch to disable the entire interface for a single boot. +// This will disable all policy enforcement for the duration of the boot. +STATIC BOOLEAN mProtectionDisabled = FALSE; + +// Table to hold all the current policies. +UINT8 *mPolicyTable = NULL; +STATIC UINT32 mCurrentTableSize = 0; +STATIC UINT32 mCurrentTableUsage = 0; +STATIC UINT32 mCurrentTableCount = 0; + +#define POLICY_TABLE_STEP_SIZE 0x1000 + +// NOTE: DO NOT USE THESE MACROS on any structure that has not been validated. +// Current table data has already been sanitized. +#define GET_NEXT_POLICY(CurPolicy) (VARIABLE_POLICY_ENTRY*)((UINT8*)CurPolicy + CurPolicy->Size) +#define GET_POLICY_NAME(CurPolicy) (CHAR16*)((UINTN)CurPolicy + CurPolicy->OffsetToName) + +#define MATCH_PRIORITY_EXACT 0 +#define MATCH_PRIORITY_MAX MATCH_PRIORITY_EXACT +#define MATCH_PRIORITY_MIN MAX_UINT8 + + +/** + An extra init hook that enables the RuntimeDxe library instance to + register VirtualAddress change callbacks. Among other things. + + @retval EFI_SUCCESS Everything is good. Continue with init. + @retval Others Uh... don't continue. + +**/ +EFI_STATUS +VariablePolicyExtraInit ( + VOID + ); + +/** + An extra deinit hook that enables the RuntimeDxe library instance to + register VirtualAddress change callbacks. Among other things. + + @retval EFI_SUCCESS Everything is good. Continue with deinit. + @retval Others Uh... don't continue. + +**/ +EFI_STATUS +VariablePolicyExtraDeinit ( + VOID + ); + + +/** + This helper function determines whether the structure of an incoming policy + is valid and internally consistent. + + @param[in] NewPolicy Pointer to the incoming policy structure. + + @retval TRUE + @retval FALSE Pointer is NULL, size is wrong, strings are empty, or + substructures overlap. + +**/ +STATIC +BOOLEAN +IsValidVariablePolicyStructure ( + IN CONST VARIABLE_POLICY_ENTRY *NewPolicy + ) +{ + EFI_STATUS Status; + UINTN EntryEnd; + CHAR16 *CheckChar; + UINTN WildcardCount; + + // Sanitize some quick values. + if (NewPolicy == NULL || NewPolicy->Size == 0 || + // Structure size should be at least as long as the minumum structure and a NULL string. + NewPolicy->Size < sizeof(VARIABLE_POLICY_ENTRY) || + // Check for the known revision. + NewPolicy->Version != VARIABLE_POLICY_ENTRY_REVISION) { + return FALSE; + } + + // Calculate the theoretical end of the structure and make sure + // that the structure can fit in memory. + Status = SafeUintnAdd( (UINTN)NewPolicy, NewPolicy->Size, &EntryEnd ); + if (EFI_ERROR( Status )) { + return FALSE; + } + + // Check for a valid Max Size. + if (NewPolicy->MaxSize == 0) { + return FALSE; + } + + // Check for the valid list of lock policies. + if (NewPolicy->LockPolicyType != VARIABLE_POLICY_TYPE_NO_LOCK && + NewPolicy->LockPolicyType != VARIABLE_POLICY_TYPE_LOCK_NOW && + NewPolicy->LockPolicyType != VARIABLE_POLICY_TYPE_LOCK_ON_CREATE && + NewPolicy->LockPolicyType != VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE) + { + return FALSE; + } + + // If the policy type is VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE, make sure that the matching state variable Name + // terminates before the OffsetToName for the matching policy variable Name. + if (NewPolicy->LockPolicyType == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE) { + // Adjust CheckChar to the offset of the LockPolicy->Name. + Status = SafeUintnAdd( (UINTN)NewPolicy + sizeof(VARIABLE_POLICY_ENTRY), + sizeof(VARIABLE_LOCK_ON_VAR_STATE_POLICY), + (UINTN*)&CheckChar ); + if (EFI_ERROR( Status ) || EntryEnd <= (UINTN)CheckChar) { + return FALSE; + } + while (*CheckChar != CHAR_NULL) { + if (EntryEnd <= (UINTN)CheckChar) { + return FALSE; + } + CheckChar++; + } + // At this point we should have either exeeded the structure or be pointing at the last char in LockPolicy->Name. + // We should check to make sure that the policy Name comes immediately after this charcter. + if ((UINTN)++CheckChar != (UINTN)NewPolicy + NewPolicy->OffsetToName) { + return FALSE; + } + // If the policy type is any other value, make sure that the LockPolicy structure has a zero length. + } else { + if (NewPolicy->OffsetToName != sizeof(VARIABLE_POLICY_ENTRY)) { + return FALSE; + } + } + + // Check to make sure that the name has a terminating character + // before the end of the structure. + // We've already checked that the name is within the bounds of the structure. + if (NewPolicy->Size != NewPolicy->OffsetToName) { + CheckChar = (CHAR16*)((UINTN)NewPolicy + NewPolicy->OffsetToName); + WildcardCount = 0; + while (*CheckChar != CHAR_NULL) { + // Make sure there aren't excessive wildcards. + if (*CheckChar == '#') { + WildcardCount++; + if (WildcardCount > MATCH_PRIORITY_MIN) { + return FALSE; + } + } + // Make sure you're still within the bounds of the policy structure. + if (EntryEnd <= (UINTN)CheckChar) { + return FALSE; + } + CheckChar++; + } + + // Finally, we should be pointed at the very last character in Name, so we should be right + // up against the end of the structure. + if ((UINTN)++CheckChar != EntryEnd) { + return FALSE; + } + } + + return TRUE; +} + + +/** + This helper function evaluates a policy and determines whether it matches the target + variable. If matched, will also return a value corresponding to the priority of the match. + + The rules for "best match" are listed in the Variable Policy Spec. + Perfect name matches will return 0. + Single wildcard characters will return the number of wildcard characters. + Full namespaces will return MAX_UINT8. + + @param[in] EvalEntry Pointer to the policy entry being evaluated. + @param[in] VariableName Same as EFI_SET_VARIABLE. + @param[in] VendorGuid Same as EFI_SET_VARIABLE. + @param[out] MatchPriority [Optional] On finding a match, this value contains the priority of the match. + Lower number == higher priority. Only valid if a match found. + + @retval TRUE Current entry matches the target variable. + @retval FALSE Current entry does not match at all. + +**/ +STATIC +BOOLEAN +EvaluatePolicyMatch ( + IN CONST VARIABLE_POLICY_ENTRY *EvalEntry, + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + OUT UINT8 *MatchPriority OPTIONAL + ) +{ + BOOLEAN Result; + CHAR16 *PolicyName; + UINT8 CalculatedPriority; + UINTN Index; + + Result = FALSE; + CalculatedPriority = MATCH_PRIORITY_EXACT; + + // Step 1: If the GUID doesn't match, we're done. No need to evaluate anything else. + if (!CompareGuid( &EvalEntry->Namespace, VendorGuid )) { + goto Exit; + } + + // If the GUID matches, check to see whether there is a Name associated + // with the policy. If not, this policy matches the entire namespace. + // Missing Name is indicated by size being equal to name. + if (EvalEntry->Size == EvalEntry->OffsetToName) { + CalculatedPriority = MATCH_PRIORITY_MIN; + Result = TRUE; + goto Exit; + } + + // Now that we know the name exists, get it. + PolicyName = GET_POLICY_NAME( EvalEntry ); + + // Evaluate the name against the policy name and check for a match. + // Account for any wildcards. + Index = 0; + Result = TRUE; + // Keep going until the end of both strings. + while (PolicyName[Index] != CHAR_NULL || VariableName[Index] != CHAR_NULL) { + // If we don't have a match... + if (PolicyName[Index] != VariableName[Index] || PolicyName[Index] == '#') { + // If this is a numerical wildcard, we can consider + // it a match if we alter the priority. + if (PolicyName[Index] == L'#' && + ((L'0' <= VariableName[Index] && VariableName[Index] <= L'9') || + (L'A' <= VariableName[Index] && VariableName[Index] <= L'F') || + (L'a' <= VariableName[Index] && VariableName[Index] <= L'f'))) { + if (CalculatedPriority < MATCH_PRIORITY_MIN) { + CalculatedPriority++; + } + // Otherwise, not a match. + } else { + Result = FALSE; + goto Exit; + } + } + Index++; + } + +Exit: + if (Result && MatchPriority != NULL) { + *MatchPriority = CalculatedPriority; + } + return Result; +} + + +/** + This helper function walks the current policy table and returns a pointer + to the best match, if any are found. Leverages EvaluatePolicyMatch() to + determine "best". + + @param[in] VariableName Same as EFI_SET_VARIABLE. + @param[in] VendorGuid Same as EFI_SET_VARIABLE. + @param[out] ReturnPriority [Optional] If pointer is provided, return the + priority of the match. Same as EvaluatePolicyMatch(). + Only valid if a match is returned. + + @retval VARIABLE_POLICY_ENTRY* Best match that was found. + @retval NULL No match was found. + +**/ +STATIC +VARIABLE_POLICY_ENTRY* +GetBestPolicyMatch ( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VendorGuid, + OUT UINT8 *ReturnPriority OPTIONAL + ) +{ + VARIABLE_POLICY_ENTRY *BestResult; + VARIABLE_POLICY_ENTRY *CurrentEntry; + UINT8 MatchPriority; + UINT8 CurrentPriority; + UINTN Index; + + BestResult = NULL; + MatchPriority = MATCH_PRIORITY_EXACT; + + // Walk all entries in the table, looking for matches. + CurrentEntry = (VARIABLE_POLICY_ENTRY*)mPolicyTable; + for (Index = 0; Index < mCurrentTableCount; Index++) { + // Check for a match. + if (EvaluatePolicyMatch( CurrentEntry, VariableName, VendorGuid, &CurrentPriority )) { + // If match is better, take it. + if (BestResult == NULL || CurrentPriority < MatchPriority) { + BestResult = CurrentEntry; + MatchPriority = CurrentPriority; + } + + // If you've hit the highest-priority match, can exit now. + if (MatchPriority == 0) { + break; + } + } + + // If we're still in the loop, move to the next entry. + CurrentEntry = GET_NEXT_POLICY( CurrentEntry ); + } + + // If a return priority was requested, return it. + if (ReturnPriority != NULL) { + *ReturnPriority = MatchPriority; + } + + return BestResult; +} + + +/** + This API function validates and registers a new policy with + the policy enforcement engine. + + @param[in] NewPolicy Pointer to the incoming policy structure. + + @retval EFI_SUCCESS + @retval EFI_INVALID_PARAMETER NewPolicy is NULL or is internally inconsistent. + @retval EFI_ALREADY_STARTED An identical matching policy already exists. + @retval EFI_WRITE_PROTECTED The interface has been locked until the next reboot. + @retval EFI_UNSUPPORTED Policy enforcement has been disabled. No reason to add more policies. + @retval EFI_ABORTED A calculation error has prevented this function from completing. + @retval EFI_OUT_OF_RESOURCES Cannot grow the table to hold any more policies. + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +RegisterVariablePolicy ( + IN CONST VARIABLE_POLICY_ENTRY *NewPolicy + ) +{ + EFI_STATUS Status; + VARIABLE_POLICY_ENTRY *MatchPolicy; + UINT8 MatchPriority; + UINT32 NewSize; + UINT8 *NewTable; + + if (!IsVariablePolicyLibInitialized()) { + return EFI_NOT_READY; + } + if (mInterfaceLocked) { + return EFI_WRITE_PROTECTED; + } + + if (!IsValidVariablePolicyStructure( NewPolicy )) { + return EFI_INVALID_PARAMETER; + } + + // Check to see whether an exact matching policy already exists. + MatchPolicy = GetBestPolicyMatch( GET_POLICY_NAME( NewPolicy ), + &NewPolicy->Namespace, + &MatchPriority ); + if (MatchPolicy != NULL && MatchPriority == MATCH_PRIORITY_EXACT) { + return EFI_ALREADY_STARTED; + } + + // If none exists, create it. + // If we need more space, allocate that now. + Status = SafeUint32Add( mCurrentTableUsage, NewPolicy->Size, &NewSize ); + if (EFI_ERROR( Status )) { + return EFI_ABORTED; + } + if (NewSize > mCurrentTableSize) { + // Use NewSize to calculate the new table size in units of POLICY_TABLE_STEP_SIZE. + NewSize = (NewSize % POLICY_TABLE_STEP_SIZE) > 0 ? + (NewSize / POLICY_TABLE_STEP_SIZE) + 1 : + (NewSize / POLICY_TABLE_STEP_SIZE); + // Calculate the new table size in absolute bytes. + Status = SafeUint32Mult( NewSize, POLICY_TABLE_STEP_SIZE, &NewSize ); + if (EFI_ERROR( Status )) { + return EFI_ABORTED; + } + + // Reallocate and copy the table. + NewTable = AllocatePool( NewSize ); + if (NewTable == NULL) { + return EFI_OUT_OF_RESOURCES; + } + CopyMem( NewTable, mPolicyTable, mCurrentTableUsage ); + mCurrentTableSize = NewSize; + if (mPolicyTable != NULL) { + FreePool( mPolicyTable ); + } + mPolicyTable = NewTable; + } + // Copy the policy into the table. + CopyMem( mPolicyTable + mCurrentTableUsage, NewPolicy, NewPolicy->Size ); + mCurrentTableUsage += NewPolicy->Size; + mCurrentTableCount += 1; + + // We're done here. + + return EFI_SUCCESS; +} + + +/** + This API function checks to see whether the parameters to SetVariable would + be allowed according to the current variable policies. + + @param[in] VariableName Same as EFI_SET_VARIABLE. + @param[in] VendorGuid Same as EFI_SET_VARIABLE. + @param[in] Attributes Same as EFI_SET_VARIABLE. + @param[in] DataSize Same as EFI_SET_VARIABLE. + @param[in] Data Same as EFI_SET_VARIABLE. + + @retval EFI_SUCCESS A matching policy allows this update. + @retval EFI_SUCCESS There are currently no policies that restrict this update. + @retval EFI_SUCCESS The protections have been disable until the next reboot. + @retval EFI_WRITE_PROTECTED Variable is currently locked. + @retval EFI_INVALID_PARAMETER Attributes or size are invalid. + @retval EFI_ABORTED A lock policy exists, but an error prevented evaluation. + @retval EFI_NOT_READY Library has not been initialized. + +**/ +EFI_STATUS +EFIAPI +ValidateSetVariable ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN UINT32 Attributes, + IN UINTN DataSize, + IN VOID *Data + ) +{ + BOOLEAN IsDel; + VARIABLE_POLICY_ENTRY *ActivePolicy; + EFI_STATUS Status; + EFI_STATUS ReturnStatus; + VARIABLE_LOCK_ON_VAR_STATE_POLICY *StateVarPolicy; + CHAR16 *StateVarName; + UINTN StateVarSize; + UINT8 StateVar; + + ReturnStatus = EFI_SUCCESS; + + if (!IsVariablePolicyLibInitialized()) { + ReturnStatus = EFI_NOT_READY; + goto Exit; + } + + // Bail if the protections are currently disabled. + if (mProtectionDisabled) { + ReturnStatus = EFI_SUCCESS; + goto Exit; + } + + // Determine whether this is a delete operation. + // If so, it will affect which tests are applied. + if ((DataSize == 0) && ((Attributes & EFI_VARIABLE_APPEND_WRITE) == 0)) { + IsDel = TRUE; + } else { + IsDel = FALSE; + } + + // Find an active policy if one exists. + ActivePolicy = GetBestPolicyMatch( VariableName, VendorGuid, NULL ); + + // If we have an active policy, check it against the incoming data. + if (ActivePolicy != NULL) { + // + // Only enforce size and attribute constraints when updating data, not deleting. + if (!IsDel) { + // Check for size constraints. + if ((ActivePolicy->MinSize > 0 && DataSize < ActivePolicy->MinSize) || + (ActivePolicy->MaxSize > 0 && DataSize > ActivePolicy->MaxSize)) { + ReturnStatus = EFI_INVALID_PARAMETER; + DEBUG(( DEBUG_VERBOSE, "%a - Bad Size. 0x%X <> 0x%X-0x%X\n", __FUNCTION__, + DataSize, ActivePolicy->MinSize, ActivePolicy->MaxSize )); + goto Exit; + } + + // Check for attribute constraints. + if ((ActivePolicy->AttributesMustHave & Attributes) != ActivePolicy->AttributesMustHave || + (ActivePolicy->AttributesCantHave & Attributes) != 0) { + ReturnStatus = EFI_INVALID_PARAMETER; + DEBUG(( DEBUG_VERBOSE, "%a - Bad Attributes. 0x%X <> 0x%X:0x%X\n", __FUNCTION__, + Attributes, ActivePolicy->AttributesMustHave, ActivePolicy->AttributesCantHave )); + goto Exit; + } + } + + // + // Lock policy check. + // + // Check for immediate lock. + if (ActivePolicy->LockPolicyType == VARIABLE_POLICY_TYPE_LOCK_NOW) { + ReturnStatus = EFI_WRITE_PROTECTED; + goto Exit; + // Check for lock on create. + } else if (ActivePolicy->LockPolicyType == VARIABLE_POLICY_TYPE_LOCK_ON_CREATE) { + StateVarSize = 0; + Status = mGetVariableHelper( VariableName, + VendorGuid, + NULL, + &StateVarSize, + NULL ); + if (Status == EFI_BUFFER_TOO_SMALL) { + ReturnStatus = EFI_WRITE_PROTECTED; + goto Exit; + } + // Check for lock on state variable. + } else if (ActivePolicy->LockPolicyType == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE) { + StateVarPolicy = (VARIABLE_LOCK_ON_VAR_STATE_POLICY*)((UINT8*)ActivePolicy + sizeof(VARIABLE_POLICY_ENTRY)); + StateVarName = (CHAR16*)((UINT8*)StateVarPolicy + sizeof(VARIABLE_LOCK_ON_VAR_STATE_POLICY)); + StateVarSize = sizeof(StateVar); + Status = mGetVariableHelper( StateVarName, + &StateVarPolicy->Namespace, + NULL, + &StateVarSize, + &StateVar ); + + // If the variable was found, check the state. If matched, this variable is locked. + if (!EFI_ERROR( Status )) { + if (StateVar == StateVarPolicy->Value) { + ReturnStatus = EFI_WRITE_PROTECTED; + goto Exit; + } + // EFI_NOT_FOUND and EFI_BUFFER_TOO_SMALL indicate that the state doesn't match. + } else if (Status != EFI_NOT_FOUND && Status != EFI_BUFFER_TOO_SMALL) { + // We don't know what happened, but it isn't good. + ReturnStatus = EFI_ABORTED; + goto Exit; + } + } + } + +Exit: + DEBUG(( DEBUG_VERBOSE, "%a - Variable (%g:%s) returning %r.\n", __FUNCTION__, VendorGuid, VariableName, ReturnStatus )); + return ReturnStatus; +} + + +/** + This API function disables the variable policy enforcement. If it's + already been called once, will return EFI_ALREADY_STARTED. + + @retval EFI_SUCCESS + @retval EFI_ALREADY_STARTED Has already been called once this boot. + @retval EFI_WRITE_PROTECTED Interface has been locked until reboot. + @retval EFI_WRITE_PROTECTED Interface option is disabled by platform PCD. + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +DisableVariablePolicy ( + VOID + ) +{ + if (!IsVariablePolicyLibInitialized()) { + return EFI_NOT_READY; + } + if (mProtectionDisabled) { + return EFI_ALREADY_STARTED; + } + if (mInterfaceLocked) { + return EFI_WRITE_PROTECTED; + } + if (!PcdGetBool (PcdAllowVariablePolicyEnforcementDisable)) { + return EFI_WRITE_PROTECTED; + } + mProtectionDisabled = TRUE; + return EFI_SUCCESS; +} + + +/** + This API function will dump the entire contents of the variable policy table. + + Similar to GetVariable, the first call can be made with a 0 size and it will return + the size of the buffer required to hold the entire table. + + @param[out] Policy Pointer to the policy buffer. Can be NULL if Size is 0. + @param[in,out] Size On input, the size of the output buffer. On output, the size + of the data returned. + + @retval EFI_SUCCESS Policy data is in the output buffer and Size has been updated. + @retval EFI_INVALID_PARAMETER Size is NULL, or Size is non-zero and Policy is NULL. + @retval EFI_BUFFER_TOO_SMALL Size is insufficient to hold policy. Size updated with required size. + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +DumpVariablePolicy ( + OUT UINT8 *Policy, + IN OUT UINT32 *Size + ) +{ + if (!IsVariablePolicyLibInitialized()) { + return EFI_NOT_READY; + } + + // Check the parameters. + if (Size == NULL || (*Size > 0 && Policy == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // Make sure the size is sufficient to hold the policy table. + if (*Size < mCurrentTableUsage) { + *Size = mCurrentTableUsage; + return EFI_BUFFER_TOO_SMALL; + } + + // If we're still here, copy the table and bounce. + CopyMem( Policy, mPolicyTable, mCurrentTableUsage ); + *Size = mCurrentTableUsage; + + return EFI_SUCCESS; +} + + +/** + This API function returns whether or not the policy engine is + currently being enforced. + + @retval TRUE + @retval FALSE + @retval FALSE Library has not yet been initialized. + +**/ +BOOLEAN +EFIAPI +IsVariablePolicyEnabled ( + VOID + ) +{ + if (!IsVariablePolicyLibInitialized()) { + return FALSE; + } + return !mProtectionDisabled; +} + + +/** + This API function locks the interface so that no more policy updates + can be performed or changes made to the enforcement until the next boot. + + @retval EFI_SUCCESS + @retval EFI_NOT_READY Library has not yet been initialized. + +**/ +EFI_STATUS +EFIAPI +LockVariablePolicy ( + VOID + ) +{ + if (!IsVariablePolicyLibInitialized()) { + return EFI_NOT_READY; + } + if (mInterfaceLocked) { + return EFI_WRITE_PROTECTED; + } + mInterfaceLocked = TRUE; + return EFI_SUCCESS; +} + + +/** + This API function returns whether or not the policy interface is locked + for the remainder of the boot. + + @retval TRUE + @retval FALSE + @retval FALSE Library has not yet been initialized. + +**/ +BOOLEAN +EFIAPI +IsVariablePolicyInterfaceLocked ( + VOID + ) +{ + if (!IsVariablePolicyLibInitialized()) { + return FALSE; + } + return mInterfaceLocked; +} + + +/** + This helper function initializes the library and sets + up any required internal structures or handlers. + + Also registers the internal pointer for the GetVariable helper. + + @param[in] GetVariableHelper A function pointer matching the EFI_GET_VARIABLE prototype that will be used to + check policy criteria that involve the existence of other variables. + + @retval EFI_SUCCESS + @retval EFI_ALREADY_STARTED The initialize function has been called more than once without a call to + deinitialize. + +**/ +EFI_STATUS +EFIAPI +InitVariablePolicyLib ( + IN EFI_GET_VARIABLE GetVariableHelper + ) +{ + EFI_STATUS Status; + + Status = EFI_SUCCESS; + + if (mGetVariableHelper != NULL) { + return EFI_ALREADY_STARTED; + } + + if (!EFI_ERROR( Status )) { + Status = VariablePolicyExtraInit(); + } + + if (!EFI_ERROR( Status )) { + // Save an internal pointer to the GetVariableHelper. + mGetVariableHelper = GetVariableHelper; + + // Initialize the global state. + mInterfaceLocked = FALSE; + mProtectionDisabled = FALSE; + mPolicyTable = NULL; + mCurrentTableSize = 0; + mCurrentTableUsage = 0; + mCurrentTableCount = 0; + } + + return Status; +} + + +/** + This helper function returns whether or not the library is currently initialized. + + @retval TRUE + @retval FALSE + +**/ +BOOLEAN +EFIAPI +IsVariablePolicyLibInitialized ( + VOID + ) +{ + return (mGetVariableHelper != NULL); +} + + +/** + This helper function tears down the library. + + Should generally only be used for test harnesses. + + @retval EFI_SUCCESS + @retval EFI_NOT_READY Deinitialize was called without first calling initialize. + +**/ +EFI_STATUS +EFIAPI +DeinitVariablePolicyLib ( + VOID + ) +{ + EFI_STATUS Status; + + Status = EFI_SUCCESS; + + if (mGetVariableHelper == NULL) { + return EFI_NOT_READY; + } + + if (!EFI_ERROR( Status )) { + Status = VariablePolicyExtraDeinit(); + } + + if (!EFI_ERROR( Status )) { + mGetVariableHelper = NULL; + mInterfaceLocked = FALSE; + mProtectionDisabled = FALSE; + mCurrentTableSize = 0; + mCurrentTableUsage = 0; + mCurrentTableCount = 0; + + if (mPolicyTable != NULL) { + FreePool( mPolicyTable ); + mPolicyTable = NULL; + } + } + + return Status; +} diff --git a/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.inf b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.inf new file mode 100644 index 0000000000..3fe6043bf6 --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.inf @@ -0,0 +1,48 @@ +## @file VariablePolicyLib.inf +# Business logic for Variable Policy enforcement. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + + +[Defines] + INF_VERSION = 0x00010017 + BASE_NAME = VariablePolicyLib + FILE_GUID = E9ECD342-159A-4F24-9FDF-65724027C594 + VERSION_STRING = 1.0 + MODULE_TYPE = DXE_DRIVER + LIBRARY_CLASS = VariablePolicyLib|DXE_DRIVER DXE_SMM_DRIVER MM_STANDALONE + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = ANY +# + + +[Sources] + VariablePolicyLib.c + VariablePolicyExtraInitNull.c + + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + + +[LibraryClasses] + DebugLib + BaseMemoryLib + MemoryAllocationLib + SafeIntLib + PcdLib + + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdAllowVariablePolicyEnforcementDisable ## CONSUMES + + +[BuildOptions] + MSFT:NOOPT_*_*_CC_FLAGS = -DINTERNAL_UNIT_TEST + GCC:NOOPT_*_*_CC_FLAGS = -DINTERNAL_UNIT_TEST diff --git a/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.uni b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.uni new file mode 100644 index 0000000000..2227ec4278 --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.uni @@ -0,0 +1,12 @@ +// /** @file +// VariablePolicyLib.uni +// +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: BSD-2-Clause-Patent +// +// **/ + + +#string STR_MODULE_ABSTRACT #language en-US "Library containing the business logic for the VariablePolicy engine" + +#string STR_MODULE_DESCRIPTION #language en-US "Library containing the business logic for the VariablePolicy engine" diff --git a/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLibRuntimeDxe.inf b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLibRuntimeDxe.inf new file mode 100644 index 0000000000..8b83657418 --- /dev/null +++ b/MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLibRuntimeDxe.inf @@ -0,0 +1,51 @@ +## @file VariablePolicyLibRuntimeDxe.inf +# Business logic for Variable Policy enforcement. +# This instance is specifically for RuntimeDxe and contains +# extra routines to register for VirtualAddressChangeEvents. +# +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + + +[Defines] + INF_VERSION = 0x00010017 + BASE_NAME = VariablePolicyLibRuntimeDxe + FILE_GUID = 205F7F0E-8EAC-4914-8390-1B90DD7E2A27 + VERSION_STRING = 1.0 + MODULE_TYPE = DXE_RUNTIME_DRIVER + LIBRARY_CLASS = VariablePolicyLib|DXE_RUNTIME_DRIVER + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = ANY +# + + +[Sources] + VariablePolicyLib.c + VariablePolicyExtraInitRuntimeDxe.c + + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + + +[LibraryClasses] + DebugLib + BaseMemoryLib + MemoryAllocationLib + SafeIntLib + UefiBootServicesTableLib + UefiRuntimeServicesTableLib + PcdLib + + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdAllowVariablePolicyEnforcementDisable ## CONSUMES + + +[Guids] + gEfiEventVirtualAddressChangeGuid diff --git a/MdeModulePkg/MdeModulePkg.ci.yaml b/MdeModulePkg/MdeModulePkg.ci.yaml index 1a7e955185..20d53fc5a5 100644 --- a/MdeModulePkg/MdeModulePkg.ci.yaml +++ b/MdeModulePkg/MdeModulePkg.ci.yaml @@ -104,7 +104,9 @@ "FVMAIN", "VARCHECKPCD", "Getxx", - "lzturbo" + "lzturbo", + "musthave", + "canthave" ], "AdditionalIncludePaths": [] # Additional paths to spell check relative to package root (wildcards supported) } diff --git a/MdeModulePkg/MdeModulePkg.dec b/MdeModulePkg/MdeModulePkg.dec index 82aecc40d9..51c7057bfd 100644 --- a/MdeModulePkg/MdeModulePkg.dec +++ b/MdeModulePkg/MdeModulePkg.dec @@ -31,6 +31,9 @@ ## @libraryclass Defines a set of methods to reset whole system. ResetSystemLib|Include/Library/ResetSystemLib.h + ## @libraryclass Business logic for storing and testing variable policies + VariablePolicyLib|Include/Library/VariablePolicyLib.h + ## @libraryclass Defines a set of helper functions for resetting the system. ResetUtilityLib|Include/Library/ResetUtilityLib.h diff --git a/MdeModulePkg/MdeModulePkg.dsc b/MdeModulePkg/MdeModulePkg.dsc index fbbc9933f5..3c8bf8009c 100644 --- a/MdeModulePkg/MdeModulePkg.dsc +++ b/MdeModulePkg/MdeModulePkg.dsc @@ -3,6 +3,7 @@ # # (C) Copyright 2014 Hewlett-Packard Development Company, L.P.
# Copyright (c) 2007 - 2019, Intel Corporation. All rights reserved.
+# Copyright (c) Microsoft Corporation. # # SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -58,6 +59,7 @@ DxeServicesLib|MdePkg/Library/DxeServicesLib/DxeServicesLib.inf DxeServicesTableLib|MdePkg/Library/DxeServicesTableLib/DxeServicesTableLib.inf UefiBootManagerLib|MdeModulePkg/Library/UefiBootManagerLib/UefiBootManagerLib.inf + VariablePolicyLib|MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.inf # # Generic Modules # @@ -129,6 +131,7 @@ DebugLib|MdePkg/Library/UefiDebugLibConOut/UefiDebugLibConOut.inf LockBoxLib|MdeModulePkg/Library/SmmLockBoxLib/SmmLockBoxDxeLib.inf CapsuleLib|MdeModulePkg/Library/DxeCapsuleLibFmp/DxeRuntimeCapsuleLib.inf + VariablePolicyLib|MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLibRuntimeDxe.inf [LibraryClasses.common.SMM_CORE] HobLib|MdePkg/Library/DxeHobLib/DxeHobLib.inf @@ -306,6 +309,8 @@ MdeModulePkg/Library/BootLogoLib/BootLogoLib.inf MdeModulePkg/Library/TpmMeasurementLibNull/TpmMeasurementLibNull.inf MdeModulePkg/Library/AuthVariableLibNull/AuthVariableLibNull.inf + MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLib.inf + MdeModulePkg/Library/VariablePolicyLib/VariablePolicyLibRuntimeDxe.inf MdeModulePkg/Library/VarCheckLib/VarCheckLib.inf MdeModulePkg/Library/VarCheckHiiLib/VarCheckHiiLib.inf MdeModulePkg/Library/VarCheckPcdLib/VarCheckPcdLib.inf -- 2.39.2