From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mga11.intel.com (mga11.intel.com [192.55.52.93]) by mx.groups.io with SMTP id smtpd.web09.1000.1648250924288850149 for ; Fri, 25 Mar 2022 16:28:44 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="unable to parse pub key" header.i=@intel.com header.s=intel header.b=fl1gsYJo; spf=pass (domain: intel.com, ip: 192.55.52.93, mailfrom: judah.vang@intel.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1648250924; x=1679786924; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=O37JvRHkKtMXGYKN3TvcFq9dDaM/btureiufJm7e8AQ=; b=fl1gsYJozIJnCIF2uYVb43behh+hsyzRi4DRD0Yx1tZw97lbLPA8lfxl t1a4z83dCOkBBlfNE1Xv5qz30VgeM6lcYk4Rjw+hzfu0fKeVe+dM5jQmS EbAm+BEW/OHj/MTACNYmNIGYrP1bwhg3oXggfkLUm6MzGYicIBPvwvd6Y 5z9EZpexf2LaywN7LOa0G5qjtnPFHp7tF/Pp1lQeBEQTeCM6yFcwSWqYv Skdai+c6R4XHsAIVuQe5vLbV5rQspxTjazniIjxbsoZ/5BYcN7oCA/Ok6 6yhd+3qx/N7DeSkiTL1RHtcS3GyeNY79v/hvy0tbiueJIYqJVq+i2LvQS A==; X-IronPort-AV: E=McAfee;i="6200,9189,10297"; a="256309527" X-IronPort-AV: E=Sophos;i="5.90,211,1643702400"; d="scan'208";a="256309527" Received: from fmsmga003.fm.intel.com ([10.253.24.29]) by fmsmga102.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 25 Mar 2022 16:28:43 -0700 X-IronPort-AV: E=Sophos;i="5.90,211,1643702400"; d="scan'208";a="638366582" Received: from jvang-mobl.amr.corp.intel.com ([10.212.95.18]) by fmsmga003-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 25 Mar 2022 16:28:43 -0700 From: "Judah Vang" To: devel@edk2.groups.io Cc: Judah Vang , Jian J Wang , Jiewen Yao , Nishant C Mistry Subject: [PATCH v1 17/28] SecurityPkg: Add Protected Variable Services Date: Fri, 25 Mar 2022 16:28:14 -0700 Message-Id: <20220325232825.2167-13-judah.vang@intel.com> X-Mailer: git-send-email 2.35.1.windows.2 In-Reply-To: <20220325232825.2167-1-judah.vang@intel.com> References: <20220325232825.2167-1-judah.vang@intel.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit REF: https://bugzilla.tianocore.org/show_bug.cgi?id=2594 Add Protected Variable Services across the different UEFI phases. Functions includes creating variable digest, performing integrity check, initializing protected variables, updating protected variables, and verifying the MetaDataHmacVar variable. Cc: Jian J Wang Cc: Jiewen Yao Cc: Nishant C Mistry Signed-off-by: Judah Vang --- SecurityPkg/Library/ProtectedVariableLib/DxeProtectedVariableLib.inf | 64 + SecurityPkg/Library/ProtectedVariableLib/PeiProtectedVariableLib.inf | 68 + SecurityPkg/Library/ProtectedVariableLib/SmmProtectedVariableLib.inf | 67 + SecurityPkg/Library/ProtectedVariableLib/SmmRuntimeProtectedVariableLib.inf | 62 + SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableInternal.h | 611 ++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableCommon.c | 2095 ++++++++++++++++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableDxe.c | 163 ++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariablePei.c | 1331 +++++++++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmm.c | 209 ++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmDxeCommon.c | 975 +++++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmRuntime.c | 233 +++ 11 files changed, 5878 insertions(+) diff --git a/SecurityPkg/Library/ProtectedVariableLib/DxeProtectedVariableLib.inf b/SecurityPkg/Library/ProtectedVariableLib/DxeProtectedVariableLib.inf new file mode 100644 index 000000000000..74a0285af7ef --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/DxeProtectedVariableLib.inf @@ -0,0 +1,64 @@ +## @file +# Provides protected variable services for EmulatorPkg. +# +# Copyright (c) 2022, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010029 + BASE_NAME = DxeProtectedVariableLib + MODULE_UNI_FILE = ProtectedVariableLib.uni + FILE_GUID = 6F424E10-0F75-4716-9F97-58C2E1C643AD + MODULE_TYPE = DXE_RUNTIME_DRIVER + VERSION_STRING = 0.1 + LIBRARY_CLASS = ProtectedVariableLib|DXE_RUNTIME_DRIVER + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + ProtectedVariableDxe.c + ProtectedVariableCommon.c + ProtectedVariableSmmDxeCommon.c + ProtectedVariableInternal.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + SecurityPkg/SecurityPkg.dec + CryptoPkg/CryptoPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + HobLib + BaseCryptLib + EncryptionVariableLib + RpmcLib + HashApiLib + SortLib + +[Protocols] + gEfiVariableWriteArchProtocolGuid + +[Guids] + gEdkiiMetaDataHmacVariableGuid + gEdkiiProtectedVariableGlobalGuid + gEdkiiVarErrorFlagGuid + gEdkiiProtectedVariableContextGuid + +[Pcd] + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableName + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableGuid + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableIntegrity + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableConfidentiality + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxAuthVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxHardwareErrorVariableSize diff --git a/SecurityPkg/Library/ProtectedVariableLib/PeiProtectedVariableLib.inf b/SecurityPkg/Library/ProtectedVariableLib/PeiProtectedVariableLib.inf new file mode 100644 index 000000000000..44c959a94ca3 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/PeiProtectedVariableLib.inf @@ -0,0 +1,68 @@ +## @file +# Provides protected variable services. +# +# Copyright (c) 2022, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010029 + BASE_NAME = PeiProtectedVariableLib + MODULE_UNI_FILE = ProtectedVariableLib.uni + FILE_GUID = 76FBFBCE-ACBB-4084-A348-8FCC97AAEB9D + MODULE_TYPE = PEIM + VERSION_STRING = 0.1 + LIBRARY_CLASS = ProtectedVariableLib|PEIM + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 AARCH64 +# + +[Sources] + ProtectedVariablePei.c + ProtectedVariableCommon.c + ProtectedVariableInternal.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + SecurityPkg/SecurityPkg.dec + CryptoPkg/CryptoPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + HobLib + BaseCryptLib + RpmcLib + VariableKeyLib + EncryptionVariableLib + ReportStatusCodeLib + PeiServicesLib + HashApiLib + SortLib + +[Guids] + gEdkiiMetaDataHmacVariableGuid + gEdkiiProtectedVariableGlobalGuid + gEdkiiVarErrorFlagGuid + gEdkiiProtectedVariableContextGuid + +[Ppis] + gEfiPeiMemoryDiscoveredPpiGuid + gEfiPeiVariableStoreDiscoveredPpiGuid + +[Pcd] + gEfiSecurityPkgTokenSpaceGuid.PcdStatusCodeVariableIntegrity + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableName + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableGuid + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableIntegrity + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableConfidentiality + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxAuthVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxHardwareErrorVariableSize diff --git a/SecurityPkg/Library/ProtectedVariableLib/SmmProtectedVariableLib.inf b/SecurityPkg/Library/ProtectedVariableLib/SmmProtectedVariableLib.inf new file mode 100644 index 000000000000..ecf0b1a43d30 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/SmmProtectedVariableLib.inf @@ -0,0 +1,67 @@ +## @file +# Provides protected variable services. +# +# Copyright (c) 2022, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010029 + BASE_NAME = SmmProtectedVariableLib + MODULE_UNI_FILE = ProtectedVariableLib.uni + FILE_GUID = 2BEE71E5-259B-4057-A2C1-2115DF43C76A + MODULE_TYPE = DXE_SMM_DRIVER + VERSION_STRING = 0.1 + LIBRARY_CLASS = ProtectedVariableLib|DXE_SMM_DRIVER MM_STANDALONE + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + ProtectedVariableSmm.c + ProtectedVariableCommon.c + ProtectedVariableSmmDxeCommon.c + ProtectedVariableInternal.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + SecurityPkg/SecurityPkg.dec + CryptoPkg/CryptoPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + HobLib + BaseCryptLib + EncryptionVariableLib + RpmcLib + VariableKeyLib + HashApiLib + SortLib + +[Protocols] + gEfiMmEndOfDxeProtocolGuid + +[Guids] + gSmmVariableWriteGuid + gEdkiiMetaDataHmacVariableGuid + gEdkiiProtectedVariableGlobalGuid + gEdkiiVarErrorFlagGuid + gEdkiiProtectedVariableContextGuid + +[Pcd] + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableName + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableGuid + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableIntegrity + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableConfidentiality + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxAuthVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxHardwareErrorVariableSize + diff --git a/SecurityPkg/Library/ProtectedVariableLib/SmmRuntimeProtectedVariableLib.inf b/SecurityPkg/Library/ProtectedVariableLib/SmmRuntimeProtectedVariableLib.inf new file mode 100644 index 000000000000..011ccdce2db8 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/SmmRuntimeProtectedVariableLib.inf @@ -0,0 +1,62 @@ +## @file +# Provides protected variable services. +# +# Copyright (c) 2022, Intel Corporation. All rights reserved.
+# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x00010029 + BASE_NAME = SmmRuntimeProtectedVariableLib + MODULE_UNI_FILE = ProtectedVariableLib.uni + FILE_GUID = 99A623DE-1AD3-4AB3-909D-E3AADD7845EF + MODULE_TYPE = DXE_RUNTIME_DRIVER + VERSION_STRING = 0.1 + LIBRARY_CLASS = ProtectedVariableLib|DXE_DRIVER DXE_RUNTIME_DRIVER + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 +# + +[Sources] + ProtectedVariableSmmRuntime.c + ProtectedVariableCommon.c + ProtectedVariableInternal.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + SecurityPkg/SecurityPkg.dec + CryptoPkg/CryptoPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + HobLib + BaseCryptLib + EncryptionVariableLib + RpmcLib + HashApiLib + SortLib + +[Guids] + gEfiEventVirtualAddressChangeGuid + gEdkiiMetaDataHmacVariableGuid + gEdkiiProtectedVariableGlobalGuid + gEdkiiVarErrorFlagGuid + gEdkiiProtectedVariableContextGuid + +[Pcd] + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableName + gEfiSecurityPkgTokenSpaceGuid.PcdPlatformVariableGuid + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableIntegrity + gEfiSecurityPkgTokenSpaceGuid.PcdProtectedVariableConfidentiality + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxAuthVariableSize + gEfiMdeModulePkgTokenSpaceGuid.PcdMaxHardwareErrorVariableSize + diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableInternal.h b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableInternal.h new file mode 100644 index 000000000000..fd38b5bd2019 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableInternal.h @@ -0,0 +1,611 @@ +/** @file + Definitions shared among different implementation of ProtectedVariableLib. + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef PROTECTED_VARIABLE_INTERNAL_H_ +#define PROTECTED_VARIABLE_INTERNAL_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define VARIABLE_KEY_SIZE (256/8) + +#define METADATA_HMAC_SIZE (256/8) +#define METADATA_HMAC_KEY_NAME L"HMAC_KEY" +#define METADATA_HMAC_KEY_NAME_SIZE 0x10 + +#define METADATA_HMAC_SEP L":" +#define METADATA_HMAC_SEP_SIZE 2 + +#define METADATA_HMAC_VARIABLE_NAME L"MetaDataHmacVar" +#define METADATA_HMAC_VARIABLE_NAME_SIZE sizeof (METADATA_HMAC_VARIABLE_NAME) +#define METADATA_HMAC_VARIABLE_GUID gEdkiiMetaDataHmacVariableGuid +#define METADATA_HMAC_VARIABLE_ATTR VARIABLE_ATTRIBUTE_NV_BS_RT + +#define DIGEST_CONTEXT_SIZE (HashApiGetContextSize()) + +#define MAX_VARIABLE_SIZE \ + MAX (MAX (PcdGet32 (PcdMaxVariableSize), PcdGet32 (PcdMaxAuthVariableSize)), \ + PcdGet32 (PcdMaxHardwareErrorVariableSize)) + +#define IS_VARIABLE(Var, Name, Guid) \ + (StrCmp ((Var)->VariableName, (Name)) == 0 \ + && CompareGuid ((CONST EFI_GUID *)(Var)->VendorGuid, (CONST EFI_GUID *)(Guid))) + +#define VARIABLE_SIZE(VarInfo) \ + (((UINTN)(VarInfo)->Header.Data - (UINTN)(VarInfo)->Buffer) \ + + (VarInfo)->Header.DataSize \ + + GET_PAD_SIZE ((VarInfo)->Header.DataSize)) + +#define VARIABLE_HEADER_SIZE(AuthFlag) \ + ((AuthFlag) ? sizeof (AUTHENTICATED_VARIABLE_HEADER) \ + : sizeof (VARIABLE_HEADER)) + +#define VARIABLE_NAME(Var, AuthFlag) \ + ((CHAR16 *)((UINTN)(Var) + VARIABLE_HEADER_SIZE(AuthFlag))) + +#define VARIABLE_START(VarStore) \ + ((VARIABLE_HEADER *)HEADER_ALIGN ((VARIABLE_STORE_HEADER *)(VarStore) + 1)) + +#define VARIABLE_END(VarStore) \ + ((VARIABLE_HEADER *)HEADER_ALIGN ((UINTN)(VarStore) \ + + ((VARIABLE_STORE_HEADER *)(VarStore))->Size)) + +#define SET_VARIABLE_DATA_SIZE(VarInfo, Size) \ + if ((VarInfo)->Flags.Auth) { \ + ((AUTHENTICATED_VARIABLE_HEADER *)((VarInfo)->Buffer))->DataSize = Size; \ + (VarInfo)->Header.DataSize = Size; \ + } else { \ + ((VARIABLE_HEADER *)((VarInfo)->Buffer))->DataSize = Size; \ + (VarInfo)->Header.DataSize = Size; \ + } + +#define IS_KNOWN_UNPROTECTED_VARIABLE(Global, VarInfo) \ + (CheckKnownUnprotectedVariable ((Global), (VarInfo)) < UnprotectedVarIndexMax) + +#define GET_CNTX(Global) ((PROTECTED_VARIABLE_CONTEXT_IN *)(UINTN)((Global)->ContextIn)) +#define GET_BUFR(Address) ((VOID *)(UINTN)(Address)) +#define GET_ADRS(Buffer) ((EFI_PHYSICAL_ADDRESS)(UINTN)(Buffer)) + +typedef struct _VARIABLE_IDENTIFIER { + CHAR16 *VariableName; + EFI_GUID *VendorGuid; + UINT8 State; +} VARIABLE_IDENTIFIER; + +typedef enum { + IndexHmacInDel = 0, /// MetaDataHmacVar with state VAR_IN_DELETED_TRANSITION + IndexHmacAdded, /// MetaDataHmacVar with state VAR_ADDED + IndexErrorFlag, /// VarErrorFlag + IndexPlatformVar, /// Platform Variable + UnprotectedVarIndexMax +} UNPROTECTED_VARIABLE_INDEX; + +#pragma pack(1) + +#define PROTECTED_VARIABLE_CONTEXT_OUT_STRUCT_VERSION 0x02 + +typedef struct _PROTECTED_VARIABLE_FLAG { + BOOLEAN Auth; // Authenticated variable format + BOOLEAN WriteInit; // Write-init-done + BOOLEAN WriteReady; // Ready-to-write + BOOLEAN RecoveryMode; // Variable storage recovery or provisioning + BOOLEAN CacheReady; // Indicates Cache is available + BOOLEAN Reserved; // reserved +} PROTECTED_VARIABLE_FLAG; + +typedef struct _PROTECTED_VARIABLE_GLOBAL { + UINT32 StructVersion; + UINT32 StructSize; + + /// + /// Variable root key used to derive Encryption key and HMAC key. + /// + UINT8 RootKey[VARIABLE_KEY_SIZE]; + /// + /// HMAC key derived from RootKey. + /// + UINT8 MetaDataHmacKey[VARIABLE_KEY_SIZE]; + /// + /// Number of variables in linked list pointed by VariableDigests. + /// + UINT32 VariableNumber; + /// + /// Size of memory reserved by VariableCache. + /// + UINT32 VariableCacheSize; + /// + /// Memory reserved to temporarily hold data of one variable, for integrity + /// validation purpose. + /// + EFI_PHYSICAL_ADDRESS VariableCache; + /// + /// Pointer to linked list, in which each node holds the digest value of each + /// variable. + /// + EFI_PHYSICAL_ADDRESS VariableDigests; + /// + /// Memory reserved for Context used in hash API to avoid repeat alloc/free. + /// + EFI_PHYSICAL_ADDRESS DigestContext; + /// + /// Pointer to one of node in linked list pointed by VariableDigests, which + /// has been just accessed. This is mainly used to facilitate the two calls + /// use case of GetVariable(). + /// + EFI_PHYSICAL_ADDRESS LastAccessedVariable; + /// + /// Cached copy of pointers to nodes of unprotected variables in the linked + /// list pointed by VariableDigests. + /// + EFI_PHYSICAL_ADDRESS Unprotected[UnprotectedVarIndexMax]; + /// + /// Pointer to data structure holding helper functions passed by user of + /// ProtectedVariableLib, most of which are used to complete operations on + /// variable storage. + /// + EFI_PHYSICAL_ADDRESS ContextIn; + + /// + /// Pointer to Global data structure. This is to hold pre-mem address value. + /// Later to be used to identify pre-mem to post-mem transition. + /// + EFI_PHYSICAL_ADDRESS GlobalSelf; + + PROTECTED_VARIABLE_FLAG Flags; +} PROTECTED_VARIABLE_GLOBAL; + +#pragma pack() + +/* Sort method function pointer taking two parameters */ +typedef +INTN +(*SORT_METHOD) ( + IN VARIABLE_DIGEST *Variable1, + IN VARIABLE_DIGEST *Variable2 + ); + +/* Update variable digest data function pointer */ +typedef +BOOLEAN +(*DIGEST_UPDATE) ( + IN OUT VOID *Context, + IN VOID *Data, + IN UINTN DataSize + ); + +/** + + Print variable information + + @param[in] Data8 Pointer to data + @param[out] DataSize Size of data + +**/ +VOID +PrintVariableData ( + IN UINT8 *Data8, + IN UINTN DataSize + ); + +/** + + Derive HMAC key from given variable root key. + + @param[in] RootKey Pointer to root key to derive from. + @param[in] RootKeySize Size of root key. + @param[out] HmacKey Pointer to generated HMAC key. + @param[in] HmacKeySize Size of HMAC key. + + @retval TRUE The HMAC key is derived successfully. + @retval FALSE Failed to generate HMAC key from given root key. + +**/ +BOOLEAN +EFIAPI +GenerateMetaDataHmacKey ( + IN CONST UINT8 *RootKey, + IN UINTN RootKeySize, + OUT UINT8 *HmacKey, + IN UINTN HmacKeySize + ); + +/** + + Digests the given variable data and updates HMAC context. + + @param[in,out] Context Pointer to initialized HMAC context. + @param[in] VarInfo Pointer to variable data. + + @retval TRUE HMAC context was updated successfully. + @retval FALSE Failed to update HMAC context. + +**/ +BOOLEAN +UpdateVariableMetadataHmac ( + IN VOID *Context, + IN PROTECTED_VARIABLE_INFO *VarInfo + ); + +/** + + Re-calculate HMAC based on new variable data and re-generate MetaDataHmacVar. + + @param[in] Global Pointer to global configuration data. + @param[in] NewVarInfo Pointer to buffer of new variable data. + @param[in,out] NewHmacVarInfo Pointer to buffer of new MetaDataHmacVar. + + @return EFI_SUCCESS The HMAC value was updated successfully. + @return EFI_ABORTED Failed to calculate the HMAC value. + @return EFI_OUT_OF_RESOURCES Not enough resource to calculate HMC value. + @return EFI_NOT_FOUND The MetaDataHmacVar was not found in storage. + +**/ +EFI_STATUS +RefreshVariableMetadataHmac ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *NewVarInfo, + IN OUT PROTECTED_VARIABLE_INFO *NewHmacVarInfo + ); + +/** + + Retrieve the context and global configuration data structure from HOB. + + Once protected NV variable storage is cached and verified in PEI phase, + all related information are stored in a HOB which can be used by PEI variable + service itself and passed to SMM along with the boot flow, which can avoid + many duplicate works, like generating HMAC key, verifying NV variable storage, + etc. + + The HOB can be identified by gEdkiiProtectedVariableGlobalGuid. + + @param[out] Global Pointer to global configuration data from PEI phase. + + @retval EFI_SUCCESS The HOB was found, and Context and Global are retrieved. + @retval EFI_NOT_FOUND The HOB was not found. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableGlobalFromHob ( + OUT PROTECTED_VARIABLE_GLOBAL **Global OPTIONAL + ); + +/** + + Get context and/or global data structure used to process protected variable. + + @param[out] Global Pointer to global configuration data. + + @retval EFI_SUCCESS Get requested structure successfully. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableGlobal ( + OUT PROTECTED_VARIABLE_GLOBAL **Global OPTIONAL + ); + +/** + + Get context data structure used to process protected variable. + + @param[out] ContextIn Pointer to context provided by variable runtime services. + + @retval EFI_SUCCESS Get requested structure successfully. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableContext ( + PROTECTED_VARIABLE_CONTEXT_IN **ContextIn OPTIONAL + ); + +/** + + Check if a given variable is unprotected variable specified in advance + and return its index ID. + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to variable information data. + + @retval IndexHmacInDel Variable is MetaDataHmacVar in delete-transition state. + @retval IndexHmacAdded Variable is MetaDataHmacVar in valid state. + @retval IndexErrorFlag Variable is VarErrorLog. + @retval Others Variable is not any known unprotected variables. + +**/ +UNPROTECTED_VARIABLE_INDEX +CheckKnownUnprotectedVariable ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo + ); + +/** + + Return the size of variable MetaDataHmacVar. + + @param[in] AuthFlag Auth-variable indicator. + + @retval size of variable MetaDataHmacVar. + +**/ +UINTN +GetMetaDataHmacVarSize ( + IN BOOLEAN AuthFlag + ); + +/** + + Fix state of MetaDataHmacVar on NV variable storage, if there's failure at + last boot during updating variable. + + This must be done before the first writing of variable in current boot, + including storage reclaim. + + @retval EFI_UNSUPPORTED Updating NV variable storage is not supported. + @retval EFI_OUT_OF_RESOURCES Not enough resource to complete the operation. + @retval EFI_SUCCESS Variable store was successfully updated. + +**/ +EFI_STATUS +FixupHmacVariable ( + VOID + ); + +/** + + Verify the variable digest. + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to verified copy of protected variables. + @param[in] VarDig Pointer to variable digest data. + + @retval EFI_INVALID_PARAMETER Invalid parameter was passed in. + @retval EFI_OUT_OF_RESOURCES Not enough resource to calculate hash. + @retval EFI_ABORTED An error was encountered. + @retval EFI_COMPROMISED_DATA The data was compromised. + @retval EFI_SUCCESS Variable digest was successfully verified. + +**/ +EFI_STATUS +VerifyVariableDigest ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN VARIABLE_DIGEST *VarDig + ); + +/** + + Get the variable digest. + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to verified copy of protected variables. + @param[in,out] DigestValue Pointer to variable digest value. + + @retval EFI_INVALID_PARAMETER Invalid parameter was passed in. + @retval EFI_OUT_OF_RESOURCES Not enough resource to calculate hash. + @retval EFI_ABORTED An error was encountered. + @retval EFI_COMPROMISED_DATA The data was compromised. + @retval EFI_SUCCESS Variable digest was successfully verified. + +**/ +EFI_STATUS +GetVariableDigest ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN OUT UINT8 *DigestValue + ); + +/** + + Compare variable name and Guid + + @param[in] Name1 Name of first variable. + @param[in] Name1Size Size of first variable. + @param[in] Name2 Name of second variable. + @param[in] Name2Size Size of second variable. + @param[in] Guid1 Guid for first variable. + @param[in] Guid2 Guid for second variable. + + @retval 0 First name is identical to Second name. + @return others First name is not identical to Second name. + +**/ +INTN +CompareVariableNameAndGuid ( + IN CONST CHAR16 *Name1, + IN UINTN Name1Size, + IN CONST CHAR16 *Name2, + IN UINTN Name2Size, + IN EFI_GUID *Guid1, + IN EFI_GUID *Guid2 + ); + +/** + + Compare variable digest. + + @param[in] Variable1 Pointer to first variable digest. + @param[in] Variable2 Pointer to second variable digest. + + @retval 0 Variables are identical. + @return others Variables are not identical. + +**/ +INTN +CompareVariableDigestInfo ( + IN VARIABLE_DIGEST *Variable1, + IN VARIABLE_DIGEST *Variable2 + ); + +/** + + Move a node backward in the order controlled by SortMethod. + + @param[in] Node Pointer to node to be moved. + @param[in] SortMethod Method used to compare node in list. + +**/ +VOID +MoveNodeBackward ( + IN OUT VARIABLE_DIGEST *Node, + IN SORT_METHOD SortMethod + ); + +/** + + Remove variable digest node. + + @param[in,out] Global Pointer to global configuration data. + @param[in,out] VarDig Pointer to variable digest value. + @param[in] FreeResource Flag to indicate whether to free resource. + +**/ +VOID +RemoveVariableDigestNode ( + IN OUT PROTECTED_VARIABLE_GLOBAL *Global, + IN OUT VARIABLE_DIGEST *VarDig, + IN BOOLEAN FreeResource + ); + +/** + + Insert variable digest node. + + @param[in,out] Global Pointer to global configuration data. + @param[in] VarDig Pointer to variable digest value. + @param[in] SortMethod Method for sorting. + +**/ +VOID +InsertVariableDigestNode ( + IN OUT PROTECTED_VARIABLE_GLOBAL *Global, + IN VARIABLE_DIGEST *VarDig, + IN SORT_METHOD SortMethod + ); + +/** + + Create variable digest node. + + @param[in] VariableName Name of variable. + @param[in] VendorGuid Guid of variable. + @param[in] NameSize Size of variable name. + @param[in] DataSize Size of variable data. + @param[in] AuthVar Authenticated variable flag. + @param[in] Global Pointer to global configuration data. + + @retval Ptr Pointer to variable digest + +**/ +VARIABLE_DIGEST * +CreateVariableDigestNode ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN UINT16 NameSize, + IN UINT32 DataSize, + IN BOOLEAN AuthVar, + IN PROTECTED_VARIABLE_GLOBAL *Global + ); + +/** + + This function is used to enumerate the variables managed by current + ProtectedVariableLib. + + If the VarInfo->StoreIndex is invalid (VAR_INDEX_INVALID), the first variable + with the smallest StoreIndex will be returned. Otherwise, the variable with + StoreIndex just after than the VarInfo->StoreIndex will be returned. + + @param[in,out] VarInfo Pointer to structure containing variable information. + + @retval EFI_SUCCESS Found the specified variable. + @retval EFI_INVALID_PARAMETER VarInfo is NULL. + @retval EFI_NOT_FOUND The specified variable could not be found. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibGetNextInternal ( + IN OUT PROTECTED_VARIABLE_INFO *VarInfo + ); + +/** + + Find the specified variable digest + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to variable data. + @param[in] FindNext Flag to continue looking for variable. + +**/ +VARIABLE_DIGEST * +FindVariableInternal ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN BOOLEAN FindNext + ); + +/** + + Synchronize the RPMC counters + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to variable data. + @param[in] FindNext Flag to continue looking for variable. + + @retval EFI_SUCCESS Successfully sync RPMC counters. + @return others Failed to sync RPMC counters. + +**/ +EFI_STATUS +SyncRpmcCounter ( + VOID + ); + +/** + + Perform for protected variable integrity check. + + If this initialization failed upon any error, the whole variable services + should not be used. A system reset might be needed to re-construct NV + variable storage to be the default state. + + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + + @retval EFI_SUCCESS Protected variable services are ready. + @retval EFI_INVALID_PARAMETER If ContextIn == NULL or something missing or + mismatching in the content in ContextIn. + @retval EFI_COMPROMISED_DATA If failed to check integrity of protected variables. + @retval EFI_OUT_OF_RESOURCES Fail to allocate enough resource. + @retval EFI_UNSUPPORTED Unsupported to process protected variable. + +**/ +EFI_STATUS +EFIAPI +PerformVariableIntegrityCheck ( + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn + ); + +extern EFI_TIME mDefaultTimeStamp; +extern VARIABLE_IDENTIFIER mUnprotectedVariables[UnprotectedVarIndexMax]; +extern PROTECTED_VARIABLE_CONTEXT_IN mVariableContextIn; +extern PROTECTED_VARIABLE_GLOBAL mProtectedVariableGlobal; + +#endif diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableCommon.c b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableCommon.c new file mode 100644 index 000000000000..ed1a8e1ec545 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableCommon.c @@ -0,0 +1,2095 @@ +/** @file + The common protected variable operation routines. + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "ProtectedVariableInternal.h" + +EFI_TIME mDefaultTimeStamp = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +VARIABLE_IDENTIFIER mUnprotectedVariables[] = { + { + METADATA_HMAC_VARIABLE_NAME, + &METADATA_HMAC_VARIABLE_GUID, + VAR_ADDED & VAR_IN_DELETED_TRANSITION + }, + { + METADATA_HMAC_VARIABLE_NAME, + &METADATA_HMAC_VARIABLE_GUID, + VAR_ADDED + }, + { + VAR_ERROR_FLAG_NAME, + &gEdkiiVarErrorFlagGuid, + VAR_ADDED + }, + { + (CHAR16 *)PcdGetPtr (PcdPlatformVariableName), + (EFI_GUID *)PcdGetPtr (PcdPlatformVariableGuid), + VAR_ADDED + } +}; + +/** + Print variable information. + + @param[in] Data8 Pointer to data. + @param[in] DataSize Size of data. + +**/ +VOID +PrintVariableData ( + IN UINT8 *Data8, + IN UINTN DataSize + ) +{ + UINTN Index; + + for (Index = 0; Index < DataSize; Index++) { + if (Index % 0x10 == 0) { + DEBUG ((DEBUG_INFO, "\n%08X:", Index)); + } + + DEBUG ((DEBUG_INFO, " %02X", *Data8++)); + } + + DEBUG ((DEBUG_INFO, "\n")); +} + +/** + + Retrieve the context and global configuration data structure from HOB. + + Once protected NV variable storage is cached and verified in PEI phase, + all related information are stored in a HOB which can be used by PEI variable + service itself and passed to SMM along with the boot flow, which can avoid + many duplicate works, like generating HMAC key, verifying NV variable storage, + etc. + + The HOB can be identified by gEdkiiProtectedVariableGlobalGuid. + + @param[out] Global Pointer to global configuration data from PEI phase. + + @retval EFI_SUCCESS The HOB was found, and Context and Global are retrieved. + @retval EFI_NOT_FOUND The HOB was not found. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableGlobalFromHob ( + OUT PROTECTED_VARIABLE_GLOBAL **Global + ) +{ + VOID *Data; + EFI_PEI_HOB_POINTERS Hob; + EFI_HOB_MEMORY_ALLOCATION *MemoryAllocationHob; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + EFI_PHYSICAL_ADDRESS OldStart; + VARIABLE_DIGEST *VarDig; + EFI_HOB_GUID_TYPE *GuidHob; + UINTN Index; + + Hob.Raw = GetFirstGuidHob (&gEdkiiProtectedVariableGlobalGuid); + if (Hob.Raw != NULL) { + Data = GET_GUID_HOB_DATA (Hob); + } else { + // + // Search the global from allocated memory blob. + // + Data = NULL; + MemoryAllocationHob = NULL; + + Hob.Raw = GetFirstHob (EFI_HOB_TYPE_MEMORY_ALLOCATION); + while (Hob.Raw != NULL) { + MemoryAllocationHob = (EFI_HOB_MEMORY_ALLOCATION *)Hob.Raw; + if (CompareGuid ( + &MemoryAllocationHob->AllocDescriptor.Name, + &gEdkiiProtectedVariableGlobalGuid + )) + { + Data = (VOID *)(UINTN) + MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress; + break; + } + + Hob.Raw = GET_NEXT_HOB (Hob); + Hob.Raw = GetNextHob (EFI_HOB_TYPE_MEMORY_ALLOCATION, Hob.Raw); + } + } + + if (Data == NULL) { + return EFI_NOT_FOUND; + } + + if (Global != NULL) { + GuidHob = GetFirstGuidHob (&gEdkiiProtectedVariableContextGuid); + if (GuidHob != NULL) { + ContextIn = (PROTECTED_VARIABLE_CONTEXT_IN *)GET_GUID_HOB_DATA (GuidHob); + } else { + ASSERT (GuidHob == NULL); + } + + *Global = (PROTECTED_VARIABLE_GLOBAL *)((UINT8 *)Data); + // + // Fix pointers in the HOB (due to physical memory readiness) + // + if ((*Global)->GlobalSelf != (EFI_PHYSICAL_ADDRESS)(UINTN)(*Global)) { + OldStart = (*Global)->GlobalSelf; + (*Global)->ContextIn = GET_ADRS (ContextIn); + + // + // Mark Memory caching is available + // + (*Global)->Flags.CacheReady = TRUE; + + // + // Re-allocate new minimum cache + // + (*Global)->VariableCache = GET_ADRS (Data) + + ((*Global)->VariableCache - OldStart); + + (*Global)->DigestContext = GET_ADRS (((*Global) + 1)); + for (Index = 0; Index < UnprotectedVarIndexMax; Index++) { + if ((*Global)->Unprotected[Index] != VAR_INDEX_INVALID) { + (*Global)->Unprotected[Index] = GET_ADRS (Data) + + ((*Global)->Unprotected[Index] - OldStart); + } + } + + (*Global)->LastAccessedVariable = GET_ADRS (Data) + + ((*Global)->LastAccessedVariable - OldStart); + + // + // Fix all linked-list pointers inside VARIABLE_SIGNATURE. + // + (*Global)->VariableDigests = GET_ADRS (Data) + + ((*Global)->VariableDigests - OldStart); + VarDig = VAR_DIG_PTR ((*Global)->VariableDigests); + while (VarDig != NULL) { + if (VarDig->Prev != 0) { + VarDig->Prev = GET_ADRS (Data) + (VarDig->Prev - OldStart); + } + + if (VarDig->Next != 0) { + VarDig->Next = GET_ADRS (Data) + (VarDig->Next - OldStart); + } + + VarDig = VAR_DIG_NEXT (VarDig); + } + + (*Global)->GlobalSelf = (EFI_PHYSICAL_ADDRESS)(UINTN)(*Global); + } + } + + return EFI_SUCCESS; +} + +/** + + Derive HMAC key from given variable root key. + + @param[in] RootKey Pointer to root key to derive from. + @param[in] RootKeySize Size of root key. + @param[out] HmacKey Pointer to generated HMAC key. + @param[in] HmacKeySize Size of HMAC key. + + @retval TRUE The HMAC key is derived successfully. + @retval FALSE Failed to generate HMAC key from given root key. + +**/ +BOOLEAN +EFIAPI +GenerateMetaDataHmacKey ( + IN CONST UINT8 *RootKey, + IN UINTN RootKeySize, + OUT UINT8 *HmacKey, + IN UINTN HmacKeySize + ) +{ + UINT8 Salt[AES_BLOCK_SIZE]; + + return HkdfSha256ExtractAndExpand ( + RootKey, + RootKeySize, + Salt, + 0, + (UINT8 *)METADATA_HMAC_KEY_NAME, + METADATA_HMAC_KEY_NAME_SIZE, + HmacKey, + HmacKeySize + ); +} + +/** + + Return the size of variable MetaDataHmacVar. + + @param[in] AuthFlag Auth-variable indicator. + + @retval size of variable MetaDataHmacVar. + +**/ +UINTN +GetMetaDataHmacVarSize ( + IN BOOLEAN AuthFlag + ) +{ + UINTN Size; + + if (AuthFlag) { + Size = sizeof (AUTHENTICATED_VARIABLE_HEADER); + } else { + Size = sizeof (VARIABLE_HEADER); + } + + Size += METADATA_HMAC_VARIABLE_NAME_SIZE; + Size += GET_PAD_SIZE (Size); + Size += METADATA_HMAC_SIZE; + Size += GET_PAD_SIZE (Size); + + return Size; +} + +/** + + Digests the given variable data and updates HMAC context. + + @param[in] Context Pointer to initialized HMAC context. + @param[in] VarInfo Pointer to variable data. + @param[in] UpdateMethod Function to run when updating variable digest. + + @retval TRUE HMAC context was updated successfully. + @retval FALSE Failed to update HMAC context. + +**/ +STATIC +BOOLEAN +UpdateVariableDigestData ( + IN VOID *Context, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN DIGEST_UPDATE UpdateMethod + ) +{ + VOID *Buffer[12]; + UINT32 BufferSize[12]; + UINTN Index; + BOOLEAN Status; + + // + // Empty variable is legal here (e.g. variable deletion case or write-init case). + // + if ((VarInfo == NULL) || + (VarInfo->CipherData == NULL) || + (VarInfo->CipherDataSize == 0)) + { + return TRUE; + } + + // + // HMAC (":" || VariableName) + // + Buffer[0] = METADATA_HMAC_SEP; + BufferSize[0] = METADATA_HMAC_SEP_SIZE; + + Buffer[1] = VarInfo->Header.VariableName; + BufferSize[1] = (UINT32)VarInfo->Header.NameSize; + + // + // HMAC (":" || VendorGuid || Attributes || DataSize) + // + Buffer[2] = METADATA_HMAC_SEP; + BufferSize[2] = METADATA_HMAC_SEP_SIZE; + + Buffer[3] = VarInfo->Header.VendorGuid; + BufferSize[3] = sizeof (EFI_GUID); + + Buffer[4] = &VarInfo->Header.Attributes; + BufferSize[4] = sizeof (VarInfo->Header.Attributes); + + Buffer[5] = &VarInfo->CipherDataSize; + BufferSize[5] = sizeof (VarInfo->CipherDataSize); + + // + // HMAC (":" || CipherData) + // + Buffer[6] = METADATA_HMAC_SEP; + BufferSize[6] = METADATA_HMAC_SEP_SIZE; + + Buffer[7] = VarInfo->CipherData; + BufferSize[7] = VarInfo->CipherDataSize; + + // + // HMAC (":" || PubKeyIndex || AuthMonotonicCount || TimeStamp) + // + Buffer[8] = METADATA_HMAC_SEP; + BufferSize[8] = METADATA_HMAC_SEP_SIZE; + + Buffer[9] = &VarInfo->Header.PubKeyIndex; + BufferSize[9] = sizeof (VarInfo->Header.PubKeyIndex); + + Buffer[10] = &VarInfo->Header.MonotonicCount; + BufferSize[10] = sizeof (VarInfo->Header.MonotonicCount); + + Buffer[11] = (VarInfo->Header.TimeStamp != NULL) ? + VarInfo->Header.TimeStamp : &mDefaultTimeStamp; + BufferSize[11] = sizeof (EFI_TIME); + + for (Index = 0; Index < ARRAY_SIZE (Buffer); ++Index) { + Status = UpdateMethod (Context, Buffer[Index], BufferSize[Index]); + if (!Status) { + ASSERT (FALSE); + return FALSE; + } + } + + return TRUE; +} + +/** + + Digests the given variable data and updates HMAC context. + + @param[in] Context Pointer to initialized HMAC context. + @param[in] VarInfo Pointer to variable data. + + @retval TRUE HMAC context was updated successfully. + @retval FALSE Failed to update HMAC context. + +**/ +BOOLEAN +UpdateVariableMetadataHmac ( + IN VOID *Context, + IN PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + return UpdateVariableDigestData (Context, VarInfo, (DIGEST_UPDATE)HmacSha256Update); +} + +/** + + Get the variable digest. + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to verified copy of protected variables. + @param[in,out] DigestValue Pointer to variable digest value. + + @retval EFI_INVALID_PARAMETER Invalid parameter was passed in. + @retval EFI_OUT_OF_RESOURCES Not enough resource to calculate hash. + @retval EFI_ABORTED An error was encountered. + @retval EFI_COMPROMISED_DATA The data was compromised. + @retval EFI_SUCCESS Variable digest was successfully verified. + +**/ +EFI_STATUS +GetVariableDigest ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN OUT UINT8 *DigestValue + ) +{ + EFI_STATUS Status; + VOID *Context; + + if ((Global == NULL) || (VarInfo == NULL) || (DigestValue == NULL)) { + ASSERT (Global != NULL); + ASSERT (VarInfo != NULL); + ASSERT (DigestValue != NULL); + return EFI_INVALID_PARAMETER; + } + + Context = GET_BUFR (Global->DigestContext); + if (!HashApiInit (Context)) { + ASSERT (Context != NULL); + return EFI_OUT_OF_RESOURCES; + } + + if (VarInfo->CipherData == NULL) { + VarInfo->CipherData = VarInfo->Header.Data; + VarInfo->CipherDataSize = (UINT32)VarInfo->Header.DataSize; + } + + if ( !UpdateVariableDigestData (Context, VarInfo, (DIGEST_UPDATE)HashApiUpdate) + || !HashApiFinal (Context, DigestValue)) + { + ASSERT (FALSE); + Status = EFI_ABORTED; + } else { + Status = EFI_SUCCESS; + } + + return Status; +} + +/** + + Verify the variable digest. + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to verified copy of protected variables. + @param[in] VarDig Pointer to variable digest data. + + @retval EFI_INVALID_PARAMETER Invalid parameter was passed in. + @retval EFI_OUT_OF_RESOURCES Not enough resource to calculate hash. + @retval EFI_ABORTED An error was encountered. + @retval EFI_COMPROMISED_DATA The data was compromised. + @retval EFI_SUCCESS Variable digest was successfully verified. + +**/ +EFI_STATUS +VerifyVariableDigest ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN VARIABLE_DIGEST *VarDig + ) +{ + EFI_STATUS Status; + UINT8 NewDigest[METADATA_HMAC_SIZE]; + + if (Global->Flags.RecoveryMode || !VarDig->Flags.Protected) { + return EFI_SUCCESS; + } + + ASSERT (VarDig->DigestSize == sizeof (NewDigest)); + + Status = GetVariableDigest (Global, VarInfo, NewDigest); + if (!EFI_ERROR (Status)) { + if (CompareMem (VAR_DIG_VALUE (VarDig), NewDigest, VarDig->DigestSize) != 0) { + Status = EFI_COMPROMISED_DATA; + } + } + + return Status; +} + +/** + Initialize variable MetaDataHmacVar. + + @param[in,out] Variable Pointer to buffer of MetaDataHmacVar. + @param[in] AuthFlag Variable format flag. + +**/ +VOID +InitMetadataHmacVariable ( + IN OUT VARIABLE_HEADER *Variable, + IN BOOLEAN AuthFlag + ) +{ + UINT8 *NamePtr; + AUTHENTICATED_VARIABLE_HEADER *AuthVariable; + + Variable->StartId = VARIABLE_DATA; + Variable->State = VAR_ADDED; + Variable->Reserved = 0; + Variable->Attributes = VARIABLE_ATTRIBUTE_NV_BS_RT; + + if (AuthFlag) { + AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *)Variable; + + AuthVariable->NameSize = METADATA_HMAC_VARIABLE_NAME_SIZE; + AuthVariable->DataSize = METADATA_HMAC_SIZE; + AuthVariable->PubKeyIndex = 0; + AuthVariable->MonotonicCount = 0; + + ZeroMem (&AuthVariable->TimeStamp, sizeof (EFI_TIME)); + CopyMem (&AuthVariable->VendorGuid, &METADATA_HMAC_VARIABLE_GUID, sizeof (EFI_GUID)); + + NamePtr = (UINT8 *)AuthVariable + sizeof (AUTHENTICATED_VARIABLE_HEADER); + } else { + Variable->NameSize = METADATA_HMAC_VARIABLE_NAME_SIZE; + Variable->DataSize = METADATA_HMAC_SIZE; + + CopyMem (&Variable->VendorGuid, &METADATA_HMAC_VARIABLE_GUID, sizeof (EFI_GUID)); + + NamePtr = (UINT8 *)Variable + sizeof (VARIABLE_HEADER); + } + + CopyMem (NamePtr, METADATA_HMAC_VARIABLE_NAME, METADATA_HMAC_VARIABLE_NAME_SIZE); +} + +/** + Re-calculate HMAC based on new variable data and re-generate MetaDataHmacVar. + + @param[in] Global Pointer to global configuration data. + @param[in] NewVarInfo Pointer to buffer of new variable data. + @param[in,out] NewHmacVarInfo Pointer to buffer of new MetaDataHmacVar. + + @return EFI_SUCCESS The HMAC value was updated successfully. + @return EFI_ABORTED Failed to calculate the HMAC value. + @return EFI_OUT_OF_RESOURCES Not enough resource to calculate HMC value. + @return EFI_NOT_FOUND The MetaDataHmacVar was not found in storage. + +**/ +EFI_STATUS +RefreshVariableMetadataHmac ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *NewVarInfo, + IN OUT PROTECTED_VARIABLE_INFO *NewHmacVarInfo + ) +{ + EFI_STATUS Status; + VOID *Context; + UINT32 Counter; + PROTECTED_VARIABLE_INFO VarInfo; + PROTECTED_VARIABLE_INFO CurrHmacVarInfo; + UINT8 *HmacValue; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + VARIABLE_DIGEST *VarDig; + VARIABLE_DIGEST *HmacVarDig; + + ZeroMem ((VOID *)&VarInfo, sizeof (VarInfo)); + ZeroMem ((VOID *)&CurrHmacVarInfo, sizeof (CurrHmacVarInfo)); + + Status = RequestMonotonicCounter (RPMC_COUNTER_2, &Counter); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + Counter += 1; + ContextIn = GET_CNTX (Global); + + // + // Delete current MetaDataHmacVariable first, if any. + // + if (Global->Unprotected[IndexHmacAdded] != VAR_INDEX_INVALID) { + HmacVarDig = VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded]); + + CurrHmacVarInfo.Header.NameSize = HmacVarDig->NameSize; + CurrHmacVarInfo.Header.VariableName = VAR_DIG_NAME (HmacVarDig); + CurrHmacVarInfo.Header.VendorGuid = VAR_DIG_GUID (HmacVarDig); + + CurrHmacVarInfo.Buffer = VAR_HDR_PTR (HmacVarDig->CacheIndex); + CurrHmacVarInfo.StoreIndex = HmacVarDig->StoreIndex; + CurrHmacVarInfo.Flags.Auth = HmacVarDig->Flags.Auth; + // + // Force marking current MetaDataHmacVariable as VAR_IN_DELETED_TRANSITION. + // + CurrHmacVarInfo.Buffer->State &= VAR_IN_DELETED_TRANSITION; + HmacVarDig->State &= VAR_IN_DELETED_TRANSITION; + Status = ContextIn->UpdateVariableStore ( + &CurrHmacVarInfo, + OFFSET_OF (VARIABLE_HEADER, State), + sizeof (CurrHmacVarInfo.Buffer->State), + &CurrHmacVarInfo.Buffer->State + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + } else if (Global->Unprotected[IndexHmacInDel] != VAR_INDEX_INVALID) { + HmacVarDig = VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel]); + } else { + // + // No MetaDataHmacVar. Allocate space to cache its value. + // + HmacVarDig = CreateVariableDigestNode ( + METADATA_HMAC_VARIABLE_NAME, + &METADATA_HMAC_VARIABLE_GUID, + METADATA_HMAC_VARIABLE_NAME_SIZE, + METADATA_HMAC_SIZE, + Global->Flags.Auth, + Global + ); + if (HmacVarDig == NULL) { + ASSERT (HmacVarDig != NULL); + return EFI_OUT_OF_RESOURCES; + } + + HmacVarDig->Flags.Protected = FALSE; + } + + if (HmacVarDig->CacheIndex == VAR_INDEX_INVALID) { + HmacVarDig->CacheIndex = (GET_ADRS (Global)) + (Global->StructSize - GetMetaDataHmacVarSize (Global->Flags.Auth)); + } + + // + // Construct new MetaDataHmacVar. + // + if (NewHmacVarInfo == NULL) { + NewHmacVarInfo = &VarInfo; + NewHmacVarInfo->Buffer = GET_BUFR (HmacVarDig->CacheIndex); + } + + InitMetadataHmacVariable (NewHmacVarInfo->Buffer, Global->Flags.Auth); + + NewHmacVarInfo->StoreIndex = VAR_INDEX_INVALID; // Skip calculating offset + NewHmacVarInfo->Flags.Auth = Global->Flags.Auth; + Status = ContextIn->GetVariableInfo (NewHmacVarInfo); + ASSERT_EFI_ERROR (Status); + HmacValue = NewHmacVarInfo->Header.Data; + + // + // Re-calculate HMAC for all valid variables + // + Context = HmacSha256New (); + if (Context == NULL) { + ASSERT (Context != NULL); + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_ABORTED; + if (!HmacSha256SetKey ( + Context, + Global->MetaDataHmacKey, + sizeof (Global->MetaDataHmacKey) + )) + { + ASSERT (FALSE); + goto Done; + } + + // + // HMAC (|| hash(Var1) || hash(Var2) || ... || hash(VarN)) + // + VarDig = VAR_DIG_PTR (Global->VariableDigests); + while (VarDig != NULL) { + if (VarDig->Flags.Valid && VarDig->Flags.Protected) { + HmacSha256Update (Context, VAR_DIG_VALUE (VarDig), VarDig->DigestSize); + } + + VarDig = VAR_DIG_NEXT (VarDig); + } + + // + // HMAC (RpmcMonotonicCounter) + // + if (!HmacSha256Update (Context, &Counter, sizeof (Counter))) { + ASSERT (FALSE); + goto Done; + } + + if (!HmacSha256Final (Context, HmacValue)) { + ASSERT (FALSE); + goto Done; + } + + // + // Update HMAC value in cache. + // + CopyMem (VAR_DIG_VALUE (HmacVarDig), HmacValue, HmacVarDig->DataSize); + if ((HmacVarDig->Prev == 0) && (HmacVarDig->Next == 0)) { + InsertVariableDigestNode (Global, HmacVarDig, NULL); + } + + // + // Just one MetaDataHmacVar is needed for normal operation. + // + Global->Unprotected[IndexHmacAdded] = VAR_DIG_ADR (HmacVarDig); + Global->Unprotected[IndexHmacInDel] = VAR_INDEX_INVALID; + + Status = EFI_SUCCESS; + +Done: + if (Context != NULL) { + HmacSha256Free (Context); + } + + return Status; +} + +/** + + Check if a given variable is unprotected variable specified in advance + and return its index ID. + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to variable information data. + + @retval IndexHmacInDel Variable is MetaDataHmacVar in delete-transition state. + @retval IndexHmacAdded Variable is MetaDataHmacVar in valid state. + @retval IndexErrorFlag Variable is VarErrorLog. + @retval Others Variable is not any known unprotected variables. + +**/ +UNPROTECTED_VARIABLE_INDEX +CheckKnownUnprotectedVariable ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + UNPROTECTED_VARIABLE_INDEX Index; + + if ((VarInfo == NULL) || ( (VarInfo->StoreIndex == VAR_INDEX_INVALID) + && ( (VarInfo->Header.VariableName == NULL) + || (VarInfo->Header.VendorGuid == NULL)))) + { + ASSERT (VarInfo != NULL); + ASSERT (VarInfo->StoreIndex != VAR_INDEX_INVALID); + ASSERT (VarInfo->Header.VariableName != NULL); + ASSERT (VarInfo->Header.VendorGuid != NULL); + return UnprotectedVarIndexMax; + } + + for (Index = 0; Index < UnprotectedVarIndexMax; ++Index) { + if ( (Global->Unprotected[Index] != VAR_INDEX_INVALID) + && (VarInfo->StoreIndex != VAR_INDEX_INVALID)) + { + if (VarInfo->StoreIndex == VAR_DIG_PTR (Global->Unprotected[Index])->StoreIndex) { + break; + } + } else if (IS_VARIABLE ( + &VarInfo->Header, + mUnprotectedVariables[Index].VariableName, + mUnprotectedVariables[Index].VendorGuid + ) && (VarInfo->Header.State == mUnprotectedVariables[Index].State)) + { + break; + } + } + + return Index; +} + +/** + + Compare variable name and Guid + + @param[in] Name1 Name of first variable. + @param[in] Name1Size Size of first variable. + @param[in] Name2 Name of second variable. + @param[in] Name2Size Size of second variable. + @param[in] Guid1 Guid for first variable. + @param[in] Guid2 Guid for second variable. + + @retval 0 First name is identical to Second name. + @return others First name is not identical to Second name. + +**/ +INTN +CompareVariableNameAndGuid ( + IN CONST CHAR16 *Name1, + IN UINTN Name1Size, + IN CONST CHAR16 *Name2, + IN UINTN Name2Size, + IN EFI_GUID *Guid1, + IN EFI_GUID *Guid2 + ) +{ + INTN Result; + + Result = StrnCmp ( + Name1, + Name2, + MIN (Name1Size, Name2Size) / sizeof (CHAR16) + ); + if (Result == 0) { + if (Name1Size != Name2Size) { + // + // Longer name is 'bigger' than shorter one. + // + Result = (INTN)Name1Size - (INTN)Name2Size; + } else { + // + // The variable name is the same. Compare the GUID. + // + Result = CompareMem ((VOID *)Guid1, (VOID *)Guid2, sizeof (EFI_GUID)); + } + } + + return Result; +} + +/** + + Compare variable digest. + + @param[in] Variable1 Pointer to first variable digest. + @param[in] Variable2 Pointer to second variable digest. + + @retval 0 Variables are identical. + @return others Variables are not identical. + +**/ +INTN +CompareVariableDigestInfo ( + IN VARIABLE_DIGEST *Variable1, + IN VARIABLE_DIGEST *Variable2 + ) +{ + return CompareVariableNameAndGuid ( + VAR_DIG_NAME (Variable1), + Variable1->NameSize, + VAR_DIG_NAME (Variable2), + Variable2->NameSize, + &Variable1->VendorGuid, + &Variable2->VendorGuid + ); +} + +/** + + Move a node backward in the order controlled by SortMethod. + + @param[in,out] Node Pointer to node to be moved. + @param[in] SortMethod Method used to compare node in list. + +**/ +VOID +MoveNodeBackward ( + IN OUT VARIABLE_DIGEST *Node, + IN SORT_METHOD SortMethod + ) +{ + VARIABLE_DIGEST *Curr; + VARIABLE_DIGEST *Prev; + INTN Result; + + Curr = Node; + while (Curr != NULL) { + Prev = VAR_DIG_PREV (Curr); + if (Prev == NULL) { + Result = -1; + } else { + Result = SortMethod (Prev, Node); + } + + // + // 'Result > 0' means the 'Prev' is 'bigger' than 'Node'. Continue to check + // previous node til a node 'smaller' than 'Node' found. + // + if (Result > 0) { + Curr = Prev; + continue; + } + + if (Curr != Node) { + // + // Remove Node first + // + if (VAR_DIG_PREV (Node) != NULL) { + VAR_DIG_PREV (Node)->Next = Node->Next; + } + + if (VAR_DIG_NEXT (Node) != NULL) { + VAR_DIG_NEXT (Node)->Prev = Node->Prev; + } + + // + // Insert Node before Curr. + // + Node->Prev = Curr->Prev; + Node->Next = VAR_DIG_ADR (Curr); + + if (Curr->Prev != 0) { + VAR_DIG_PREV (Curr)->Next = VAR_DIG_ADR (Node); + } + + Curr->Prev = VAR_DIG_ADR (Node); + } + + // + // If there're two identical variables in storage, one of them must be + // "in-delete-transition" state. Mark it as "deleted" anyway. + // + if (Result == 0) { + if (Curr->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION)) { + Curr->State &= VAR_DELETED; + } + + if (Prev->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION)) { + Prev->State &= VAR_DELETED; + } + } + + break; + } +} + +/** + + Create variable digest node. + + @param[in] VariableName Name of variable. + @param[in] VendorGuid Guid of variable. + @param[in] NameSize Size of variable name. + @param[in] DataSize Size of variable data. + @param[in] AuthVar Authenticated variable flag. + @param[in] Global Pointer to global configuration data. + + @retval Ptr Pointer to variable digest + +**/ +VARIABLE_DIGEST * +CreateVariableDigestNode ( + IN CHAR16 *VariableName, + IN EFI_GUID *VendorGuid, + IN UINT16 NameSize, + IN UINT32 DataSize, + IN BOOLEAN AuthVar, + IN PROTECTED_VARIABLE_GLOBAL *Global + ) +{ + VARIABLE_DIGEST *VarDig; + VOID *Buffer; + UINTN VarSize; + + VarDig = (VARIABLE_DIGEST *)AllocateZeroPool ( + sizeof (VARIABLE_DIGEST) + NameSize + METADATA_HMAC_SIZE + ); + if ((VarDig == NULL) || (Global == NULL)) { + ASSERT (VarDig != NULL); + ASSERT (Global != NULL); + return NULL; + } + + VarDig->DataSize = DataSize; + VarDig->NameSize = NameSize; + VarDig->DigestSize = METADATA_HMAC_SIZE; + VarDig->State = VAR_ADDED; + VarDig->Attributes = VARIABLE_ATTRIBUTE_NV_BS_RT; + VarDig->Flags.Auth = AuthVar; + VarDig->Flags.Valid = TRUE; + VarDig->Flags.Freeable = TRUE; + VarDig->Flags.Protected = PcdGetBool (PcdProtectedVariableIntegrity); + VarDig->Flags.Encrypted = PcdGetBool (PcdProtectedVariableConfidentiality); + VarDig->StoreIndex = VAR_INDEX_INVALID; + VarDig->CacheIndex = VAR_INDEX_INVALID; + + if (Global->Flags.CacheReady == TRUE) { + VarSize = VARIABLE_HEADER_SIZE (VarDig->Flags.Auth); + VarSize += VarDig->NameSize + GET_PAD_SIZE (VarDig->NameSize); + VarSize += VarDig->DataSize + GET_PAD_SIZE (VarDig->DataSize); + VarSize = HEADER_ALIGN (VarSize); + + Buffer = AllocateZeroPool (VarSize); + if (Buffer != NULL) { + VarDig->CacheIndex = (EFI_PHYSICAL_ADDRESS)(UINTN)Buffer; + } + } + + CopyMem (VAR_DIG_NAME (VarDig), VariableName, NameSize); + CopyMem (VAR_DIG_GUID (VarDig), VendorGuid, sizeof (EFI_GUID)); + + return VarDig; +} + +/** + + Remove variable digest node. + + @param[in,out] Global Pointer to global configuration data. + @param[in,out] VarDig Pointer to variable digest value. + @param[in] FreeResource Flag to indicate whether to free resource. + +**/ +VOID +RemoveVariableDigestNode ( + IN OUT PROTECTED_VARIABLE_GLOBAL *Global, + IN OUT VARIABLE_DIGEST *VarDig, + IN BOOLEAN FreeResource + ) +{ + VARIABLE_DIGEST *Prev; + VARIABLE_DIGEST *Next; + + Prev = VAR_DIG_PREV (VarDig); + Next = VAR_DIG_NEXT (VarDig); + + if (Global->VariableDigests == VAR_DIG_ADR (VarDig)) { + Global->VariableDigests = VAR_DIG_ADR (Next); + } + + if (Prev != NULL) { + Prev->Next = VAR_DIG_ADR (Next); + } + + if (Next != NULL) { + Next->Prev = VAR_DIG_ADR (Prev); + } + + VarDig->Prev = 0; + VarDig->Next = 0; + VarDig->Flags.Valid = FALSE; + + if (FreeResource && VarDig->Flags.Freeable) { + if ((VarDig->CacheIndex != 0) && (VarDig->CacheIndex != VAR_INDEX_INVALID)) { + VarDig->CacheIndex = VAR_INDEX_INVALID; + } + } +} + +/** + + Insert variable digest node. + + @param[in,out] Global Pointer to global configuration data. + @param[in] VarDig Pointer to variable digest value. + @param[in] SortMethod Method for sorting. + +**/ +VOID +InsertVariableDigestNode ( + IN OUT PROTECTED_VARIABLE_GLOBAL *Global, + IN VARIABLE_DIGEST *VarDig, + IN SORT_METHOD SortMethod + ) +{ + VARIABLE_DIGEST *Curr; + VARIABLE_DIGEST *Prev; + BOOLEAN DoReplace; + INTN Result; + + if (SortMethod == NULL) { + SortMethod = CompareVariableDigestInfo; + } + + DoReplace = FALSE; + Curr = VAR_DIG_PTR (Global->VariableDigests); + if (Curr == NULL) { + // + // First one. + // + VarDig->Prev = 0; + VarDig->Next = 0; + Global->VariableDigests = VAR_DIG_ADR (VarDig); + return; + } + + while (Curr != NULL && Curr != VarDig) { + Result = SortMethod (VarDig, Curr); + + if (Result <= 0) { + ASSERT (VarDig->StoreIndex != Curr->StoreIndex); + + // + // The same variable already in list? + // + if (Result == 0) { + // + // Keep only the same new one, unless states are different. In such + // situation, the one with no VAR_ADDED will be deleted. + // + if (VarDig->State >= Curr->State) { + DoReplace = TRUE; + Curr->Flags.Valid = FALSE; // to-be-deleted + } else { + DoReplace = FALSE; + VarDig->Flags.Valid = FALSE; // to-be-deleted + } + } + + // + // Put VarDig before Curr + // + VarDig->Next = VAR_DIG_ADR (Curr); + VarDig->Prev = Curr->Prev; + + if (VAR_DIG_PREV (Curr) != NULL) { + VAR_DIG_PREV (Curr)->Next = VAR_DIG_ADR (VarDig); + } + + Curr->Prev = VAR_DIG_ADR (VarDig); + + if (DoReplace) { + RemoveVariableDigestNode (Global, Curr, TRUE); + } + + break; + } + + Prev = Curr; + Curr = VAR_DIG_NEXT (Curr); + if (Curr == NULL) { + Prev->Next = VAR_DIG_ADR (VarDig); + + VarDig->Prev = VAR_DIG_ADR (Prev); + VarDig->Next = 0; + } + } + + // + // Update the head node if necessary. + // + if (VAR_DIG_PTR (VarDig->Prev) == NULL) { + Global->VariableDigests = VAR_DIG_ADR (VarDig); + } +} + +/** + + Find the specified variable digest + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to variable data. + @param[in] FindNext Flag to continue looking for variable. + +**/ +VARIABLE_DIGEST * +FindVariableInternal ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_INFO *VarInfo, + IN BOOLEAN FindNext + ) +{ + VARIABLE_DIGEST *VarDig; + VARIABLE_DIGEST *Found; + VARIABLE_DIGEST *FirstStoreIndexVar; + BOOLEAN ByIndex; + INTN FwdOrBwd; + + // + // If VarInfo->StoreIndex is valid, use it to find the variable. Otherwise, + // use the variable name and guid instead, if given. If no clue at all, return + // the variable with lowest StoreIndex. + // + if ( (VarInfo->StoreIndex != VAR_INDEX_INVALID) + || (VarInfo->Header.VariableName == NULL) + || (VarInfo->Header.VendorGuid == NULL)) + { + ByIndex = TRUE; + } else { + ByIndex = FALSE; + } + + Found = NULL; + VarDig = VAR_DIG_PTR (Global->VariableDigests); + FirstStoreIndexVar = VarDig; + FwdOrBwd = 1; + + // + // Discover variable with first/smallest store index + // + while (VarDig != NULL) { + if (VarDig->StoreIndex < FirstStoreIndexVar->StoreIndex) { + FirstStoreIndexVar = VAR_DIG_PTR (VarDig); + } + + VarDig = VAR_DIG_NEXT (VarDig); + } + + // + // Input variable is NULL than return first variable + // with smallest store index from the variable digest list. + // + if (((VarInfo->Header.VariableName == NULL) || + (VarInfo->Header.VendorGuid == NULL)) && + (ByIndex == FALSE)) + { + return FirstStoreIndexVar; + } + + // + // Start with first entry + // + VarDig = VAR_DIG_PTR (Global->VariableDigests); + while (VarDig != NULL) { + if (ByIndex) { + if (FindNext) { + if (VarDig->StoreIndex == VarInfo->StoreIndex) { + Found = VarDig = VAR_DIG_NEXT (VarDig); + break; + } + } else if (VarDig->StoreIndex == VarInfo->StoreIndex) { + Found = VarDig; + break; + } + } else { + // + // Match given variable name and vendor guid. + // + if (IS_VARIABLE (&VarInfo->Header, VAR_DIG_NAME (VarDig), VAR_DIG_GUID (VarDig))) { + Found = (FindNext) ? VAR_DIG_NEXT (VarDig) : VarDig; + break; + } + } + + VarDig = (FwdOrBwd > 0) ? VAR_DIG_NEXT (VarDig) : VAR_DIG_PREV (VarDig); + if (VarDig == NULL) { + } + } + + return Found; +} + +/** + + Synchronize the RPMC counters + + @param[in] Global Pointer to global configuration data. + @param[in] VarInfo Pointer to variable data. + @param[in] FindNext Flag to continue looking for variable. + + @retval EFI_SUCCESS Successfully sync RPMC counters. + @return others Failed to sync RPMC counters. + +**/ +EFI_STATUS +SyncRpmcCounter ( + VOID + ) +{ + UINT32 Counter1; + UINT32 Counter2; + EFI_STATUS Status; + + // + // Sync RPMC1 & RPMC2. + // + Status = RequestMonotonicCounter (RPMC_COUNTER_1, &Counter1); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + Status = RequestMonotonicCounter (RPMC_COUNTER_2, &Counter2); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + while (Counter1 < Counter2) { + Status = IncrementMonotonicCounter (RPMC_COUNTER_1); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + ++Counter1; + } + + while (Counter2 < Counter1) { + Status = IncrementMonotonicCounter (RPMC_COUNTER_2); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + ++Counter2; + } + + return EFI_SUCCESS; +} + +/** + + An alternative version of ProtectedVariableLibGetData to get plain data from + given variable, if encrypted. + + @param[in] Global Pointer to global configuration data. + @param[in,out] VarInfo Pointer to structure containing variable + information. VarInfo->Header.Data must point + to the original variable data. + + @retval EFI_SUCCESS Found the specified variable. + @retval EFI_INVALID_PARAMETER VarInfo is NULL or both VarInfo->Buffer and + VarInfo->Offset are invalid. + @retval EFI_NOT_FOUND The specified variable could not be found. + +**/ +STATIC +EFI_STATUS +ProtectedVariableLibGetDataInternal ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN OUT PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + VOID *Buffer; + UINTN BufferSize; + + if ((Global == NULL) || (VarInfo == NULL)) { + return EFI_INVALID_PARAMETER; + } + + ContextIn = GET_CNTX (Global); + + // + // Check if the data has been decrypted or not. + // + VarInfo->CipherData = NULL; + VarInfo->CipherDataSize = 0; + VarInfo->PlainData = NULL; + VarInfo->PlainDataSize = 0; + Status = GetCipherDataInfo (VarInfo); + + if ((Status == EFI_UNSUPPORTED) || (Status == EFI_NOT_FOUND)) { + VarInfo->Flags.DecryptInPlace = TRUE; + VarInfo->PlainDataSize = (UINT32)VarInfo->Header.DataSize; + VarInfo->PlainData = VarInfo->Header.Data; + VarInfo->CipherDataType = 0; + VarInfo->CipherHeaderSize = 0; + Status = EFI_SUCCESS; + } else if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + // + // If the variable data is cipher data, decrypt it inplace if possible. + // + if ((VarInfo->PlainData == NULL) && (VarInfo->CipherData != NULL)) { + VarInfo->Key = Global->RootKey; + VarInfo->KeySize = sizeof (Global->RootKey); + + switch (ContextIn->VariableServiceUser) { + case FromPeiModule: + VarInfo->Flags.DecryptInPlace = FALSE; + // + // In PEI VariableCache holds Cipher header + Cipher data + // Do not override Cipher header data during decrypt operation + // + VarInfo->PlainData = GET_BUFR (Global->VariableCache + VarInfo->CipherHeaderSize); + + Status = DecryptVariable (VarInfo); + if (Status == EFI_UNSUPPORTED) { + VarInfo->PlainData = VarInfo->Header.Data; + VarInfo->PlainDataSize = (UINT32)VarInfo->Header.DataSize; + VarInfo->CipherDataType = 0; + VarInfo->CipherHeaderSize = 0; + + Status = EFI_SUCCESS; + } + + break; + + case FromSmmModule: + VarInfo->Flags.DecryptInPlace = FALSE; + VarInfo->PlainData = GET_BUFR (Global->VariableCache); + + Status = DecryptVariable (VarInfo); + if (Status == EFI_UNSUPPORTED) { + VarInfo->PlainData = VarInfo->Header.Data; + VarInfo->PlainDataSize = (UINT32)VarInfo->Header.DataSize; + VarInfo->CipherDataType = 0; + VarInfo->CipherHeaderSize = 0; + + Status = EFI_SUCCESS; + } + + break; + + case FromBootServiceModule: + case FromRuntimeModule: + // + // The SMM passes back only decrypted data. We re-use the original cipher + // data buffer to keep the plain data along with the cipher header. + // + VarInfo->Flags.DecryptInPlace = TRUE; + Buffer = (VOID *)((UINTN)VarInfo->CipherData + VarInfo->CipherHeaderSize); + BufferSize = VarInfo->PlainDataSize; + Status = ContextIn->FindVariableSmm ( + VarInfo->Header.VariableName, + VarInfo->Header.VendorGuid, + &VarInfo->Header.Attributes, + &BufferSize, + Buffer + ); + if (!EFI_ERROR (Status)) { + // + // Flag the payload as plain data to avoid re-decrypting. + // + VarInfo->CipherDataType = ENC_TYPE_NULL; + VarInfo->PlainDataSize = (UINT32)BufferSize; + VarInfo->PlainData = Buffer; + + Status = SetCipherDataInfo (VarInfo); + if (Status == EFI_UNSUPPORTED) { + Status = EFI_SUCCESS; + } + } + + break; + + default: + Status = EFI_UNSUPPORTED; + break; + } + + VarInfo->CipherData = NULL; + VarInfo->CipherDataSize = 0; + } + + return Status; +} + +/** + + An alternative version of ProtectedVariableLibGetData to get plain data, if + encrypted, from given variable, for different use cases. + + @param[in,out] VarInfo Pointer to structure containing variable information. + + @retval EFI_SUCCESS Found the specified variable. + @retval EFI_INVALID_PARAMETER VarInfo is NULL or both VarInfo->Buffer and + VarInfo->Offset are invalid. + @retval EFI_NOT_FOUND The specified variable could not be found. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibGetByInfo ( + IN OUT PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_GLOBAL *Global; + VOID **Buffer; + UINT32 BufferSize; + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + // + // Save the output data buffer because below call + // call will use this struct field internally. + // + Buffer = VarInfo->PlainData; + BufferSize = VarInfo->PlainDataSize; + + Status = ProtectedVariableLibGetDataInternal (Global, VarInfo); + if (EFI_ERROR (Status)) { + // + // Return with caller provided buffer with zero DataSize + // + VarInfo->PlainData = Buffer; + VarInfo->PlainDataSize = 0; + return Status; + } + + if ((Buffer == NULL) || ((BufferSize) < VarInfo->PlainDataSize)) { + // + // Return with caller provided buffer with true PlainDataSize + // + VarInfo->PlainData = Buffer; + return EFI_BUFFER_TOO_SMALL; + } + + // + // Copy Plain data to ouput data buffer + // + CopyMem (Buffer, VarInfo->PlainData, VarInfo->PlainDataSize); + VarInfo->PlainData = Buffer; + + return Status; +} + +/** + + Retrieve plain data, if encrypted, of given variable. + + If variable encryption is employed, this function will initiate a SMM request + to get the plain data. Due to security consideration, the decryption can only + be done in SMM environment. + + @param[in] Variable Pointer to header of a Variable. + @param[in,out] Data Pointer to plain data of the given variable. + @param[in,out] DataSize Size of data returned or data buffer needed. + @param[in] AuthFlag Auth-variable indicator. + + @retval EFI_SUCCESS Found the specified variable. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND The specified variable could not be found. + @retval EFI_BUFFER_TOO_SMALL If *DataSize is smaller than needed. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibGetByBuffer ( + IN VARIABLE_HEADER *Variable, + IN OUT VOID *Data, + IN OUT UINT32 *DataSize, + IN BOOLEAN AuthFlag + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO VarInfo; + PROTECTED_VARIABLE_GLOBAL *Global; + AUTHENTICATED_VARIABLE_HEADER *AuthVariable; + VOID *Buffer; + + if ((Variable == NULL) || (DataSize == NULL)) { + ASSERT (Variable != NULL); + ASSERT (DataSize != NULL); + return EFI_INVALID_PARAMETER; + } + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + ZeroMem (&VarInfo, sizeof (VarInfo)); + + VarInfo.Buffer = Variable; + VarInfo.Flags.Auth = AuthFlag; + + if (VarInfo.Flags.Auth == TRUE) { + AuthVariable = (AUTHENTICATED_VARIABLE_HEADER *)Variable; + + VarInfo.Header.VariableName = (CHAR16 *)((UINTN)Variable + sizeof (AUTHENTICATED_VARIABLE_HEADER)); + VarInfo.Header.NameSize = AuthVariable->NameSize; + VarInfo.Header.VendorGuid = &AuthVariable->VendorGuid; + VarInfo.Header.Attributes = AuthVariable->Attributes; + VarInfo.Header.DataSize = AuthVariable->DataSize; + } else { + VarInfo.Header.VariableName = (CHAR16 *)((UINTN)Variable + sizeof (VARIABLE_HEADER)); + VarInfo.Header.NameSize = Variable->NameSize; + VarInfo.Header.VendorGuid = &Variable->VendorGuid; + VarInfo.Header.Attributes = Variable->Attributes; + VarInfo.Header.DataSize = Variable->DataSize; + } + + Buffer = VARIABLE_NAME (VarInfo.Buffer, VarInfo.Flags.Auth); + Buffer = GET_BUFR (GET_ADRS (Buffer) + VarInfo.Header.NameSize); + Buffer = GET_BUFR (GET_ADRS (Buffer) + GET_PAD_SIZE (VarInfo.Header.NameSize)); + VarInfo.Header.Data = Buffer; + + Status = ProtectedVariableLibGetDataInternal (Global, &VarInfo); + if (!EFI_ERROR (Status)) { + if ((*DataSize) < VarInfo.PlainDataSize) { + *DataSize = VarInfo.PlainDataSize; + return EFI_BUFFER_TOO_SMALL; + } + + *DataSize = VarInfo.PlainDataSize; + CopyMem (Data, VarInfo.PlainData, VarInfo.PlainDataSize); + } + + return Status; +} + +/** + This service retrieves a variable's value using its name and GUID. + + Read the specified variable from the UEFI variable store. If the Data + buffer is too small to hold the contents of the variable, the error + EFI_BUFFER_TOO_SMALL is returned and DataSize is set to the required buffer + size to obtain the data. + + @param VariableName A pointer to a null-terminated string that is the variable's name. + @param VariableGuid A pointer to an EFI_GUID that is the variable's GUID. The combination of + VariableGuid and VariableName must be unique. + @param Attributes If non-NULL, on return, points to the variable's attributes. + @param DataSize On entry, points to the size in bytes of the Data buffer. + On return, points to the size of the data returned in Data. + @param Data Points to the buffer which will hold the returned variable value. + May be NULL with a zero DataSize in order to determine the size of the buffer needed. + + @retval EFI_SUCCESS The variable was read successfully. + @retval EFI_NOT_FOUND The variable was be found. + @retval EFI_BUFFER_TOO_SMALL The DataSize is too small for the resulting data. + DataSize is updated with the size required for + the specified variable. + @retval EFI_INVALID_PARAMETER VariableName, VariableGuid, DataSize or Data is NULL. + @retval EFI_DEVICE_ERROR The variable could not be retrieved because of a device error. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibGetByName ( + IN CONST CHAR16 *VariableName, + IN CONST EFI_GUID *VariableGuid, + OUT UINT32 *Attributes, + IN OUT UINTN *DataSize, + OUT VOID *Data OPTIONAL + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *VarDig; + PROTECTED_VARIABLE_INFO VarInfo; + EFI_TIME TimeStamp; + VOID *DataBuffer; + + if ((VariableName == NULL) || (VariableGuid == NULL) || (DataSize == NULL)) { + ASSERT (VariableName != NULL); + ASSERT (VariableGuid != NULL); + ASSERT (DataSize != NULL); + return EFI_INVALID_PARAMETER; + } + + Status = GetProtectedVariableGlobal (&Global); + + if (EFI_ERROR (Status)) { + return EFI_UNSUPPORTED; + } + + ContextIn = GET_CNTX (Global); + + ZeroMem (&VarInfo, sizeof (VarInfo)); + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Header.VariableName = (CHAR16 *)VariableName; + VarInfo.Header.NameSize = StrSize (VariableName); + VarInfo.Header.VendorGuid = (EFI_GUID *)VariableGuid; + + VarDig = FindVariableInternal (Global, &VarInfo, FALSE); + if (VarDig == NULL) { + return EFI_NOT_FOUND; + } + + if (Attributes != NULL) { + *Attributes = VarDig->Attributes; + } + + if ((Data == NULL) || (*DataSize < VarDig->PlainDataSize)) { + *DataSize = VarDig->PlainDataSize; + return EFI_BUFFER_TOO_SMALL; + } + + VarInfo.Flags.Auth = VarDig->Flags.Auth; + VarInfo.Flags.Protected = VarDig->Flags.Protected; + + // + // Verify digest before copy the data back, if the variable is not in cache. + // + if (VarDig->CacheIndex != VAR_INDEX_INVALID) { + VarInfo.Header.VariableName = NULL; + VarInfo.Header.VendorGuid = NULL; + VarInfo.Buffer = GET_BUFR (VarDig->CacheIndex); + + Status = ContextIn->GetVariableInfo (&VarInfo); + ASSERT_EFI_ERROR (Status); + } else { + // + // A buffer for at least one variable data (<=PcdMax(Auth)VariableSize) + // must be reserved in advance. + // + ASSERT ( + Global->VariableCache != 0 + && Global->VariableCacheSize >= VarDig->DataSize + ); + DataBuffer = GET_BUFR (Global->VariableCache); + // + // Note name and GUID are already there. + // + VarInfo.StoreIndex = VarDig->StoreIndex; + + VarInfo.Header.VariableName = NULL; // Prevent name from being retrieved again. + VarInfo.Header.NameSize = 0; + VarInfo.Header.VendorGuid = NULL; // Prevent guid from being retrieved again. + VarInfo.Header.TimeStamp = &TimeStamp; + VarInfo.Header.Data = DataBuffer; + VarInfo.Header.DataSize = VarDig->DataSize; + + // + // Get detailed information about the variable. + // + Status = ContextIn->GetVariableInfo (&VarInfo); + ASSERT_EFI_ERROR (Status); + + // + // The variable must be validated its digest value to avoid TOCTOU, if it's + // not been cached yet. + // + VarInfo.Header.VariableName = VAR_DIG_NAME (VarDig); + VarInfo.Header.NameSize = VarDig->NameSize; + VarInfo.Header.VendorGuid = &VarDig->VendorGuid; + Status = VerifyVariableDigest (Global, &VarInfo, VarDig); + if (EFI_ERROR (Status)) { + return Status; + } + } + + // + // Decrypt the data, if necessary. + // + Status = ProtectedVariableLibGetDataInternal (Global, &VarInfo); + if (!EFI_ERROR (Status)) { + if (*DataSize < VarInfo.PlainDataSize) { + *DataSize = VarInfo.PlainDataSize; + return EFI_BUFFER_TOO_SMALL; + } + + *DataSize = VarInfo.PlainDataSize; + CopyMem (Data, VarInfo.PlainData, VarInfo.PlainDataSize); + } + + return Status; +} + +/** + + This function is used to enumerate the variables managed by current + ProtectedVariableLib. + + If the VarInfo->StoreIndex is invalid (VAR_INDEX_INVALID), the first variable + with the smallest StoreIndex will be returned. Otherwise, the variable with + StoreIndex just after than the VarInfo->StoreIndex will be returned. + + @param[in,out] VarInfo Pointer to structure containing variable information. + + @retval EFI_SUCCESS Found the specified variable. + @retval EFI_INVALID_PARAMETER VarInfo is NULL. + @retval EFI_NOT_FOUND The specified variable could not be found. + +**/ +STATIC +EFI_STATUS +GetNextVariableInternal ( + IN OUT PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *Found; + + if (VarInfo == NULL) { + ASSERT (VarInfo != NULL); + return EFI_INVALID_PARAMETER; + } + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + Found = FindVariableInternal (Global, VarInfo, TRUE); + if (Found == NULL) { + return EFI_NOT_FOUND; + } + + // + // Return all cached data. + // + VarInfo->Header.VariableName = VAR_DIG_NAME (Found); + VarInfo->Header.VendorGuid = VAR_DIG_GUID (Found); + VarInfo->Header.NameSize = Found->NameSize; + VarInfo->Header.DataSize = Found->DataSize; + VarInfo->Header.Attributes = Found->Attributes; + + VarInfo->PlainDataSize = Found->PlainDataSize; + VarInfo->StoreIndex = Found->StoreIndex; + if (Found->CacheIndex != VAR_INDEX_INVALID) { + VarInfo->Buffer = GET_BUFR (Found->CacheIndex); + } + + VarInfo->Flags.Auth = Found->Flags.Auth; + + return EFI_SUCCESS; +} + +/** + + Find the request variable. + + @param[in, out] VarInfo Pointer to variable data. + + @retval EFI_SUCCESS The variable was read successfully. + @retval EFI_NOT_FOUND The variable could not be found. + @retval EFI_INVALID_PARAMETER Variable info is NULL. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibFind ( + IN OUT PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *Found; + + if (VarInfo == NULL) { + ASSERT (VarInfo != NULL); + return EFI_INVALID_PARAMETER; + } + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + Found = FindVariableInternal (Global, VarInfo, FALSE); + if (Found == NULL) { + return EFI_NOT_FOUND; + } + + // + // Return all cached data. + // + VarInfo->Header.VariableName = VAR_DIG_NAME (Found); + VarInfo->Header.VendorGuid = VAR_DIG_GUID (Found); + VarInfo->Header.NameSize = Found->NameSize; + VarInfo->Header.DataSize = Found->DataSize; + VarInfo->Header.Attributes = Found->Attributes; + + VarInfo->PlainDataSize = Found->PlainDataSize; + VarInfo->StoreIndex = Found->StoreIndex; + if (Found->CacheIndex != VAR_INDEX_INVALID) { + VarInfo->Buffer = GET_BUFR (Found->CacheIndex); + } + + VarInfo->Flags.Auth = Found->Flags.Auth; + + return EFI_SUCCESS; +} + +/** + Return the next variable name and GUID. + + This function is called multiple times to retrieve the VariableName + and VariableGuid of all variables currently available in the system. + On each call, the previous results are passed into the interface, + and, on return, the interface returns the data for the next + interface. When the entire variable list has been returned, + EFI_NOT_FOUND is returned. + + @param VariableNameSize On entry, points to the size of the buffer pointed to by VariableName. + On return, the size of the variable name buffer. + @param VariableName On entry, a pointer to a null-terminated string that is the variable's name. + On return, points to the next variable's null-terminated name string. + @param VariableGuid On entry, a pointer to an EFI_GUID that is the variable's GUID. + On return, a pointer to the next variable's GUID. + + @retval EFI_SUCCESS The variable was read successfully. + @retval EFI_NOT_FOUND The variable could not be found. + @retval EFI_BUFFER_TOO_SMALL The VariableNameSize is too small for the resulting + data. VariableNameSize is updated with the size + required for the specified variable. + @retval EFI_INVALID_PARAMETER VariableName, VariableGuid or + VariableNameSize is NULL. + @retval EFI_DEVICE_ERROR The variable could not be retrieved because of a device error. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibFindNext ( + IN OUT UINTN *VariableNameSize, + IN OUT CHAR16 *VariableName, + IN OUT EFI_GUID *VariableGuid + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO VarInfo; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *VarDig; + UINTN Size; + + if ((VariableNameSize == NULL) || (VariableName == NULL) || (VariableGuid == NULL)) { + ASSERT (VariableNameSize != NULL); + ASSERT (VariableName != NULL); + ASSERT (VariableGuid != NULL); + return EFI_INVALID_PARAMETER; + } + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + return Status; + } + + SetMem (&VarInfo, sizeof (VarInfo), 0); + Size = StrSize (VariableName); + + if (Size <= 2) { + VarDig = VAR_DIG_PTR (Global->VariableDigests); + } else { + VarInfo.Header.VariableName = VariableName; + VarInfo.Header.NameSize = Size; + VarInfo.Header.VendorGuid = VariableGuid; + + VarInfo.StoreIndex = VAR_INDEX_INVALID; + + VarDig = FindVariableInternal (Global, &VarInfo, TRUE); + } + + if (VarDig == NULL) { + return EFI_NOT_FOUND; + } + + if (VarDig->NameSize > *VariableNameSize) { + return EFI_BUFFER_TOO_SMALL; + } + + CopyMem (VariableName, VAR_DIG_NAME (VarDig), VarDig->NameSize); + CopyGuid (VariableGuid, &VarDig->VendorGuid); + *VariableNameSize = VarInfo.Header.NameSize; + + return EFI_SUCCESS; +} + +/** + + Return the next variable name and GUID. + + @param[in, out] VarInfo Pointer to variable data. + + @retval EFI_SUCCESS The variable was read successfully. + @retval EFI_INVALID_PARAMETER VarInfo is NULL. + @retval EFI_NOT_FOUND The specified variable could not be found. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibFindNextEx ( + IN OUT PROTECTED_VARIABLE_INFO *VarInfo + ) +{ + return GetNextVariableInternal (VarInfo); +} + +/** + + Return the max count of a variable. + + @return max count of a variable. + +**/ +UINTN +ProtectedVariableLibGetMaxVariablesCount ( + VOID + ) +{ + PROTECTED_VARIABLE_GLOBAL *Global; + PROTECTED_VARIABLE_INFO VarInfo; + VARIABLE_DIGEST *VarDig; + EFI_STATUS Status; + UINTN Count; + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return 0; + } + + Count = 0; + ZeroMem (&VarInfo, sizeof (VarInfo)); + + // + // Start with first entry + // + VarDig = VAR_DIG_PTR (Global->VariableDigests); + VarInfo.Header.VariableName = VAR_DIG_NAME (VarDig); + VarInfo.Header.VendorGuid = VAR_DIG_GUID (VarDig); + VarInfo.StoreIndex = VarDig->StoreIndex; + + do { + VarInfo.Buffer = NULL; + Status = ProtectedVariableLibFindNextEx (&VarInfo); + if (EFI_ERROR (Status)) { + return Count; + } + + Count++; + } while (TRUE); +} + +/** + The function is called by PerformQuickSort to sort. + + @param[in] Left The pointer to first buffer. + @param[in] Right The pointer to second buffer. + + @retval 0 Buffer1 equal to Buffer2. + @return < 0 Buffer1 is less than Buffer2. + @return > 0 Buffer1 is greater than Buffer2. + +**/ +INTN +EFIAPI +CompareStoreIndex ( + IN CONST VOID *Left, + IN CONST VOID *Right + ) +{ + EFI_PHYSICAL_ADDRESS StoreIndex1; + EFI_PHYSICAL_ADDRESS StoreIndex2; + + StoreIndex1 = (*(EFI_PHYSICAL_ADDRESS *)Left); + StoreIndex2 = (*(EFI_PHYSICAL_ADDRESS *)Right); + + if (StoreIndex1 == StoreIndex2) { + return (0); + } + + if (StoreIndex1 < StoreIndex2) { + return (-1); + } + + return (1); +} + +/** + Refresh variable information changed by variable service. + + @param Buffer Pointer to a pointer of buffer. + @param NumElements Pointer to number of elements in list. + + + @return EFI_SUCCESS Successfully retrieved sorted list. + @return others Unsuccessful. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibGetSortedList ( + IN OUT EFI_PHYSICAL_ADDRESS **Buffer, + IN OUT UINTN *NumElements + ) +{ + EFI_STATUS Status; + UINTN Count; + UINTN StoreIndexTableSize; + EFI_PHYSICAL_ADDRESS *StoreIndexTable; + PROTECTED_VARIABLE_INFO VarInfo; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *VarDig; + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + Count = 0; + ZeroMem (&VarInfo, sizeof (VarInfo)); + StoreIndexTableSize = ProtectedVariableLibGetMaxVariablesCount (); + StoreIndexTable = AllocateZeroPool (sizeof (EFI_PHYSICAL_ADDRESS) * StoreIndexTableSize); + + // + // Start with first entry + // + VarDig = VAR_DIG_PTR (Global->VariableDigests); + VarInfo.Header.VariableName = VAR_DIG_NAME (VarDig); + VarInfo.Header.VendorGuid = VAR_DIG_GUID (VarDig); + VarInfo.StoreIndex = VarDig->StoreIndex; + StoreIndexTable[Count] = VarInfo.StoreIndex; + Count++; + + // + // Populate the un-sorted table + // + do { + VarInfo.Buffer = NULL; + Status = ProtectedVariableLibFindNextEx (&VarInfo); + if (EFI_ERROR (Status)) { + break; + } + + StoreIndexTable[Count] = VarInfo.StoreIndex; + Count++; + } while (TRUE); + + PerformQuickSort ( + StoreIndexTable, + Count, + sizeof (EFI_PHYSICAL_ADDRESS), + (SORT_COMPARE)CompareStoreIndex + ); + + *Buffer = StoreIndexTable; + *NumElements = Count; + + return EFI_SUCCESS; +} diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableDxe.c b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableDxe.c new file mode 100644 index 000000000000..94df21eacf25 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableDxe.c @@ -0,0 +1,163 @@ +/** @file + Implemention of ProtectedVariableLib for SMM variable services. + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +#include "Library/UefiBootServicesTableLib.h" +#include "Library/MemoryAllocationLib.h" + +#include "ProtectedVariableInternal.h" + +PROTECTED_VARIABLE_CONTEXT_IN mVariableContextIn = { + PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION, + sizeof (PROTECTED_VARIABLE_CONTEXT_IN), + 0, + FromSmmModule, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +PROTECTED_VARIABLE_GLOBAL mProtectedVariableGlobal = { + PROTECTED_VARIABLE_CONTEXT_OUT_STRUCT_VERSION, + sizeof (PROTECTED_VARIABLE_GLOBAL), + { 0 }, + { 0 }, + 0, + 0, + 0, + 0, + 0, + 0, + { 0, 0, 0 }, + 0, + 0, + { 0, 0, 0, 0, 0, 0} +}; + +/** + Fix incorrect state of MetaDataHmacVariable before any variable update. + + @param[in] Event The event that occurred + @param[in] Context For EFI compatibility. Not used. + +**/ +VOID +EFIAPI +VariableWriteProtocolCallback ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + + // + // Fix incorrect state of MetaDataHmacVariable before any variable update. + // This has to be done here due to the fact that this operation needs to + // update NV storage but the FVB and FTW protocol might not be ready during + // ProtectedVariableLibInitialize(). + // + Status = FixupHmacVariable (); + ASSERT_EFI_ERROR (Status); + + Status = ProtectedVariableLibWriteInit (); + ASSERT_EFI_ERROR (Status); + + gBS->CloseEvent (Event); +} + +/** + + Initialization for protected variable services. + + If this initialization failed upon any error, the whole variable services + should not be used. A system reset might be needed to re-construct NV + variable storage to be the default state. + + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + + @retval EFI_SUCCESS Protected variable services are ready. + @retval EFI_INVALID_PARAMETER If ContextIn == NULL or something missing or + mismatching in the content in ContextIn. + @retval EFI_COMPROMISED_DATA If failed to check integrity of protected variables. + @retval EFI_OUT_OF_RESOURCES Fail to allocate enough resource. + @retval EFI_UNSUPPORTED Unsupported to process protected variable. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibInitialize ( + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_CONTEXT_IN *ProtectedVarContext; + PROTECTED_VARIABLE_GLOBAL *OldGlobal; + PROTECTED_VARIABLE_GLOBAL *NewGlobal; + VOID *VarWriteReg; + + if ( (ContextIn == NULL) + || (ContextIn->StructVersion != PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION) + || (ContextIn->StructSize != sizeof (PROTECTED_VARIABLE_CONTEXT_IN)) + || (ContextIn->GetVariableInfo == NULL) + || (ContextIn->GetNextVariableInfo == NULL) + || (ContextIn->UpdateVariableStore == NULL) + || (ContextIn->UpdateVariable == NULL)) + { + ASSERT (ContextIn != NULL); + ASSERT (ContextIn->StructVersion == PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION); + ASSERT (ContextIn->StructSize == sizeof (PROTECTED_VARIABLE_CONTEXT_IN)); + ASSERT (ContextIn->GetVariableInfo != NULL); + ASSERT (ContextIn->GetNextVariableInfo != NULL); + ASSERT (ContextIn->UpdateVariableStore != NULL); + return EFI_INVALID_PARAMETER; + } + + GetProtectedVariableGlobal (&NewGlobal); + ProtectedVarContext = GET_CNTX (NewGlobal); + CopyMem (ProtectedVarContext, ContextIn, sizeof (mVariableContextIn)); + ProtectedVarContext->VariableServiceUser = FromSmmModule; + + // + // Get root key and HMAC key from HOB created by PEI variable driver. + // + Status = GetProtectedVariableGlobalFromHob (&OldGlobal); + ASSERT_EFI_ERROR (Status); + + CopyMem ((VOID *)NewGlobal, (CONST VOID *)OldGlobal, sizeof (*OldGlobal)); + + // + // The keys must not be available outside SMM. + // + if (ProtectedVarContext->VariableServiceUser == FromSmmModule) { + ZeroMem (OldGlobal->RootKey, sizeof (OldGlobal->RootKey)); + ZeroMem (OldGlobal->MetaDataHmacKey, sizeof (OldGlobal->MetaDataHmacKey)); + } + + // + // Register variable write protocol notify function used to fix any + // inconsistency in MetaDataHmacVariable before the first variable write + // operation. + // + NewGlobal->Flags.WriteInit = FALSE; + NewGlobal->Flags.WriteReady = FALSE; + + EfiCreateProtocolNotifyEvent ( + &gEfiVariableWriteArchProtocolGuid, + TPL_CALLBACK, + VariableWriteProtocolCallback, + NULL, + &VarWriteReg + ); + + return EFI_SUCCESS; +} diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariablePei.c b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariablePei.c new file mode 100644 index 000000000000..91d71a1e8525 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariablePei.c @@ -0,0 +1,1331 @@ +/** @file + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "ProtectedVariableInternal.h" + +/** + Function allocates a global buffer. + + This function allocates a buffer with the specified size. + + @param[in] Size Size of buffer to allocate. + @param[in] AllocatePage Whether to allocate pages. + + @retval Buffer Pointer to the Buffer allocated. + @retval NULL if no Buffer was found. + +**/ +VOID * +AllocateGlobalBuffer ( + IN UINT32 Size, + IN BOOLEAN AllocatePage + ) +{ + VOID *Buffer; + EFI_HOB_MEMORY_ALLOCATION *MemoryAllocationHob; + EFI_PEI_HOB_POINTERS Hob; + + Buffer = NULL; + if (!AllocatePage) { + Buffer = BuildGuidHob (&gEdkiiProtectedVariableGlobalGuid, Size); + } + + if (Buffer == NULL) { + // + // Use the AllocatePages() to get over size limit of general GUID-ed HOB. + // + Buffer = AllocatePages (EFI_SIZE_TO_PAGES (Size)); + if (Buffer == NULL) { + ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES); + return NULL; + } + + // + // Mark the HOB holding the pages just allocated so that it can be + // identified later. + // + MemoryAllocationHob = NULL; + Hob.Raw = GetFirstHob (EFI_HOB_TYPE_MEMORY_ALLOCATION); + while (Hob.Raw != NULL) { + MemoryAllocationHob = (EFI_HOB_MEMORY_ALLOCATION *)Hob.Raw; + if ((UINTN)Buffer == (UINTN)MemoryAllocationHob->AllocDescriptor.MemoryBaseAddress) { + CopyGuid ( + &MemoryAllocationHob->AllocDescriptor.Name, + &gEdkiiProtectedVariableGlobalGuid + ); + break; + } + + Hob.Raw = GET_NEXT_HOB (Hob); + Hob.Raw = GetNextHob (EFI_HOB_TYPE_MEMORY_ALLOCATION, Hob.Raw); + } + } + + return Buffer; +} + +/** + Callback use to re-verify all variables and cache them in memory. + + @param[in] PeiServices General purpose services available to every PEIM. + @param[in] NotifyDescriptor The notification structure this PEIM registered on install. + @param[in] Ppi The memory discovered PPI. Not used. + + @retval EFI_SUCCESS The function completed successfully. + @retval others There's error in MP initialization. +**/ +EFI_STATUS +EFIAPI +MemoryDiscoveredPpiNotifyCallback ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, + IN VOID *Ppi + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *VarDig; + PROTECTED_VARIABLE_INFO VarInfo; + VOID *Buffer; + UINT32 VarSize; + INTN Result; + + Status = GetProtectedVariableGlobal (&Global); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + ContextIn = GET_CNTX (Global); + + VarSize = Global->VariableNumber * MAX_VARIABLE_SIZE; + Buffer = AllocateGlobalBuffer (VarSize, TRUE); + + // + // It's safe to store the pointer in Global->VariableCache, which has been + // cleared by above calling of GetProtectedVariableGlobal(). + // + Global->VariableCache = GET_ADRS (Buffer); + Global->VariableCacheSize = EFI_SIZE_TO_PAGES (VarSize); + + // + // Traverse all valid variables. + // + VarDig = VAR_DIG_PTR (Global->VariableDigests); + while (VarDig != NULL) { + if (VarDig->CacheIndex == VAR_INDEX_INVALID) { + ASSERT (VarDig->StoreIndex != VAR_INDEX_INVALID); + + VarSize = VARIABLE_HEADER_SIZE (Global->Flags.Auth); + VarSize += VarDig->NameSize + GET_PAD_SIZE (VarDig->NameSize); + VarSize += VarDig->DataSize + GET_PAD_SIZE (VarDig->DataSize); + VarSize = HEADER_ALIGN (VarSize); + + // + // Note the variable might be in unconsecutive space. + // + ZeroMem (&VarInfo, sizeof (VarInfo)); + VarInfo.StoreIndex = VarDig->StoreIndex; + VarInfo.Buffer = Buffer; + VarInfo.Flags.Auth = VarDig->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&VarInfo); + ASSERT_EFI_ERROR (Status); + // + // VerifyVariableDigest() refers to CipherData for raw data. + // + VarInfo.CipherData = VarInfo.Header.Data; + VarInfo.CipherDataSize = (UINT32)VarInfo.Header.DataSize; + + // + // Make sure that the cached copy is not compromised. + // + Status = VerifyVariableDigest (Global, &VarInfo, VarDig); + if (EFI_ERROR (Status)) { + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_UNRECOVERED, + (PcdGet32 (PcdStatusCodeVariableIntegrity) | (Status & 0xFF)) + ); + ASSERT_EFI_ERROR (Status); + CpuDeadLoop (); + } + + // + // Simply use the cache address as CacheIndex of the variable. + // + VarDig->CacheIndex = GET_ADRS (Buffer); + Buffer = (UINT8 *)Buffer + MAX_VARIABLE_SIZE; + } else { + Result = StrnCmp ( + VAR_DIG_NAME (VarDig), + METADATA_HMAC_VARIABLE_NAME, + METADATA_HMAC_VARIABLE_NAME_SIZE + ); + if (Result == 0) { + CopyMem ( + Buffer, + GET_BUFR (Global->GlobalSelf + (Global->StructSize - GetMetaDataHmacVarSize (Global->Flags.Auth))), + GetMetaDataHmacVarSize (Global->Flags.Auth) + ); + + // + // Simply use the cache address as CacheIndex of the variable. + // + VarDig->CacheIndex = GET_ADRS (Buffer); + Buffer = (UINT8 *)Buffer + MAX_VARIABLE_SIZE; + } + } + + VarDig = VAR_DIG_NEXT (VarDig); + } + + return EFI_SUCCESS; +} + +/** + Callback use to perform variable integrity check. + + @param[in] PeiServices General purpose services available to every PEIM. + @param[in] NotifyDescriptor The notification structure this PEIM registered on install. + @param[in] Ppi The memory discovered PPI. Not used. + + @retval EFI_SUCCESS The function completed successfully. + @retval others There's error in MP initialization. +**/ +EFI_STATUS +EFIAPI +VariableStoreDiscoveredPpiNotifyCallback ( + IN EFI_PEI_SERVICES **PeiServices, + IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, + IN VOID *Ppi + ) +{ + EFI_STATUS Status; + EFI_HOB_GUID_TYPE *GuidHob; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + + GuidHob = GetFirstGuidHob (&gEdkiiProtectedVariableContextGuid); + if (GuidHob != NULL) { + ContextIn = (PROTECTED_VARIABLE_CONTEXT_IN *)GET_GUID_HOB_DATA (GuidHob); + } else { + ASSERT (GuidHob == NULL); + } + + Status = ContextIn->IsHobVariableStoreAvailable (); + + if (Status == EFI_NOT_READY) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + Status = PerformVariableIntegrityCheck (ContextIn); + + return Status; +} + +EFI_PEI_NOTIFY_DESCRIPTOR mPostMemNotifyList[] = { + { + (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEfiPeiMemoryDiscoveredPpiGuid, + MemoryDiscoveredPpiNotifyCallback + } +}; + +EFI_PEI_NOTIFY_DESCRIPTOR mVariableStoreNotifyList[] = { + { + (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), + &gEfiPeiVariableStoreDiscoveredPpiGuid, + VariableStoreDiscoveredPpiNotifyCallback + } +}; + +/** + + Get global data structure used to process protected variable. + + @param[out] Global Pointer to global configuration data. + + @retval EFI_SUCCESS Get requested structure successfully. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableGlobal ( + OUT PROTECTED_VARIABLE_GLOBAL **Global OPTIONAL + ) +{ + return GetProtectedVariableGlobalFromHob (Global); +} + +/** + + Get context data structure used to process protected variable. + + @param[out] ContextIn Pointer to context provided by variable runtime services. + + @retval EFI_SUCCESS Get requested structure successfully. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableContext ( + PROTECTED_VARIABLE_CONTEXT_IN **ContextIn OPTIONAL + ) +{ + EFI_HOB_GUID_TYPE *GuidHob; + + GuidHob = GetFirstGuidHob (&gEdkiiProtectedVariableContextGuid); + if (GuidHob != NULL) { + *ContextIn = (PROTECTED_VARIABLE_CONTEXT_IN *)GET_GUID_HOB_DATA (GuidHob); + return EFI_SUCCESS; + } + + return EFI_NOT_FOUND; +} + +/** + Verify the HMAC value stored in MetaDataHmacVar against all valid and + protected variables in storage. + + @param[in,out] Global Pointer to global configuration data. + + @retval EFI_SUCCESS The HMAC value matches. + @retval EFI_ABORTED Error in HMAC value calculation. + @retval EFI_VOLUME_CORRUPTED Inconsistency found in NV variable storage. + @retval EFI_COMPROMISED_DATA The HMAC value doesn't match. + +**/ +EFI_STATUS +VerifyMetaDataHmac ( + IN OUT PROTECTED_VARIABLE_GLOBAL *Global + ) +{ + EFI_STATUS Status; + VARIABLE_DIGEST *VariableDig; + UINT32 Counter1; + UINT32 Counter2; + VOID *Hmac1; + VOID *Hmac2; + UINT8 HmacVal1[METADATA_HMAC_SIZE]; + UINT8 HmacVal2[METADATA_HMAC_SIZE]; + + Hmac1 = NULL; + Hmac2 = HmacSha256New (); + if (Hmac2 == NULL) { + ASSERT (Hmac2 != NULL); + return EFI_OUT_OF_RESOURCES; + } + + if (!HmacSha256SetKey (Hmac2, Global->MetaDataHmacKey, sizeof (Global->MetaDataHmacKey))) { + ASSERT (FALSE); + Status = EFI_ABORTED; + goto Done; + } + + // + // Retrieve the RPMC counter value. + // + Status = RequestMonotonicCounter (RPMC_COUNTER_1, &Counter1); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto Done; + } + + Status = RequestMonotonicCounter (RPMC_COUNTER_2, &Counter2); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto Done; + } + + // + // Counter1 must be either equal to Counter2 or just one step ahead of Counter2. + // + if ((Counter1 > Counter2) && ((Counter1 - Counter2) > 1)) { + Status = EFI_COMPROMISED_DATA; + goto Done; + } + + VariableDig = VAR_DIG_PTR (Global->VariableDigests); + while (VariableDig != NULL) { + // + // Only take valid protected variables into account. + // + if (VariableDig->Flags.Protected && VariableDig->Flags.Valid) { + if (!HmacSha256Update ( + Hmac2, + VAR_DIG_VALUE (VariableDig), + VariableDig->DigestSize + )) + { + ASSERT (FALSE); + Status = EFI_ABORTED; + goto Done; + } + } + + VariableDig = VAR_DIG_NEXT (VariableDig); + } + + // + // If two MetaDataHmacVariable were found, check which one is valid. We might + // need two HMAC values to check against: one for Counter1, one for Counter2. + // + if ( (Global->Unprotected[IndexHmacAdded] != VAR_INDEX_INVALID) + && (Global->Unprotected[IndexHmacInDel] != VAR_INDEX_INVALID) + && (Counter1 != Counter2)) + { + // + // Might need to check Counter1. There must be something wrong in last boot. + // + Hmac1 = HmacSha256New (); + if ((Hmac1 == NULL) || !HmacSha256Duplicate (Hmac2, Hmac1)) { + ASSERT (FALSE); + Status = EFI_OUT_OF_RESOURCES; + goto Done; + } + + if ( !HmacSha256Update (Hmac1, &Counter1, sizeof (Counter1)) + || !HmacSha256Final (Hmac1, HmacVal1)) + { + ASSERT (FALSE); + Status = EFI_ABORTED; + goto Done; + } + } + + // + // Always check Counter2. + // + if ( !HmacSha256Update (Hmac2, &Counter2, sizeof (Counter2)) + || !HmacSha256Final (Hmac2, HmacVal2)) + { + ASSERT (FALSE); + Status = EFI_ABORTED; + goto Done; + } + + // + // When writing (update or add) a variable, there must be following steps + // performed: + // + // A - Increment Counter1 + // B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state of VAR_ADDED + // D - Write the new protected variable + // E - Increment Counter2 + // F - Mark old MetaDataHmacVar as VAR_DELETED + // + Status = EFI_COMPROMISED_DATA; + if ( (Global->Unprotected[IndexHmacAdded] != VAR_INDEX_INVALID) + && (Global->Unprotected[IndexHmacInDel] == VAR_INDEX_INVALID)) + { + if (CompareMem ( + VAR_DIG_VALUE (VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])), + HmacVal2, + METADATA_HMAC_SIZE + ) == 0) + { + // + // + // + A - Increment Counter1 + // B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state of VAR_ADDED + // D - Write the new protected variable + // E - Increment Counter2 + // F - Mark old MetaDataHmacVar as VAR_DELETED + // + // or, + // + // + A - Increment Counter1 + // + B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // + C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state of VAR_ADDED + // + D - Write the new protected variable + // + E - Increment Counter2 + // + F - Mark old MetaDataHmacVar as VAR_DELETED + // + Status = EFI_SUCCESS; + + VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])->Flags.Valid = TRUE; + } + } else if ( (Global->Unprotected[IndexHmacAdded] == VAR_INDEX_INVALID) + && (Global->Unprotected[IndexHmacInDel] != VAR_INDEX_INVALID)) + { + if (CompareMem ( + VAR_DIG_VALUE (VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel])), + HmacVal2, + METADATA_HMAC_SIZE + ) == 0) + { + // + // + A - Increment Counter1 + // + B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state of VAR_ADDED + // D - Write the new protected variable + // E - Increment Counter2 + // F - Mark old MetaDataHmacVar as VAR_DELETED + // + Status = EFI_SUCCESS; + + VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel])->Flags.Valid = TRUE; + } + } else if ( (Global->Unprotected[IndexHmacAdded] != VAR_INDEX_INVALID) + && (Global->Unprotected[IndexHmacInDel] != VAR_INDEX_INVALID)) + { + if (Counter1 > Counter2) { + if (CompareMem ( + VAR_DIG_VALUE (VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel])), + HmacVal2, + METADATA_HMAC_SIZE + ) == 0) + { + // + // + A - Increment Counter1 + // + B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // + C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state VAR_ADDED + // D - Write the new protected variable + // E - Increment Counter2 + // F - Mark old MetaDataHmacVar as VAR_DELETED + // + Status = EFI_SUCCESS; + + VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])->Flags.Valid = FALSE; + VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel])->Flags.Valid = TRUE; + } else if (CompareMem ( + VAR_DIG_VALUE (VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])), + HmacVal1, + METADATA_HMAC_SIZE + ) == 0) + { + // + // + A - Increment Counter1 + // + B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // + C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state of VAR_ADDED + // + D - Write the new protected variable + // E - Increment Counter2 + // F - Mark old MetaDataHmacVar as VAR_DELETED + // + Status = EFI_SUCCESS; + + VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])->Flags.Valid = TRUE; + VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel])->Flags.Valid = FALSE; + } + } else { + if (CompareMem ( + VAR_DIG_VALUE (VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])), + HmacVal2, + METADATA_HMAC_SIZE + ) == 0) + { + // + // + A - Increment Counter1 + // + B - Mark old MetaDataHmacVar as VAR_IN_DELETED_TRANSITION + // + C - Calculate new HMAC value against Counter2+1, + // and force-add a new MetaDataHmacVar with state of VAR_ADDED + // + D - Write the new protected variable + // + E - Increment Counter2 + // F - Mark old MetaDataHmacVar as VAR_DELETED + // + Status = EFI_SUCCESS; + + VAR_DIG_PTR (Global->Unprotected[IndexHmacAdded])->Flags.Valid = TRUE; + VAR_DIG_PTR (Global->Unprotected[IndexHmacInDel])->Flags.Valid = FALSE; + } + } + } else { + // + // There must be logic error or variable written to storage skipped + // the protected variable service, if code reaches here. + // + ASSERT (FALSE); + } + +Done: + if (Hmac1 != NULL) { + HmacSha256Free (Hmac1); + } + + if (Hmac2 != NULL) { + HmacSha256Free (Hmac2); + } + + return Status; +} + +/** + Collect variable digest information. + + This information is collected to be used to for integrity check. + + @param[in] Global Pointer to global configuration data. + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + @param[in, out] DigestBuffer Base address of digest of each variable. + @param[out] DigestBufferSize Digest size of one variable if DigestBuffer is NULL. + Size of DigestBuffer if DigestBuffer is NOT NULL. + @param[out] VariableNumber Number of valid variables. + + @retval EFI_SUCCESS Successfully retreived variable digest. + @retval EFI_INVALID_PARAMETER One ore more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES Unable to allocate memory. + @retval EFI_BUFFER_TOO_SMALL The DigestBufferSize pass in is too small. + +**/ +EFI_STATUS +CollectVariableDigestInfo ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn, + IN OUT VOID *DigestBuffer OPTIONAL, + OUT UINT32 *DigestBufferSize OPTIONAL, + OUT UINT32 *VariableNumber OPTIONAL + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO VarInfo; + UINT32 VarNum; + UINT32 DigSize; + VARIABLE_DIGEST *VarDig; + EFI_TIME TimeStamp; + UNPROTECTED_VARIABLE_INDEX VarIndex; + + // + // This function might be called before Global is initialized. In that case, + // Global must be NULL but not ContextIn. + // + if ((Global == NULL) && (ContextIn == NULL)) { + ASSERT (Global != NULL || ContextIn != NULL); + return EFI_INVALID_PARAMETER; + } + + if ((Global == NULL) && (DigestBuffer != NULL)) { + ASSERT (Global != NULL && DigestBuffer != NULL); + return EFI_INVALID_PARAMETER; + } + + if ( (DigestBuffer != NULL) + && ((DigestBufferSize == NULL) || (*DigestBufferSize == 0))) + { + ASSERT ( + DigestBuffer != NULL + && DigestBufferSize != NULL && *DigestBufferSize > 0 + ); + return EFI_INVALID_PARAMETER; + } + + if ((Global != NULL) && (ContextIn == NULL)) { + ContextIn = GET_CNTX (Global); + } + + DigSize = 0; + VarNum = 0; + VarDig = NULL; + + ZeroMem (&VarInfo, sizeof (VarInfo)); + VarInfo.StoreIndex = VAR_INDEX_INVALID; // To get the first variable. + + if ((Global != NULL) && + (Global->VariableCache != 0) && + (Global->VariableCacheSize > 0)) + { + // + // Use the variable cache to hold a copy of one variable. + // + VarInfo.Buffer = GET_BUFR (Global->VariableCache); + } else { + // + // Allocate a buffer to hold a copy of one variable + // + VarInfo.Buffer = AllocatePages (EFI_SIZE_TO_PAGES (MAX_VARIABLE_SIZE)); + if (VarInfo.Buffer == NULL) { + ASSERT (VarInfo.Buffer != NULL); + return EFI_OUT_OF_RESOURCES; + } + } + + if ((DigestBuffer != NULL) && (*DigestBufferSize > 0)) { + VarDig = DigestBuffer; + } + + while (TRUE) { + if (VarDig != NULL) { + if (DigSize >= (*DigestBufferSize)) { + // + // Out of buffer. + // + break; + } + + VarInfo.Header.VendorGuid = &VarDig->VendorGuid; + VarInfo.Header.VariableName = VAR_DIG_NAME (VarDig); + VarInfo.Header.NameSize = (UINTN)DigestBuffer + (UINTN)*DigestBufferSize + - (UINTN)VarInfo.Header.VariableName; + VarInfo.Header.TimeStamp = &TimeStamp; + VarInfo.Header.Data = NULL; + } else { + ZeroMem ((VOID *)&VarInfo.Header, sizeof (VarInfo.Header)); + } + + Status = ContextIn->GetNextVariableInfo (&VarInfo); + if (EFI_ERROR (Status)) { + break; + } + + // + // Skip deleted variables. + // + if ( (VarInfo.Header.State != VAR_ADDED) + && (VarInfo.Header.State != (VAR_ADDED & VAR_IN_DELETED_TRANSITION))) + { + continue; + } + + if (Global != NULL) { + Global->Flags.Auth &= VarInfo.Flags.Auth; + } + + VarNum += 1; + DigSize += (UINT32)(sizeof (VARIABLE_DIGEST) + + VarInfo.Header.NameSize + + METADATA_HMAC_SIZE); + if ((DigestBuffer != NULL) && (DigSize > *DigestBufferSize)) { + ASSERT (DigSize <= *DigestBufferSize); + return EFI_BUFFER_TOO_SMALL; + } + + if (VarDig != NULL) { + VarDig->Prev = 0; + VarDig->Next = 0; + VarDig->State = VarInfo.Header.State; + VarDig->Attributes = VarInfo.Header.Attributes; + VarDig->DataSize = (UINT32)VarInfo.Header.DataSize; + VarDig->NameSize = (UINT16)VarInfo.Header.NameSize; + VarDig->DigestSize = METADATA_HMAC_SIZE; + VarDig->StoreIndex = VarInfo.StoreIndex; + + if ((VarInfo.Buffer != NULL) && ((UINTN)VarInfo.Buffer != Global->VariableCache)) { + VarDig->CacheIndex = GET_ADRS (VarInfo.Buffer); + } else { + VarDig->CacheIndex = VAR_INDEX_INVALID; + } + + VarDig->Flags.Auth = VarInfo.Flags.Auth; + VarDig->Flags.Valid = TRUE; + + VarIndex = CheckKnownUnprotectedVariable (Global, &VarInfo); + if (VarIndex >= UnprotectedVarIndexMax) { + // + // Check information relating to encryption, if enabled. + // + VarDig->Flags.Encrypted = FALSE; + if ((VarInfo.Header.Data != NULL) && (VarInfo.Header.DataSize > 0)) { + VarInfo.CipherData = NULL; + VarInfo.CipherDataSize = 0; + VarInfo.PlainData = NULL; + VarInfo.PlainDataSize = 0; + Status = GetCipherDataInfo (&VarInfo); + if (!EFI_ERROR (Status)) { + // + // Discovered encrypted variable mark variable to be + // encrypted on the next SetVariable() operation + // + VarDig->Flags.Encrypted = PcdGetBool (PcdProtectedVariableConfidentiality); + } else { + VarInfo.PlainData = VarInfo.Header.Data; + VarInfo.PlainDataSize = (UINT32)VarInfo.Header.DataSize; + VarInfo.CipherDataType = 0; + VarInfo.CipherHeaderSize = 0; + if (Status == EFI_NOT_FOUND) { + // + // Found variable that is not encrypted mark variable to be + // encrypted on the next SetVariable() operation + // + VarDig->Flags.Encrypted = PcdGetBool (PcdProtectedVariableConfidentiality); + } + } + } + + // + // Variable is protected + // + VarDig->Flags.Protected = PcdGetBool (PcdProtectedVariableIntegrity); + VarDig->PlainDataSize = VarInfo.PlainDataSize; + + // + // Calculate digest only for protected variable. + // + Status = GetVariableDigest (Global, &VarInfo, VAR_DIG_VALUE (VarDig)); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Keep the VarDig in an ordered list. + // + InsertVariableDigestNode (Global, VarDig, CompareVariableDigestInfo); + } else { + VarDig->Flags.Protected = FALSE; + VarDig->Flags.Encrypted = FALSE; + VarDig->PlainDataSize = VarDig->DataSize; + + // + // Make use of VARIABLE_DIGEST->DigestValue to cache HMAC value from + // MetaDataHmacVar, which doesn't need a digest value (only protected + // variables need it for integrity check). + // + if ((VarIndex == IndexHmacInDel) || (VarIndex == IndexHmacAdded)) { + if (VarDig->State == VAR_ADDED) { + VarIndex = IndexHmacAdded; + } else { + VarIndex = IndexHmacInDel; + } + } + + Global->Unprotected[VarIndex] = VAR_DIG_ADR (VarDig); + + if ((VarInfo.Header.Data != NULL) && (VarDig->DataSize <= VarDig->DigestSize)) { + CopyMem (VAR_DIG_VALUE (VarDig), VarInfo.Header.Data, VarDig->DataSize); + } + + // + // Don't add the VarDig for MetaDataHmacVar into the linked list now. + // Do it after the HMAC has been validated. + // + if ((VarIndex != IndexHmacInDel) || (VarIndex != IndexHmacAdded)) { + InsertVariableDigestNode (Global, VarDig, CompareVariableDigestInfo); + } + } + + VarDig = (VARIABLE_DIGEST *)((UINTN)VarDig + VAR_DIG_END (VarDig)); + } + } + + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + return Status; + } + + if (DigestBufferSize != NULL) { + *DigestBufferSize = DigSize; + } + + if (VariableNumber != NULL) { + *VariableNumber = VarNum; + } + + if ((Global == NULL) && (VarInfo.Buffer != NULL)) { + // + // Free Buffer + // + FreePages (VarInfo.Buffer, EFI_SIZE_TO_PAGES (MAX_VARIABLE_SIZE)); + } + + return EFI_SUCCESS; +} + +/** + + Perform for protected variable integrity check. + + If this initialization failed upon any error, the whole variable services + should not be used. A system reset might be needed to re-construct NV + variable storage to be the default state. + + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + + @retval EFI_SUCCESS Protected variable services are ready. + @retval EFI_INVALID_PARAMETER If ContextIn == NULL or something missing or + mismatching in the content in ContextIn. + @retval EFI_COMPROMISED_DATA If failed to check integrity of protected variables. + @retval EFI_OUT_OF_RESOURCES Fail to allocate enough resource. + @retval EFI_UNSUPPORTED Unsupported to process protected variable. + +**/ +EFI_STATUS +EFIAPI +PerformVariableIntegrityCheck ( + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn + ) +{ + EFI_STATUS Status; + UINT32 HobDataSize; + UINT32 VarNumber; + VOID *Buffer; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *DigBuffer; + UINT32 DigBufferSize; + UINT32 HmacMetaDataSize; + UINTN Index; + BOOLEAN PreviousKey; + EFI_HOB_GUID_TYPE *GuidHob; + + if ((ContextIn == NULL) || (ContextIn->GetNextVariableInfo == NULL)) { + ASSERT (ContextIn != NULL); + ASSERT (ContextIn->GetNextVariableInfo != NULL); + return EFI_INVALID_PARAMETER; + } + + ContextIn->StructSize = (ContextIn->StructSize == 0) ? sizeof (*ContextIn) + : ContextIn->StructSize; + + // + // Enumerate all variables first to collect info for resource allocation. + // + DigBufferSize = 0; + Status = CollectVariableDigestInfo ( + NULL, + ContextIn, + NULL, + &DigBufferSize, + &VarNumber + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return EFI_VOLUME_CORRUPTED; + } + + // + // Allocate buffer for Global. Memory layout: + // + // Global + // Digest context + // Variable Digest List + // HmacMetaData + // + // To save precious NEM space of processor, variable cache will not be + // allocated at this point until physical memory is ready for use. + // + HmacMetaDataSize = (UINT32)GetMetaDataHmacVarSize (TRUE); + HobDataSize = sizeof (PROTECTED_VARIABLE_GLOBAL) + + (UINT32)DIGEST_CONTEXT_SIZE + + DigBufferSize + + HmacMetaDataSize; + Buffer = AllocateGlobalBuffer (HobDataSize, FALSE); + if (Buffer == NULL) { + ASSERT (Buffer != NULL); + return EFI_OUT_OF_RESOURCES; + } + + Global = (PROTECTED_VARIABLE_GLOBAL *)((UINTN)Buffer); + Global->DigestContext = GET_ADRS (Global + 1); + + if (DigBufferSize > 0) { + DigBuffer = (VARIABLE_DIGEST *)(UINTN)(Global->DigestContext + DIGEST_CONTEXT_SIZE); + ZeroMem (DigBuffer, DigBufferSize); + } else { + DigBuffer = NULL; + } + + // + // Keep a copy of ContextIn in HOB for later uses. + // + Global->GlobalSelf = GET_ADRS (Global); + Global->ContextIn = GET_ADRS (ContextIn); + + Global->StructVersion = PROTECTED_VARIABLE_CONTEXT_OUT_STRUCT_VERSION; + Global->StructSize = HobDataSize; + + Global->VariableNumber = VarNumber; + Global->VariableDigests = 0; + + Global->Flags.Auth = TRUE; + Global->Flags.WriteInit = FALSE; + Global->Flags.WriteReady = FALSE; + + GuidHob = GetFirstGuidHob (&gEfiAuthenticatedVariableGuid); + if (GuidHob == NULL) { + GuidHob = GetFirstGuidHob (&gEfiVariableGuid); + if (GuidHob != NULL) { + Global->Flags.Auth = FALSE; + } + } + + Global->Flags.RecoveryMode = (GuidHob != NULL); + + // + // Before physical memory is ready, we cannot cache all variables in the very + // limited NEM space. But we still need to reserve buffer to hold data of + // one variable as well as context for integrity check (HMAC calculation). + // + Global->VariableCacheSize = MAX_VARIABLE_SIZE; + Buffer = AllocatePages (EFI_SIZE_TO_PAGES (Global->VariableCacheSize)); + if (Buffer == NULL) { + ASSERT (Buffer != NULL); + return EFI_OUT_OF_RESOURCES; + } + + Global->VariableCache = GET_ADRS (Buffer); + Global->LastAccessedVariable = VAR_INDEX_INVALID; + + for (Index = 0; Index < UnprotectedVarIndexMax; ++Index) { + Global->Unprotected[Index] = VAR_INDEX_INVALID; + } + + // + // Re-enumerate all NV variables and build digest list. + // + Status = CollectVariableDigestInfo ( + Global, + ContextIn, + DigBuffer, + &DigBufferSize, + &VarNumber + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + ASSERT (Global->VariableNumber == VarNumber); + + // + // Fix-up number of valid protected variables (i.e. exclude unprotected ones) + // + for (Index = 0; VarNumber != 0 && Index < UnprotectedVarIndexMax; ++Index) { + if (Global->Unprotected[Index] != VAR_INDEX_INVALID) { + --VarNumber; + } + } + + // + // Get root key and generate HMAC key. + // + PreviousKey = FALSE; + Status = GetVariableKey ((VOID *)Global->RootKey, sizeof (Global->RootKey)); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + Status = EFI_COMPROMISED_DATA; + } + + // + // Derive the MetaDataHmacKey from root key + // + if (!GenerateMetaDataHmacKey ( + Global->RootKey, + sizeof (Global->RootKey), + Global->MetaDataHmacKey, + sizeof (Global->MetaDataHmacKey) + )) + { + ASSERT (FALSE); + Status = EFI_COMPROMISED_DATA; + } + + // + // Check the integrity of all NV variables, if any. + // + if (( (Global->Unprotected[IndexHmacAdded] != VAR_INDEX_INVALID) + || (Global->Unprotected[IndexHmacInDel] != VAR_INDEX_INVALID))) + { + // + // Validate the HMAC stored in variable MetaDataHmacVar. + // + Status = VerifyMetaDataHmac (Global); + if (EFI_ERROR (Status)) { + // + // Try again with the previous root key if the latest key failed the HMAC validation. + // + Status = GetVariableKey ((VOID *)Global->RootKey, sizeof (Global->RootKey)); + if (!EFI_ERROR (Status)) { + // + // Derive the MetaDataHmacKey from previous root key + // + if (GenerateMetaDataHmacKey ( + Global->RootKey, + sizeof (Global->RootKey), + Global->MetaDataHmacKey, + sizeof (Global->MetaDataHmacKey) + ) == TRUE) + { + // + // Validate the HMAC stored in variable MetaDataHmacVar. + // + Status = VerifyMetaDataHmac (Global); + if (!EFI_ERROR (Status)) { + Status = EFI_COMPROMISED_DATA; + } + } else { + Status = EFI_COMPROMISED_DATA; + } + } + } + } else if (Global->Flags.RecoveryMode) { + // + // Generate the first version of MetaDataHmacVar. + // + Status = SyncRpmcCounter (); + if (!EFI_ERROR (Status)) { + Status = RefreshVariableMetadataHmac (Global, NULL, NULL); + if (!EFI_ERROR (Status)) { + // + // MetaDataHmacVar is always calculated against Counter2+1. Updating + // RPMCs to match it. + // + (VOID)IncrementMonotonicCounter (RPMC_COUNTER_1); + (VOID)IncrementMonotonicCounter (RPMC_COUNTER_2); + } + } + } else if ((VarNumber > 0) && !Global->Flags.RecoveryMode) { + // + // There's no MetaDataHmacVar found for protected variables. Suppose + // the variable storage is compromised. + // + Status = EFI_COMPROMISED_DATA; + } + + if (EFI_ERROR (Status)) { + // + // The integrity of variables have been compromised. The platform has to do + // something to recover the variable store. But the boot should not go on + // anyway this time. + // + DEBUG ((DEBUG_ERROR, "%a: %d Integrity check Status = %r\n", __FUNCTION__, __LINE__, Status)); + REPORT_STATUS_CODE ( + EFI_ERROR_CODE | EFI_ERROR_UNRECOVERED, + (PcdGet32 (PcdStatusCodeVariableIntegrity) | EFI_SW_PEI_PC_RECOVERY_BEGIN) + ); + #if defined (EDKII_UNIT_TEST_FRAMEWORK_ENABLED) // Avoid test malfunctioning. + return Status; + #else + ASSERT_EFI_ERROR (Status); + CpuDeadLoop (); + #endif + } + + // + // Everything's OK. + // + REPORT_STATUS_CODE ( + EFI_PROGRESS_CODE, + PcdGet32 (PcdStatusCodeVariableIntegrity) + ); + + if (GET_BUFR (Global->VariableCacheSize) != NULL) { + // + // Free Buffer + // + FreePages (Buffer, EFI_SIZE_TO_PAGES (Global->VariableCacheSize)); + } + + // + // Keep the valid MetaDataHmacVar in the list. + // + for (Index = 0; Index < IndexPlatformVar; ++Index) { + if ( (Global->Unprotected[Index] != VAR_INDEX_INVALID) + && VAR_DIG_PTR (Global->Unprotected[Index])->Flags.Valid) + { + InsertVariableDigestNode ( + Global, + VAR_DIG_PTR (Global->Unprotected[Index]), + NULL + ); + } + } + + // + // Restore the key to the latest one. + // + if (PreviousKey) { + Status = GetVariableKey ((VOID *)Global->RootKey, sizeof (Global->RootKey)); + ASSERT_EFI_ERROR (Status); + + // + // Derive the MetaDataHmacKey from root key + // + if (!GenerateMetaDataHmacKey ( + Global->RootKey, + sizeof (Global->RootKey), + Global->MetaDataHmacKey, + sizeof (Global->MetaDataHmacKey) + )) + { + ASSERT (FALSE); + } + } + + // + // Make sure that the RPMC counter is in-sync. + // + Status = SyncRpmcCounter (); + + // + // Setup a hook to migrate data in Global once physical memory is ready. + // + Status = PeiServicesNotifyPpi (mPostMemNotifyList); + + return Status; +} + +/** + + Initialization for protected variable services. + + If the variable store is available than perform integrity check. + Otherwise, defer integrity check until variable store is available. + + + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + + @retval EFI_SUCCESS Protected variable services are ready. + @retval EFI_INVALID_PARAMETER If ContextIn == NULL or something missing or + mismatching in the content in ContextIn. + @retval EFI_COMPROMISED_DATA If failed to check integrity of protected variables. + @retval EFI_OUT_OF_RESOURCES Fail to allocate enough resource. + @retval EFI_UNSUPPORTED Unsupported to process protected variable. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibInitialize ( + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn + ) +{ + EFI_STATUS Status; + VOID *ContextInHob; + PROTECTED_VARIABLE_INFO VarInfo; + + if ((ContextIn == NULL) || (ContextIn->GetNextVariableInfo == NULL)) { + ASSERT (ContextIn != NULL); + ASSERT (ContextIn->GetNextVariableInfo != NULL); + return EFI_INVALID_PARAMETER; + } + + // + // Keep a copy of ContextIn in HOB for later uses. + // + ContextIn->StructSize = (ContextIn->StructSize == 0) ? sizeof (*ContextIn) + : ContextIn->StructSize; + ContextInHob = BuildGuidHob (&gEdkiiProtectedVariableContextGuid, ContextIn->StructSize); + CopyMem (ContextInHob, ContextIn, ContextIn->StructSize); + + // + // Discover if Variable Store Info Hob has been published by platform driver. + // It contains information regards to HOB or NV Variable Store availability + // + ZeroMem ((VOID *)&VarInfo.Header, sizeof (VarInfo.Header)); + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Buffer = AllocatePages (EFI_SIZE_TO_PAGES (MAX_VARIABLE_SIZE)); + if (VarInfo.Buffer == NULL) { + ASSERT (VarInfo.Buffer != NULL); + return EFI_OUT_OF_RESOURCES; + } + + FreePages (VarInfo.Buffer, EFI_SIZE_TO_PAGES ((MAX_VARIABLE_SIZE))); + + Status = ContextIn->GetNextVariableInfo (&VarInfo); + if (EFI_ERROR (Status)) { + // + // Register for platform driver callback when Variable Store is available. + // + DEBUG ((DEBUG_INFO, "Variable Store is not available. Register for a integrity check callback\n")); + Status = PeiServicesNotifyPpi (mVariableStoreNotifyList); + return Status; + } + + // + // HOB Variable store is not available + // Assume NV Variable store is available instead + // Perform integrity check on NV Variable Store + // + DEBUG ((DEBUG_INFO, "NV Variable Store is available. Perform integrity check\n")); + Status = PerformVariableIntegrityCheck (ContextInHob); + return Status; +} + +/** + + Prepare for variable update. + + (Not suppported in PEI phase.) + + @retval EFI_UNSUPPORTED Updating variable is not supported. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibWriteInit ( + VOID + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} + +/** + + Update a variable with protection provided by this library. + + Not supported in PEI phase. + + @param[in,out] CurrVariable Variable to be updated. It's NULL if + adding a new variable. + @param[in] CurrVariableInDel In-delete-transition copy of updating variable. + @param[in,out] NewVariable Buffer of new variable data. + Buffer of "MetaDataHmacVar" and new + variable (encrypted). + @param[in,out] NewVariableSize Size of NewVariable. + Size of (encrypted) NewVariable and + "MetaDataHmacVar". + + @retval EFI_UNSUPPORTED Not support updating variable in PEI phase. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibUpdate ( + IN OUT VARIABLE_HEADER *CurrVariable, + IN VARIABLE_HEADER *CurrVariableInDel, + IN OUT VARIABLE_HEADER *NewVariable, + IN OUT UINTN *NewVariableSize + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} + +/** + + Finalize a variable updating after it's written to NV variable storage + successfully. + + @param[in] NewVariable Buffer of new variables and MetaDataHmacVar. + @param[in] VariableSize Size of buffer pointed by NewVariable. + @param[in] StoreIndex StoreIndex to NV variable storage from where the new + variable and MetaDataHmacVar have been written. + + @retval EFI_UNSUPPORTED Not support updating variable in PEI phase. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibWriteFinal ( + IN VARIABLE_HEADER *NewVariable, + IN UINTN VariableSize, + IN UINT64 StoreIndex + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmm.c b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmm.c new file mode 100644 index 000000000000..8e964f4cd28d --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmm.c @@ -0,0 +1,209 @@ +/** @file + Implemention of ProtectedVariableLib for SMM variable services. + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +#include "Guid/SmmVariableCommon.h" + +#include "Library/MmServicesTableLib.h" +#include "Library/MemoryAllocationLib.h" + +#include "ProtectedVariableInternal.h" + +PROTECTED_VARIABLE_CONTEXT_IN mVariableContextIn = { + PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION, + sizeof (PROTECTED_VARIABLE_CONTEXT_IN), + 0, + FromSmmModule, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +PROTECTED_VARIABLE_GLOBAL mProtectedVariableGlobal = { + PROTECTED_VARIABLE_CONTEXT_OUT_STRUCT_VERSION, + sizeof (PROTECTED_VARIABLE_GLOBAL), + { 0 }, + { 0 }, + 0, + 0, + 0, + 0, + 0, + 0, + { 0, 0, 0 }, + 0, + 0, + { 0, 0, 0, 0} +}; + +/** + + Callback function to call variable write. + + @param[in] Protocol Not Used. + @param[in] Interface Not Used. + @param[in] Handle Not Used. + + @retval EFI_SUCCESS Protected variable write successful. + @retval others Protected variable write failed. + +**/ +EFI_STATUS +EFIAPI +VariableWriteProtocolCallback ( + IN CONST EFI_GUID *Protocol, + IN VOID *Interface, + IN EFI_HANDLE Handle + ) +{ + EFI_STATUS Status; + + Status = ProtectedVariableLibWriteInit (); + ASSERT_EFI_ERROR (Status); + + return EFI_SUCCESS; +} + +/** + + Initialization for protected variable services. + + If this initialization failed upon any error, the whole variable services + should not be used. A system reset might be needed to re-construct NV + variable storage to be the default state. + + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + + @retval EFI_SUCCESS Protected variable services are ready. + @retval EFI_INVALID_PARAMETER If ContextIn == NULL or something missing or + mismatching in the content in ContextIn. + @retval EFI_COMPROMISED_DATA If failed to check integrity of protected variables. + @retval EFI_OUT_OF_RESOURCES Fail to allocate enough resource. + @retval EFI_UNSUPPORTED Unsupported to process protected variable. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibInitialize ( + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_CONTEXT_IN *ProtectedVarContext; + PROTECTED_VARIABLE_GLOBAL *OldGlobal; + PROTECTED_VARIABLE_GLOBAL *NewGlobal; + VARIABLE_DIGEST *VarDig; + VARIABLE_DIGEST *NewVarDig; + EFI_PHYSICAL_ADDRESS NewCacheIndex; + UINTN VarSize; + UNPROTECTED_VARIABLE_INDEX Index; + + if ( (ContextIn == NULL) + || (ContextIn->StructVersion != PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION) + || (ContextIn->StructSize != sizeof (PROTECTED_VARIABLE_CONTEXT_IN)) + || (ContextIn->GetVariableInfo == NULL) + || (ContextIn->GetNextVariableInfo == NULL) + || (ContextIn->UpdateVariableStore == NULL) + || (ContextIn->UpdateVariable == NULL)) + { + ASSERT (ContextIn != NULL); + ASSERT (ContextIn->StructVersion == PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION); + ASSERT (ContextIn->StructSize == sizeof (PROTECTED_VARIABLE_CONTEXT_IN)); + ASSERT (ContextIn->GetVariableInfo != NULL); + ASSERT (ContextIn->GetNextVariableInfo != NULL); + ASSERT (ContextIn->UpdateVariableStore != NULL); + ASSERT (ContextIn->UpdateVariable != NULL); + return EFI_INVALID_PARAMETER; + } + + GetProtectedVariableGlobal (&NewGlobal); + ProtectedVarContext = GET_CNTX (NewGlobal); + CopyMem (ProtectedVarContext, ContextIn, sizeof (mVariableContextIn)); + ProtectedVarContext->VariableServiceUser = FromSmmModule; + + // + // Get root key and HMAC key from HOB created by PEI variable driver. + // + Status = GetProtectedVariableGlobalFromHob (&OldGlobal); + ASSERT_EFI_ERROR (Status); + + CopyMem ((VOID *)NewGlobal, (CONST VOID *)OldGlobal, sizeof (*OldGlobal)); + + // + // The keys must not be available outside SMM. + // + if (ProtectedVarContext->VariableServiceUser == FromSmmModule) { + ZeroMem (OldGlobal->RootKey, sizeof (OldGlobal->RootKey)); + ZeroMem (OldGlobal->MetaDataHmacKey, sizeof (OldGlobal->MetaDataHmacKey)); + } + + NewGlobal->Flags.WriteInit = FALSE; + NewGlobal->Flags.WriteReady = FALSE; + NewGlobal->LastAccessedVariable = 0; + NewGlobal->VariableCache = GET_ADRS (AllocateZeroPool (MAX_VARIABLE_SIZE)); + NewGlobal->DigestContext = GET_ADRS (AllocateZeroPool (DIGEST_CONTEXT_SIZE)); + if (NewGlobal->DigestContext == 0) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Copy over variable from HOB to SMM memory + // + NewGlobal->VariableDigests = 0; + VarDig = VAR_DIG_PTR (OldGlobal->VariableDigests); + while (VarDig != NULL) { + // + // Allocate new Var Digest in SMM memory + // + NewVarDig = (VARIABLE_DIGEST *)AllocateZeroPool ( + sizeof (VARIABLE_DIGEST) + VarDig->NameSize + METADATA_HMAC_SIZE + ); + if (NewVarDig == NULL) { + ASSERT (NewVarDig != NULL); + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (NewVarDig, VarDig, sizeof (VARIABLE_DIGEST)); + NewVarDig->Prev = 0; + NewVarDig->Next = 0; + + CopyMem (VAR_DIG_NAME (NewVarDig), VAR_DIG_NAME (VarDig), VarDig->NameSize); + CopyMem (VAR_DIG_VALUE (NewVarDig), VAR_DIG_VALUE (VarDig), VarDig->DigestSize); + + VarSize = VARIABLE_HEADER_SIZE (NewGlobal->Flags.Auth); + VarSize += VarDig->NameSize + GET_PAD_SIZE (VarDig->NameSize); + VarSize += VarDig->DataSize + GET_PAD_SIZE (VarDig->DataSize); + VarSize = HEADER_ALIGN (VarSize); + + NewCacheIndex = GET_ADRS (AllocateZeroPool (VarSize)); + if (GET_BUFR (NewCacheIndex) == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + CopyMem (GET_BUFR (NewCacheIndex), GET_BUFR (VarDig->CacheIndex), VarSize); + NewVarDig->CacheIndex = NewCacheIndex; + NewVarDig->Flags.Freeable = TRUE; + + for (Index = 0; Index < UnprotectedVarIndexMax; ++Index) { + if (OldGlobal->Unprotected[Index] == VAR_DIG_ADR (VarDig)) { + NewGlobal->Unprotected[Index] = VAR_DIG_ADR (NewVarDig); + } + } + + InsertVariableDigestNode (NewGlobal, NewVarDig, NULL); + + VarDig = VAR_DIG_NEXT (VarDig); + } + + return Status; +} diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmDxeCommon.c b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmDxeCommon.c new file mode 100644 index 000000000000..92ba7cb12763 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmDxeCommon.c @@ -0,0 +1,975 @@ +/** @file + Implemention of ProtectedVariableLib for SMM variable services. + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +#include "Guid/SmmVariableCommon.h" + +#include "Library/MmServicesTableLib.h" +#include "Library/MemoryAllocationLib.h" +#include + +#include "ProtectedVariableInternal.h" + +/** + + Get context and/or global data structure used to process protected variable. + + @param[out] Global Pointer to global configuration data. + + @retval EFI_SUCCESS Get requested structure successfully. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableGlobal ( + OUT PROTECTED_VARIABLE_GLOBAL **Global OPTIONAL + ) +{ + if (Global != NULL) { + mProtectedVariableGlobal.ContextIn = GET_ADRS (&mVariableContextIn); + *Global = &mProtectedVariableGlobal; + } + + return EFI_SUCCESS; +} + +/** + Encrypt given variable data and generate new HMAC value against it. + + @param[in] Global Pointer to global configuration data. + @param[in,out] NewVarInfo Pointer to buffer of new variable data. + @param[in,out] NewVarDig Pointer to buffer of new variable digest. + + @retval EFI_SUCCESS No error occurred during the encryption and HMC calculation. + @retval EFI_ABORTED Failed to do HMC calculation. + @return EFI_OUT_OF_RESOURCES Not enough resource to calculate HMC value. + @return EFI_NOT_FOUND The MetaDataHmacVar was not found in storage. + +**/ +STATIC +EFI_STATUS +UpdateVariableInternal ( + IN PROTECTED_VARIABLE_GLOBAL *Global, + IN OUT PROTECTED_VARIABLE_INFO *NewVarInfo, + IN OUT VARIABLE_DIGEST *NewVarDig + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO CachedVarInfo; + VOID *Buffer; + UINTN VarSize; + + if ((NewVarInfo == NULL) || (NewVarDig == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // + // If Add or update variable, encrypt new data first. + // + if (NewVarInfo->Buffer != NULL) { + Status = EFI_UNSUPPORTED; + + if (NewVarDig->Flags.Encrypted) { + NewVarInfo->PlainData = NULL; + NewVarInfo->PlainDataSize = 0; + NewVarInfo->CipherData = NULL; + NewVarInfo->CipherDataSize = 0; + NewVarInfo->Key = Global->RootKey; + NewVarInfo->KeySize = sizeof (Global->RootKey); + NewVarInfo->Header.Attributes &= (~EFI_VARIABLE_APPEND_WRITE); + Status = EncryptVariable (NewVarInfo); + if (!EFI_ERROR (Status)) { + // + // Update new data size in variable header. + // + SET_VARIABLE_DATA_SIZE (NewVarInfo, NewVarInfo->CipherDataSize); + } else if (Status != EFI_UNSUPPORTED) { + ASSERT (FALSE); + return Status; + } + } + + if (Status == EFI_UNSUPPORTED) { + NewVarInfo->CipherData = NewVarInfo->Header.Data; + NewVarInfo->CipherDataSize = (UINT32)NewVarInfo->Header.DataSize; + NewVarInfo->PlainData = NULL; + NewVarInfo->PlainDataSize = 0; + } + } else { + NewVarInfo->CipherData = NULL; + NewVarInfo->CipherDataSize = 0; + NewVarInfo->PlainData = NULL; + NewVarInfo->PlainDataSize = 0; + } + + if (NewVarDig->CacheIndex != 0) { + // + // Update the cached copy. + // + ZeroMem ((VOID *)&CachedVarInfo, sizeof (CachedVarInfo)); + CachedVarInfo.Buffer = GET_BUFR (NewVarDig->CacheIndex); + CachedVarInfo.StoreIndex = VAR_INDEX_INVALID; + CachedVarInfo.Flags.Auth = NewVarInfo->Flags.Auth; + + Status = GET_CNTX (Global)->GetVariableInfo (&CachedVarInfo); + ASSERT_EFI_ERROR (Status); + + if ((CachedVarInfo.Header.DataSize != 0) && (NewVarInfo->CipherDataSize > CachedVarInfo.Header.DataSize)) { + // + // allocate new VarInfo buffer that is of greater CipherDataSize + // + VarSize = VARIABLE_HEADER_SIZE (NewVarDig->Flags.Auth); + VarSize += NewVarInfo->Header.NameSize + GET_PAD_SIZE (NewVarInfo->Header.NameSize); + VarSize += NewVarInfo->CipherDataSize + GET_PAD_SIZE (NewVarInfo->CipherDataSize); + VarSize = HEADER_ALIGN (VarSize); + Buffer = AllocateZeroPool (VarSize); + if (Buffer != NULL) { + VarSize = VARIABLE_HEADER_SIZE (NewVarDig->Flags.Auth); + VarSize += CachedVarInfo.Header.NameSize + GET_PAD_SIZE (CachedVarInfo.Header.NameSize); + VarSize += CachedVarInfo.Header.DataSize + GET_PAD_SIZE (CachedVarInfo.DataSize); + VarSize = HEADER_ALIGN (VarSize); + + CopyMem ( + Buffer, + CachedVarInfo.Buffer, + VarSize + ); + + FreePool (CachedVarInfo.Buffer); + + // + // Update the cached copy. + // + ZeroMem ((VOID *)&CachedVarInfo, sizeof (CachedVarInfo)); + CachedVarInfo.Buffer = Buffer; + CachedVarInfo.StoreIndex = VAR_INDEX_INVALID; + CachedVarInfo.Flags.Auth = NewVarInfo->Flags.Auth; + Status = GET_CNTX (Global)->GetVariableInfo (&CachedVarInfo); + ASSERT_EFI_ERROR (Status); + NewVarDig->CacheIndex = (EFI_PHYSICAL_ADDRESS)(UINTN)Buffer; + } + } + + CopyMem ( + CachedVarInfo.Header.Data, + NewVarInfo->CipherData, + NewVarInfo->CipherDataSize + ); + SET_VARIABLE_DATA_SIZE (&CachedVarInfo, NewVarInfo->CipherDataSize); + + NewVarDig->State = VAR_ADDED; + NewVarDig->DataSize = NewVarInfo->CipherDataSize; + + if (NewVarInfo->PlainDataSize > 0) { + NewVarDig->PlainDataSize = NewVarInfo->PlainDataSize; + } else { + NewVarDig->PlainDataSize = NewVarDig->DataSize; + } + + // + // (Re-)Calculate the hash of the variable. + // + if (NewVarDig->Flags.Protected) { + GetVariableDigest (Global, NewVarInfo, VAR_DIG_VALUE (NewVarDig)); + } + } + + return EFI_SUCCESS; +} + +/** + + Fix state of MetaDataHmacVar on NV variable storage, if there's failure at + last boot during updating variable. + + This must be done before the first writing of variable in current boot, + including storage reclaim. + + @retval EFI_UNSUPPORTED Updating NV variable storage is not supported. + @retval EFI_OUT_OF_RESOURCES Not enough resource to complete the operation. + @retval EFI_SUCCESS Variable store was successfully updated. + +**/ +EFI_STATUS +FixupHmacVariable ( + VOID + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO HmacVarInfo; + PROTECTED_VARIABLE_GLOBAL *Global; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + VARIABLE_DIGEST *VarDig; + UINTN Index; + + Status = GetProtectedVariableGlobal (&Global); + ASSERT_EFI_ERROR (Status); + + if (Global->Flags.WriteReady) { + return EFI_SUCCESS; + } + + ContextIn = GET_CNTX (Global); + + // + // Delete invalid MetaDataHmacVar. + // + for (Index = 0; Index <= IndexHmacAdded; ++Index) { + if (Global->Unprotected[Index] == VAR_INDEX_INVALID) { + continue; + } + + VarDig = VAR_DIG_PTR (Global->Unprotected[Index]); + if (VarDig->Flags.Valid) { + continue; + } + + ZeroMem ((VOID *)&HmacVarInfo, sizeof (HmacVarInfo)); + HmacVarInfo.StoreIndex = VarDig->StoreIndex; + HmacVarInfo.Flags.Auth = VarDig->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&HmacVarInfo); + if (!EFI_ERROR (Status) && (HmacVarInfo.Buffer != NULL)) { + HmacVarInfo.Buffer->State &= VAR_DELETED; + Status = ContextIn->UpdateVariableStore ( + &HmacVarInfo, + OFFSET_OF (VARIABLE_HEADER, State), + sizeof (HmacVarInfo.Buffer->State), + &HmacVarInfo.Buffer->State + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + } + + // + // Release the resource and update related states. + // + VarDig->State &= VAR_DELETED; + RemoveVariableDigestNode (Global, VarDig, FALSE); + Global->Unprotected[Index] = VAR_INDEX_INVALID; + } + + // + // There should be no MetaDataHmacVar if in variable storage recovery mode. + // + if (Global->Flags.RecoveryMode) { + ASSERT (Global->Unprotected[IndexHmacAdded] == VAR_INDEX_INVALID); + ASSERT (Global->Unprotected[IndexHmacInDel] == VAR_INDEX_INVALID); + } + + Global->Flags.WriteReady = TRUE; + + return EFI_SUCCESS; +} + +/** + + Prepare for variable update. + + This is needed only once during current boot to mitigate replay attack. Its + major job is to advance RPMC (Replay Protected Monotonic Counter). + + @retval EFI_SUCCESS Variable is ready to update hereafter. + @retval EFI_UNSUPPORTED Updating variable is not supported. + @retval EFI_DEVICE_ERROR Error in advancing RPMC. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibWriteInit ( + VOID + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_GLOBAL *Global; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + PROTECTED_VARIABLE_INFO VarInfo; + + (VOID)GetProtectedVariableGlobal (&Global); + ContextIn = GET_CNTX (Global); + + // + // HmacVarInfo should be here + // + if (Global->Flags.RecoveryMode) { + // + // Flush default variables to variable storage if in variable recovery mode. + // + Status = ContextIn->UpdateVariableStore (NULL, 0, (UINT32)-1, NULL); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + } else { + // + // RPMC1 is going to be one step ahead of RPMC2 here, in order to + // refresh the MetaDataHmacVar in each boot before writing any other + // variables. + // + Status = IncrementMonotonicCounter (RPMC_COUNTER_1); + ASSERT_EFI_ERROR (Status); + + ContextIn = GET_CNTX (Global); + + // + // Fix any wrong MetaDataHmacVar information before adding new one. + // + Status = FixupHmacVariable (); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + if (!Global->Flags.WriteReady) { + return EFI_NOT_READY; + } + + // + // Refresh MetaDataHmacVar with RPMC2 by 1 in each boot before any variable + // update, by deleting (attr == 0 && datasize == 0) the old one. + // + ZeroMem (&VarInfo, sizeof (PROTECTED_VARIABLE_INFO)); // Zero attr & datasize + + VarInfo.Flags.Auth = Global->Flags.Auth; + VarInfo.Header.VariableName = METADATA_HMAC_VARIABLE_NAME; + VarInfo.Header.NameSize = METADATA_HMAC_VARIABLE_NAME_SIZE; + VarInfo.Header.VendorGuid = &METADATA_HMAC_VARIABLE_GUID; + + // + // Pretend to delete MetaDataHmacVar. + // + Status = ContextIn->UpdateVariable (&VarInfo.Header); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + } + + mProtectedVariableGlobal.Flags.WriteInit = TRUE; + + return EFI_SUCCESS; +} + +/** + + Update a variable with protection provided by this library. + + If variable encryption is employed, the new variable data will be encrypted + before being written to NV variable storage. + + A special variable, called "MetaDataHmacVar", will always be updated along + with variable being updated to reflect the changes (HMAC value) of all + protected valid variables. The only exceptions, currently, is variable + variable "VarErrorLog". + + The buffer passed by NewVariable must be double of maximum variable size, + which allows to pass the "MetaDataHmacVar" back to caller along with encrypted + new variable data, if any. This can make sure the new variable data and + "MetaDataHmacVar" can be written at almost the same time to reduce the chance + of compromising the integrity. + + If *NewVariableSize is zero, it means to delete variable passed by CurrVariable + and/or CurrVariableInDel. "MetaDataHmacVar" will be updated as well in such + case because of less variables in storage. NewVariable should be always passed + in to convey new "MetaDataHmacVar" back. + + @param[in,out] CurrVariable Variable to be updated. It's NULL if + adding a new variable. + @param[in] CurrVariableInDel In-delete-transition copy of updating variable. + @param[in,out] NewVariable Buffer of new variable data. + Buffer of "MetaDataHmacVar" and new + variable (encrypted). + @param[in,out] NewVariableSize Size of NewVariable. + Size of (encrypted) NewVariable and + "MetaDataHmacVar". + + @retval EFI_SUCCESS The variable is updated with protection successfully. + @retval EFI_INVALID_PARAMETER NewVariable is NULL. + @retval EFI_NOT_FOUND Information missing to finish the operation. + @retval EFI_ABORTED Failed to encrypt variable or calculate HMAC. + @retval EFI_NOT_READY The RPMC device is not yet initialized. + @retval EFI_DEVICE_ERROR The RPMC device has error in updating. + @retval EFI_ACCESS_DENIED The given variable is not allowed to update. + Currently this only happens on updating + "MetaDataHmacVar" from code outside of this + library. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibUpdate ( + IN OUT VARIABLE_HEADER *CurrVariable, + IN VARIABLE_HEADER *CurrVariableInDel, + IN OUT VARIABLE_HEADER *NewVariable, + IN OUT UINTN *NewVariableSize + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + PROTECTED_VARIABLE_GLOBAL *Global; + PROTECTED_VARIABLE_INFO VarInfo; + VARIABLE_DIGEST *VarDig; + VARIABLE_DIGEST *CurrVarDig; + VARIABLE_DIGEST *NewVarDig; + PROTECTED_VARIABLE_INFO NewVarInfo; + PROTECTED_VARIABLE_INFO NewHmacVarInfo; + UINTN VarSize; + UINT64 UnprotectedVarIndex; + + // + // Advance RPMC + // + Status = IncrementMonotonicCounter (RPMC_COUNTER_1); + ASSERT_EFI_ERROR (Status); + + // + // Buffer for new variable is always needed, even this function is called to + // delete an existing one, because we need to pass the MetaDataHmacVar back + // which will be updated upon each variable addition or deletion. + // + if ((NewVariable == NULL) || (NewVariableSize == NULL)) { + ASSERT (NewVariable != NULL); + ASSERT (NewVariableSize != NULL); + return EFI_INVALID_PARAMETER; + } + + Status = GetProtectedVariableGlobal (&Global); + ASSERT_EFI_ERROR (Status); + ContextIn = GET_CNTX (Global); + + if (!Global->Flags.WriteReady && !Global->Flags.WriteInit) { + return EFI_NOT_READY; + } + + VarSize = 0; + CurrVarDig = NULL; + NewVarDig = NULL; + UnprotectedVarIndex = VAR_INDEX_INVALID; + + // + // Check existing copy of the same variable. + // + if (CurrVariable != NULL) { + // + // Find local cached copy, if possible. + // + ZeroMem (&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = CurrVariable; + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Flags.Auth = Global->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&VarInfo); // Retrieve the name/guid + ASSERT_EFI_ERROR (Status); + + UnprotectedVarIndex = CheckKnownUnprotectedVariable (Global, &VarInfo); + if (UnprotectedVarIndex < UnprotectedVarIndexMax) { + CurrVarDig = VAR_DIG_PTR (Global->Unprotected[UnprotectedVarIndex]); + } else { + CurrVarDig = FindVariableInternal (Global, &VarInfo, FALSE); + } + + ASSERT (CurrVarDig != NULL); + CurrVarDig->State &= VAR_DELETED; + } + + // + // The old copy of the variable might haven't been deleted completely. + // + if (CurrVariableInDel != NULL) { + // + // Find local cached copy, if possible. + // + ZeroMem (&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = CurrVariableInDel; + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Flags.Auth = Global->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&VarInfo); // Retrieve the name/guid + ASSERT_EFI_ERROR (Status); + + if (UnprotectedVarIndex == VAR_INDEX_INVALID) { + UnprotectedVarIndex = CheckKnownUnprotectedVariable (Global, &VarInfo); + } + + if (UnprotectedVarIndex < UnprotectedVarIndexMax) { + VarDig = VAR_DIG_PTR (Global->Unprotected[UnprotectedVarIndex]); + } else { + VarDig = FindVariableInternal (Global, &VarInfo, FALSE); + } + + if ((VarDig != NULL) && (VAR_DIG_ADR (VarDig) != VAR_INDEX_INVALID)) { + VarDig->State &= VAR_DELETED; + + // + // Just need one node for the same variable. So remove the one + // in-del-transition. + // + if ((CurrVarDig != NULL) && (VarDig != CurrVarDig)) { + RemoveVariableDigestNode (Global, VarDig, TRUE); + } else { + CurrVarDig = VarDig; // Reuse the one in-del-transition. + } + } + } + + // + // New data of the variable or new variable to be added. + // + if (NewVariable != NULL) { + // + // Completely new variable? + // + if (UnprotectedVarIndex == VAR_INDEX_INVALID) { + ZeroMem (&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = NewVariable; + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Flags.Auth = Global->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&VarInfo); // Retrieve the name/guid + ASSERT_EFI_ERROR (Status); + + UnprotectedVarIndex = CheckKnownUnprotectedVariable (Global, &VarInfo); + } + } + + // + // Reserve space for MetaDataHmacVar (before the new variable so + // that it can be written first). + // + ZeroMem (&NewVarInfo, sizeof (NewVarInfo)); + ZeroMem (&NewHmacVarInfo, sizeof (NewHmacVarInfo)); + + // + // Put the MetaDataHmacVar at the beginning of buffer. + // + NewHmacVarInfo.Buffer = NewVariable; + + if (*NewVariableSize == 0) { + // + // Delete variable (but not MetaDataHmacVar) + // + if ( (UnprotectedVarIndex != IndexHmacAdded) + && (UnprotectedVarIndex != IndexHmacInDel)) + { + RemoveVariableDigestNode (Global, CurrVarDig, TRUE); + } + + NewVarInfo.Buffer = NULL; + } else if (UnprotectedVarIndex >= IndexPlatformVar) { + // + // Add/update variable. Move new variable data to be after MetaDataHmacVar. + // + // TRICK: New MetaDataHmacVar will be put at the beginning of buffer + // for new variable so that they can be written into non-volatile + // variable storage in one call. This can avoid writing one variable + // (NewHmacVarInfo) in the middle of writing another variable + // (NewVarInfo), which will need two calls and introduce extra + // complexities (from temp variable buffer reservation to variable + // space reclaim, etc.) in current implementation of variable + // services. The caller must make sure there's enough space in + // variable buffer (i.e. at least 2 * MaxVariableSize). + // + NewVarInfo.Buffer = (VARIABLE_HEADER *)((UINTN)NewVariable + + GetMetaDataHmacVarSize (Global->Flags.Auth)); + CopyMem ((VOID *)NewVarInfo.Buffer, (VOID *)NewVariable, *NewVariableSize); + + NewVarInfo.StoreIndex = VAR_INDEX_INVALID; // Skip offset calculation (it's new one) + NewVarInfo.Flags.Auth = Global->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&NewVarInfo); + ASSERT_EFI_ERROR (Status); + + if (CurrVarDig != NULL) { + // + // Update existing variable. Re-use the node. + // + NewVarDig = CurrVarDig; + } else { + // + // Add new variable. + // + NewVarDig = CreateVariableDigestNode ( + NewVarInfo.Header.VariableName, + NewVarInfo.Header.VendorGuid, + (UINT16)NewVarInfo.Header.NameSize, + (UINT32)NewVarInfo.Header.DataSize, + NewVarInfo.Flags.Auth, + Global + ); + if (NewVarDig == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + NewVarDig->Attributes = NewVarInfo.Header.Attributes; + if (UnprotectedVarIndex < UnprotectedVarIndexMax) { + NewVarDig->Flags.Protected = FALSE; + NewVarDig->Flags.Encrypted = FALSE; + Global->Unprotected[UnprotectedVarIndex] = VAR_DIG_ADR (NewVarDig); + } + + // + // copy new variable to CacheIndex + // + VarSize = VARIABLE_HEADER_SIZE (NewVarInfo.Flags.Auth); + VarSize += NewVarInfo.Header.NameSize + GET_PAD_SIZE (NewVarInfo.Header.NameSize); + VarSize += NewVarInfo.Header.DataSize + GET_PAD_SIZE (NewVarInfo.Header.DataSize); + VarSize = HEADER_ALIGN (VarSize); + CopyMem (GET_BUFR (NewVarDig->CacheIndex), GET_BUFR (NewVarInfo.Buffer), VarSize); + InsertVariableDigestNode (Global, NewVarDig, NULL); + } + } + + if ( (UnprotectedVarIndex == IndexHmacAdded) + || (UnprotectedVarIndex == IndexHmacInDel)) + { + // + // MetaDataHmacVar should be managed only by this library. It's not + // supposed to be updated by external users of variable service. The only + // exception is that deleting it (not really delete but refresh the HMAC + // value against RPMC+1) is allowed before WriteInit, as a way to always + // increment RPMC once in current boot before any variable updates. + // + if ((NewVarInfo.Buffer != NULL) || Global->Flags.WriteInit) { + return EFI_ACCESS_DENIED; + } + } else { + // + // Do encryption, if enabled. + // + if ((NewVarDig != NULL) && (NewVarInfo.Buffer != NULL)) { + Status = UpdateVariableInternal (Global, &NewVarInfo, NewVarDig); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + } + } + + // + // Refresh MetaDataHmacVar. + // + Status = RefreshVariableMetadataHmac (Global, NULL, &NewHmacVarInfo); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + // + // Return size for both MetaDataHmacVar and added/updated one. + // + VarSize = VARIABLE_SIZE (&NewHmacVarInfo); + *NewVariableSize = HEADER_ALIGN (VarSize); + if (NewVarInfo.Buffer != NULL) { + VarSize = VARIABLE_SIZE (&NewVarInfo); + VarSize = HEADER_ALIGN (VarSize); + + if (VarSize > GET_CNTX (Global)->MaxVariableSize) { + return EFI_BAD_BUFFER_SIZE; + } + + *NewVariableSize += VarSize; + } + + return EFI_SUCCESS; +} + +/** + + Finalize a variable updating after it's written to NV variable storage + successfully. + + This usually includes works like increasing RPMC, synchronizing local cache, + updating new position of "MetaDataHmacVar", deleting old copy of "MetaDataHmacVar" + completely, etc. + + @param[in] NewVariable Buffer of new variables and MetaDataHmacVar. + @param[in] VariableSize Size of buffer pointed by NewVariable. + @param[in] StoreIndex StoreIndex to NV variable storage from where the new + variable and MetaDataHmacVar have been written. + + @retval EFI_SUCCESS No problem in winding up the variable write operation. + @retval Others Failed to updating state of old copy of updated + variable, or failed to increase RPMC, etc. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibWriteFinal ( + IN VARIABLE_HEADER *NewVariable, + IN UINTN VariableSize, + IN UINT64 StoreIndex + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO VarInfo; + PROTECTED_VARIABLE_CONTEXT_IN *ContextIn; + PROTECTED_VARIABLE_GLOBAL *Global; + UNPROTECTED_VARIABLE_INDEX Index; + VARIABLE_DIGEST *VarDig; + VOID *Buffer; + UINTN VarSize; + UINTN NewVarSize; + + Status = GetProtectedVariableGlobal (&Global); + ASSERT_EFI_ERROR (Status); + ContextIn = GET_CNTX (Global); + + ZeroMem ((VOID *)&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = NewVariable; + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Flags.Auth = Global->Flags.Auth; + + Status = ContextIn->GetVariableInfo (&VarInfo); + ASSERT_EFI_ERROR (Status); + + Index = CheckKnownUnprotectedVariable (Global, &VarInfo); + if (Index < UnprotectedVarIndexMax) { + VarDig = VAR_DIG_PTR (Global->Unprotected[Index]); + } else { + VarDig = FindVariableInternal (Global, &VarInfo, FALSE); + } + + if (Index == IndexHmacAdded) { + // + // Advance the RPMC to let it match new MetaDataHmacVar. + // + Status = IncrementMonotonicCounter (RPMC_COUNTER_2); + ASSERT_EFI_ERROR (Status); + + if ((VarDig->StoreIndex != VAR_INDEX_INVALID) && (VarDig->State != VAR_ADDED)) { + ZeroMem ((VOID *)&VarInfo, sizeof (VarInfo)); + VarInfo.StoreIndex = VarDig->StoreIndex; // Still point to old copy + VarInfo.Flags.Auth = Global->Flags.Auth; + + // + // Delete variable completely. + // + Status = ContextIn->GetVariableInfo (&VarInfo); + ASSERT_EFI_ERROR (Status); + + if ( (VarInfo.Buffer->State == VAR_ADDED) + || (VarInfo.Buffer->State == (VAR_ADDED & VAR_IN_DELETED_TRANSITION))) + { + VarInfo.Buffer->State &= VAR_DELETED; + Status = ContextIn->UpdateVariableStore ( + &VarInfo, + OFFSET_OF (VARIABLE_HEADER, State), + sizeof (VarInfo.Buffer->State), + &VarInfo.Buffer->State + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + } + } + } + + VarDig->StoreIndex = StoreIndex; + VarDig->State = VAR_ADDED; + + ZeroMem ((VOID *)&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = NULL; + VarInfo.StoreIndex = VarDig->StoreIndex; + VarInfo.Flags.Auth = Global->Flags.Auth; + Status = ContextIn->GetVariableInfo (&VarInfo); + + // + // Check if cache pool need re-allocation due to variable size increase + // + VarSize = VARIABLE_HEADER_SIZE (VarDig->Flags.Auth); + VarSize += VarDig->NameSize + GET_PAD_SIZE (VarDig->NameSize); + VarSize += VarDig->DataSize + GET_PAD_SIZE (VarDig->DataSize); + VarSize = HEADER_ALIGN (VarSize); + + NewVarSize = VARIABLE_HEADER_SIZE (VarInfo.Flags.Auth); + NewVarSize += VarInfo.Header.NameSize + GET_PAD_SIZE (VarInfo.Header.NameSize); + NewVarSize += VarInfo.Header.DataSize + GET_PAD_SIZE (VarInfo.Header.DataSize); + NewVarSize = HEADER_ALIGN (NewVarSize); + + if (VarSize < NewVarSize) { + if (VarDig->Flags.Freeable == TRUE) { + FreePool (GET_BUFR (VarDig->CacheIndex)); + } + + Buffer = AllocatePool (NewVarSize); + if (Buffer != NULL) { + VarDig->CacheIndex = (EFI_PHYSICAL_ADDRESS)(UINTN)Buffer; + } else { + ASSERT (FALSE); + return EFI_ABORTED; + } + } + + // + // Update cached copy. + // + CopyMem (GET_BUFR (VarDig->CacheIndex), NewVariable, NewVarSize); + + // + // Check if there is consecutive variable as part of the write or + // is it just the MetaDataHmacVar variable + // + if (NewVarSize < VariableSize) { + // + // Advance to consecutive Variable + // + NewVariable = GET_BUFR (GET_ADRS (NewVariable) + NewVarSize); + + // + // Update the StoreIndex of consecutive Variable + // + ZeroMem ((VOID *)&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = NULL; + VarInfo.StoreIndex = VarDig->StoreIndex; + VarInfo.Flags.Auth = Global->Flags.Auth; + Status = ContextIn->GetNextVariableInfo (&VarInfo); + StoreIndex = VarInfo.StoreIndex; + + // + // The new StoreIndex does not exist in the variable digest. + // It is yet to be updated. + // Therefore, find variable by Name & Guid instead. + // + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarDig = FindVariableInternal (Global, &VarInfo, FALSE); + + // + // Check if cache pool need re-allocation due to variable size increase + // + VarSize = VARIABLE_HEADER_SIZE (VarDig->Flags.Auth); + VarSize += VarDig->NameSize + GET_PAD_SIZE (VarDig->NameSize); + VarSize += VarDig->DataSize + GET_PAD_SIZE (VarDig->DataSize); + VarSize = HEADER_ALIGN (VarSize); + + NewVarSize = VARIABLE_HEADER_SIZE (VarInfo.Flags.Auth); + NewVarSize += VarInfo.Header.NameSize + GET_PAD_SIZE (VarInfo.Header.NameSize); + NewVarSize += VarInfo.Header.DataSize + GET_PAD_SIZE (VarInfo.Header.DataSize); + NewVarSize = HEADER_ALIGN (NewVarSize); + + if (VarSize < NewVarSize) { + if (VarDig->Flags.Freeable == TRUE) { + FreePool (GET_BUFR (VarDig->CacheIndex)); + } + + Buffer = AllocatePool (NewVarSize); + if (Buffer != NULL) { + VarDig->CacheIndex = (EFI_PHYSICAL_ADDRESS)(UINTN)Buffer; + } else { + ASSERT (FALSE); + return EFI_ABORTED; + } + } + + // + // Update cached copy. + // + CopyMem (GET_BUFR (VarDig->CacheIndex), NewVariable, NewVarSize); + VarDig->StoreIndex = StoreIndex; + } + + return Status; +} + +/** + Refresh variable information changed by variable service. + + @param[in] Variable Pointer to buffer of the updated variable. + @param[in] VariableSize Size of variable pointed by Variable. + @param[in] StoreIndex New index of the variable in store. + @param[in] RefreshData Flag to indicate if the variable has been updated. + + @return EFI_SUCCESS No error occurred in updating. + @return EFI_NOT_FOUND The given variable was not found in + ProtectedVariableLib. +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibRefresh ( + IN VARIABLE_HEADER *Variable, + IN UINTN VariableSize, + IN UINT64 StoreIndex, + IN BOOLEAN RefreshData + ) +{ + EFI_STATUS Status; + PROTECTED_VARIABLE_INFO VarInfo; + PROTECTED_VARIABLE_GLOBAL *Global; + VARIABLE_DIGEST *VarDig; + + (VOID)GetProtectedVariableGlobal (&Global); + + ZeroMem ((VOID *)&VarInfo, sizeof (VarInfo)); + VarInfo.Buffer = Variable; + VarInfo.StoreIndex = VAR_INDEX_INVALID; + VarInfo.Flags.Auth = Global->Flags.Auth; + + Status = GET_CNTX (Global)->GetVariableInfo (&VarInfo); + ASSERT_EFI_ERROR (Status); + + VarDig = FindVariableInternal (Global, &VarInfo, FALSE); + if (VarDig == NULL) { + ASSERT (VarDig != NULL); + return EFI_NOT_FOUND; + } + + if (StoreIndex != VAR_INDEX_INVALID) { + VarDig->StoreIndex = StoreIndex; + } + + if (RefreshData) { + if (VarDig->CacheIndex == VAR_INDEX_INVALID) { + VarDig->CacheIndex = (EFI_PHYSICAL_ADDRESS)(UINTN) + AllocatePool (MAX_VARIABLE_SIZE); + } + + CopyMem (GET_BUFR (VarDig->CacheIndex), Variable, VariableSize); + } + + // + // Information should stay the same other than following ones. + // + VarDig->State = VarInfo.Header.State; + VarDig->DataSize = (UINT32)VarInfo.Header.DataSize; + + return EFI_SUCCESS; +} + +/** + + Determine if the variable is the HMAC variable + + @param VariableName Pointer to variable name. + + @return TRUE Variable is HMAC variable + @return FALSE Variable is not HMAC variable + +**/ +BOOLEAN +ProtectedVariableLibIsHmac ( + IN CHAR16 *VariableName + ) +{ + INTN Result; + + Result = StrnCmp ( + METADATA_HMAC_VARIABLE_NAME, + VariableName, + METADATA_HMAC_VARIABLE_NAME_SIZE + ); + + if (Result == 0) { + return TRUE; + } + + return FALSE; +} diff --git a/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmRuntime.c b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmRuntime.c new file mode 100644 index 000000000000..4591d1cd59e5 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmRuntime.c @@ -0,0 +1,233 @@ +/** @file + Implemention of ProtectedVariableLib for BootService/Runtime use cases. + +Copyright (c) 2022, Intel Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include + +#include "Library/MemoryAllocationLib.h" +#include "Library/UefiBootServicesTableLib.h" +#include "Library/UefiRuntimeLib.h" +#include "ProtectedVariableInternal.h" + +EFI_EVENT mVaChangeEvent = NULL; + +PROTECTED_VARIABLE_CONTEXT_IN mRtVariableContextIn = { + PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION, + sizeof (PROTECTED_VARIABLE_CONTEXT_IN), + 0, + FromRuntimeModule, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +PROTECTED_VARIABLE_GLOBAL mRtProtectedVariableGlobal = { + PROTECTED_VARIABLE_CONTEXT_OUT_STRUCT_VERSION, + sizeof (PROTECTED_VARIABLE_GLOBAL), + { 0 }, + { 0 }, + 0, + 0, + 0, + 0, + 0, + 0, + { 0, 0, 0 }, + 0, + 0, + { 0, 0, 0, 0, 0, 0} +}; + +/** + + Get global data structure used to process protected variable. + + @param[out] Global Pointer to global configuration data. + + @retval EFI_SUCCESS Get requested structure successfully. + +**/ +EFI_STATUS +EFIAPI +GetProtectedVariableGlobal ( + OUT PROTECTED_VARIABLE_GLOBAL **Global OPTIONAL + ) +{ + if (Global != NULL) { + mRtProtectedVariableGlobal.ContextIn = GET_ADRS (&mRtVariableContextIn); + *Global = &mRtProtectedVariableGlobal; + } + + return EFI_SUCCESS; +} + +/** + Notification function of EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE. + + This is a notification function registered on EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE event. + It convers pointer to new virtual address. + + @param[in] Event Event whose notification function is being invoked. + @param[in] Context Pointer to the notification function's context. + +**/ +VOID +EFIAPI +VirtualAddressChangeEvent ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + if (mRtVariableContextIn.FindVariableSmm != NULL) { + EfiConvertPointer (0x0, (VOID **)&mRtVariableContextIn.FindVariableSmm); + } + + if (mRtVariableContextIn.GetVariableInfo != NULL) { + EfiConvertPointer (0x0, (VOID **)&mRtVariableContextIn.GetVariableInfo); + } + + if (mRtVariableContextIn.GetNextVariableInfo != NULL) { + EfiConvertPointer (0x0, (VOID **)&mRtVariableContextIn.GetNextVariableInfo); + } + + if (mRtVariableContextIn.UpdateVariableStore != NULL) { + EfiConvertPointer (0x0, (VOID **)&mRtVariableContextIn.UpdateVariableStore); + } + + EfiConvertPointer (0x0, (VOID **)&mRtVariableContextIn); + if (mRtProtectedVariableGlobal.VariableCache != 0) { + EfiConvertPointer (0x0, (VOID **)&mRtProtectedVariableGlobal.VariableCache); + } + + EfiConvertPointer (0x0, (VOID **)&mRtProtectedVariableGlobal); +} + +/** + + Initialization for protected variable services. + + If this initialization failed upon any error, the whole variable services + should not be used. A system reset might be needed to re-construct NV + variable storage to be the default state. + + @param[in] ContextIn Pointer to variable service context needed by + protected variable. + + @retval EFI_SUCCESS Protected variable services are ready. + @retval EFI_INVALID_PARAMETER If ContextIn == NULL or something missing or + mismatching in the content in ContextIn. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibInitialize ( + IN PROTECTED_VARIABLE_CONTEXT_IN *ContextIn + ) +{ + if ( (ContextIn == NULL) + || (ContextIn->StructVersion != PROTECTED_VARIABLE_CONTEXT_IN_STRUCT_VERSION) + || (ContextIn->FindVariableSmm == NULL) + || (ContextIn->GetVariableInfo == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + CopyMem (&mRtVariableContextIn, ContextIn, sizeof (mRtVariableContextIn)); + + // + // Register the event to convert the pointer for runtime. + // + gBS->CreateEventEx ( + EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + VirtualAddressChangeEvent, + NULL, + &gEfiEventVirtualAddressChangeGuid, + &mVaChangeEvent + ); + + return EFI_SUCCESS; +} + +/** + + Prepare for variable update. + + Not supported in DXE phase. + + @retval EFI_UNSUPPORTED Updating variable is not supported. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibWriteInit ( + VOID + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} + +/** + + Update a variable with protection provided by this library. + + Not supported in DXE phase. + + @param[in,out] CurrVariable Variable to be updated. It's NULL if + adding a new variable. + @param[in] CurrVariableInDel In-delete-transition copy of updating variable. + @param[in,out] NewVariable Buffer of new variable data or + Buffer of "MetaDataHmacVar" and new variable (encrypted). + @param[in,out] NewVariableSize Size of NewVariable or + Size of (encrypted) NewVariable and "MetaDataHmacVar". + + @retval EFI_UNSUPPORTED Not support updating variable. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibUpdate ( + IN OUT VARIABLE_HEADER *CurrVariable, + IN VARIABLE_HEADER *CurrVariableInDel, + IN OUT VARIABLE_HEADER *NewVariable, + IN OUT UINTN *NewVariableSize + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} + +/** + + Finalize a variable updating after it's written to NV variable storage + successfully. + + (Not supported for BootService/Runtime use cases.) + + @param[in] NewVariable Buffer of new variables and MetaDataHmacVar. + @param[in] VariableSize Size of buffer pointed by NewVariable. + @param[in] StoreIndex StoreIndex to NV variable storage from where the new + variable and MetaDataHmacVar have been written. + + @retval EFI_UNSUPPORTED Not supported for BootService/Runtime use cases. + +**/ +EFI_STATUS +EFIAPI +ProtectedVariableLibWriteFinal ( + IN VARIABLE_HEADER *NewVariable, + IN UINTN VariableSize, + IN UINT64 StoreIndex + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} -- 2.26.2.windows.1