From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mga05.intel.com (mga05.intel.com [192.55.52.43]) by mx.groups.io with SMTP id smtpd.web08.3540.1660200857329267552 for ; Wed, 10 Aug 2022 23:54:19 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="unable to parse pub key" header.i=@intel.com header.s=intel header.b=AlK+ihMU; spf=pass (domain: intel.com, ip: 192.55.52.43, 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=1660200859; x=1691736859; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=6+jfnft0olQk3qmedzWyXCczH4J9hZoMUiSj0iP8CDc=; b=AlK+ihMUHCOc+sk3SDf3z8CNDA8AUdgXyIAV4rIlffKtXNY9FyK5rxaH ZqP3j6tqw3kBWFVbq6S014qx4RnzO5M0z72c7TEtxveA+bPE1sd2Mcepg 826xpBe/jFjs4CRSm4uYaHJB2vQO9NjWrTiFtEZ2Iw//HUPfIcX2ZpVt/ rimJ1ymp/s/Gb3+w18UFQgtiBOcrDhNwZl63+f2yq0UUXR0BX237kc75z hmq5rbHSJRavjp6ePRv6s7Lqm4n6AEP7OZ+ZVIVaYZKV4X4ixYR7kxFCO D6qifyMKJu4Ioc/XUwxnTsKZLRvyxRmRaONV91PlR3zFIpnqxfZQjxJWb A==; X-IronPort-AV: E=McAfee;i="6400,9594,10435"; a="377565853" X-IronPort-AV: E=Sophos;i="5.93,228,1654585200"; d="scan'208";a="377565853" Received: from fmsmga004.fm.intel.com ([10.253.24.48]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 10 Aug 2022 23:54:18 -0700 X-IronPort-AV: E=Sophos;i="5.93,228,1654585200"; d="scan'208";a="673586433" Received: from moisespe-mobl.amr.corp.intel.com (HELO jvang-mobl.amr.corp.intel.com) ([10.212.171.64]) by fmsmga004-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 10 Aug 2022 23:54:17 -0700 From: "Judah Vang" To: devel@edk2.groups.io Cc: Jian J Wang , Jiewen Yao , Min Xu , Nishant C Mistry Subject: [PATCH v4 18/28] SecurityPkg: Add Protected Variable Services Date: Wed, 10 Aug 2022 23:53:27 -0700 Message-Id: <20220811065337.2068-19-judah.vang@intel.com> X-Mailer: git-send-email 2.35.1.windows.2 In-Reply-To: <20220811065337.2068-1-judah.vang@intel.com> References: <20220811065337.2068-1-judah.vang@intel.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit REF: https://bugzilla.tianocore.org/show_bug.cgi?id=2594 V3: Change placement of buffer used for confidentiality crypto operation to fix an issue when enabling confidentiality. Remove un-needed increment of monotonic counter. V1: 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. This module prevents UEFI variable tampering. It provides variable integrity and confidentiality. Cc: Jian J Wang Cc: Jiewen Yao Cc: Min Xu Cc: Nishant C Mistry Signed-off-by: Jian J Wang Signed-off-by: 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 | 2103 ++++++++++++++++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableDxe.c | 163 ++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariablePei.c | 1327 ++++++++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmm.c | 209 ++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmDxeCommon.c | 967 +++++++++ SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmRuntime.c | 233 +++ 11 files changed, 5874 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..456c871a4561 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableCommon.c @@ -0,0 +1,2103 @@ +/** @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. + // + BufferSize = VarInfo->PlainDataSize; + 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; + } + + // + // Don't do decryption if the caller provided buffer is too small + // Simply return the real Plain Data Size via VarInfo->PlainDataSize + // + if (BufferSize < VarInfo->PlainDataSize) { + return EFI_BUFFER_TOO_SMALL; + } + + // + // 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) || ((BufferSize) < VarInfo->PlainDataSize)) { + // + // Return with caller provided buffer with zero DataSize + // + VarInfo->PlainData = Buffer; + return Status; + } + + // + // 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; + VarInfo.PlainDataSize = *DataSize; + + 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); + *DataSize = VarInfo.PlainDataSize; + if (EFI_ERROR (Status)) { + return Status; + } + + 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; + } + } + + VarInfo.PlainDataSize = (UINT32)*DataSize; + + // + // Decrypt the data, if necessary. + // + Status = ProtectedVariableLibGetDataInternal (Global, &VarInfo); + *DataSize = VarInfo.PlainDataSize; + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_INFO, + "%a: %d Exit(). VariableName = %s, VariableGuid = 0x%g, DataSize = 0x%X, Data Buffer = 0x%lX, Status = %r\n", + __FUNCTION__, + __LINE__, + VariableName, + VariableGuid, + *DataSize, + Data, + Status + )); + return Status; + } + + 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..8b5ccb83e32d --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariablePei.c @@ -0,0 +1,1327 @@ +/** @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); + + // + // Allocate last Var buffer for confidentiality crypto operation + // + VarSize = (Global->VariableNumber + 1) * MAX_VARIABLE_SIZE; + Buffer = AllocateGlobalBuffer (VarSize, TRUE); + + // + // 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..8472fc8a33c7 --- /dev/null +++ b/SecurityPkg/Library/ProtectedVariableLib/ProtectedVariableSmmDxeCommon.c @@ -0,0 +1,967 @@ +/** @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 { + 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.35.1.windows.2