--- /dev/null
+/** @file -- VarCheckPolicyLib.c\r
+This is a NULL library instance that leverages the VarCheck interface\r
+and the business logic behind the VariablePolicy code to make its decisions.\r
+\r
+Copyright (c) Microsoft Corporation.\r
+SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include <Library/VarCheckLib.h>\r
+#include <Library/BaseLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/SafeIntLib.h>\r
+#include <Library/MmServicesTableLib.h>\r
+#include <Library/SmmMemLib.h>\r
+#include <Library/BaseMemoryLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+\r
+#include <Protocol/MmCommunication.h>\r
+\r
+#include <Protocol/VariablePolicy.h>\r
+#include <Library/VariablePolicyLib.h>\r
+\r
+#include <Guid/VarCheckPolicyMmi.h>\r
+\r
+//================================================\r
+// As a VarCheck library, we're linked into the VariableServices\r
+// and may not be able to call them indirectly. To get around this,\r
+// use the internal GetVariable function to query the variable store.\r
+//================================================\r
+EFI_STATUS\r
+EFIAPI\r
+VariableServiceGetVariable (\r
+ IN CHAR16 *VariableName,\r
+ IN EFI_GUID *VendorGuid,\r
+ OUT UINT32 *Attributes OPTIONAL,\r
+ IN OUT UINTN *DataSize,\r
+ OUT VOID *Data\r
+ );\r
+\r
+\r
+UINT8 mSecurityEvalBuffer[VAR_CHECK_POLICY_MM_COMM_BUFFER_SIZE];\r
+\r
+// Pagination Cache Variables\r
+UINT8 *mPaginationCache = NULL;\r
+UINTN mPaginationCacheSize = 0;\r
+UINT32 mCurrentPaginationCommand = 0;\r
+\r
+\r
+/**\r
+ MM Communication Handler to recieve commands from the DXE protocol for\r
+ Variable Policies. This communication channel is used to register new policies\r
+ and poll and toggle the enforcement of variable policies.\r
+\r
+ @param[in] DispatchHandle All parameters standard to MM communications convention.\r
+ @param[in] RegisterContext All parameters standard to MM communications convention.\r
+ @param[in,out] CommBuffer All parameters standard to MM communications convention.\r
+ @param[in,out] CommBufferSize All parameters standard to MM communications convention.\r
+\r
+ @retval EFI_SUCCESS\r
+ @retval EFI_INVALID_PARAMETER CommBuffer or CommBufferSize is null pointer.\r
+ @retval EFI_INVALID_PARAMETER CommBuffer size is wrong.\r
+ @retval EFI_INVALID_PARAMETER Revision or signature don't match.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+VarCheckPolicyLibMmiHandler (\r
+ IN EFI_HANDLE DispatchHandle,\r
+ IN CONST VOID *RegisterContext,\r
+ IN OUT VOID *CommBuffer,\r
+ IN OUT UINTN *CommBufferSize\r
+ )\r
+{\r
+ UINTN InternalCommBufferSize;\r
+ VOID *InternalCommBuffer;\r
+ EFI_STATUS Status;\r
+ EFI_STATUS SubCommandStatus;\r
+ VAR_CHECK_POLICY_COMM_HEADER *PolicyCommmHeader;\r
+ VAR_CHECK_POLICY_COMM_HEADER *InternalPolicyCommmHeader;\r
+ VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS *IsEnabledParams;\r
+ VAR_CHECK_POLICY_COMM_DUMP_PARAMS *DumpParamsIn;\r
+ VAR_CHECK_POLICY_COMM_DUMP_PARAMS *DumpParamsOut;\r
+ UINT8 *DumpInputBuffer;\r
+ UINT8 *DumpOutputBuffer;\r
+ UINTN DumpTotalPages;\r
+ VARIABLE_POLICY_ENTRY *PolicyEntry;\r
+ UINTN ExpectedSize;\r
+ UINT32 TempSize;\r
+\r
+ Status = EFI_SUCCESS;\r
+\r
+ //\r
+ // Validate some input parameters.\r
+ //\r
+ // If either of the pointers are NULL, we can't proceed.\r
+ if (CommBuffer == NULL || CommBufferSize == NULL) {\r
+ DEBUG(( DEBUG_INFO, "%a - Invalid comm buffer pointers!\n", __FUNCTION__ ));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ // Make sure that the buffer does not overlap SMM.\r
+ // This should be covered by the SmiManage infrastructure, but just to be safe...\r
+ InternalCommBufferSize = *CommBufferSize;\r
+ if (InternalCommBufferSize > VAR_CHECK_POLICY_MM_COMM_BUFFER_SIZE || !SmmIsBufferOutsideSmmValid((UINTN)CommBuffer, (UINT64)InternalCommBufferSize)) {\r
+ DEBUG ((DEBUG_ERROR, "%a - Invalid CommBuffer supplied! 0x%016lX[0x%016lX]\n", __FUNCTION__, CommBuffer, InternalCommBufferSize));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+ // If the size does not meet a minimum threshold, we cannot proceed.\r
+ ExpectedSize = sizeof(VAR_CHECK_POLICY_COMM_HEADER);\r
+ if (InternalCommBufferSize < ExpectedSize) {\r
+ DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));\r
+ return EFI_INVALID_PARAMETER;\r
+ }\r
+\r
+ //\r
+ // Before proceeding any further, copy the buffer internally so that we can compare\r
+ // without worrying about TOCTOU.\r
+ //\r
+ InternalCommBuffer = &mSecurityEvalBuffer[0];\r
+ CopyMem(InternalCommBuffer, CommBuffer, InternalCommBufferSize);\r
+ PolicyCommmHeader = CommBuffer;\r
+ InternalPolicyCommmHeader = InternalCommBuffer;\r
+ // Check the revision and the signature of the comm header.\r
+ if (InternalPolicyCommmHeader->Signature != VAR_CHECK_POLICY_COMM_SIG ||\r
+ InternalPolicyCommmHeader->Revision != VAR_CHECK_POLICY_COMM_REVISION) {\r
+ DEBUG(( DEBUG_INFO, "%a - Signature or revision are incorrect!\n", __FUNCTION__ ));\r
+ // We have verified the buffer is not null and have enough size to hold Result field.\r
+ PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;\r
+ return EFI_SUCCESS;\r
+ }\r
+\r
+ // If we're in the middle of a paginated dump and any other command is sent,\r
+ // pagination cache must be cleared.\r
+ if (mPaginationCache != NULL && InternalPolicyCommmHeader->Command != mCurrentPaginationCommand) {\r
+ FreePool (mPaginationCache);\r
+ mPaginationCache = NULL;\r
+ mPaginationCacheSize = 0;\r
+ mCurrentPaginationCommand = 0;\r
+ }\r
+\r
+ //\r
+ // Now we can process the command as it was sent.\r
+ //\r
+ PolicyCommmHeader->Result = EFI_ABORTED; // Set a default return for incomplete commands.\r
+ switch(InternalPolicyCommmHeader->Command) {\r
+ case VAR_CHECK_POLICY_COMMAND_DISABLE:\r
+ PolicyCommmHeader->Result = DisableVariablePolicy();\r
+ break;\r
+\r
+ case VAR_CHECK_POLICY_COMMAND_IS_ENABLED:\r
+ // Make sure that we're dealing with a reasonable size.\r
+ // This add should be safe because these are fixed sizes so far.\r
+ ExpectedSize += sizeof(VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS);\r
+ if (InternalCommBufferSize < ExpectedSize) {\r
+ DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));\r
+ PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;\r
+ break;\r
+ }\r
+\r
+ // Now that we know we've got a valid size, we can fill in the rest of the data.\r
+ IsEnabledParams = (VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS*)((UINT8*)CommBuffer + sizeof(VAR_CHECK_POLICY_COMM_HEADER));\r
+ IsEnabledParams->State = IsVariablePolicyEnabled();\r
+ PolicyCommmHeader->Result = EFI_SUCCESS;\r
+ break;\r
+\r
+ case VAR_CHECK_POLICY_COMMAND_REGISTER:\r
+ // Make sure that we're dealing with a reasonable size.\r
+ // This add should be safe because these are fixed sizes so far.\r
+ ExpectedSize += sizeof(VARIABLE_POLICY_ENTRY);\r
+ if (InternalCommBufferSize < ExpectedSize) {\r
+ DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));\r
+ PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;\r
+ break;\r
+ }\r
+\r
+ // At the very least, we can assume that we're working with a valid policy entry.\r
+ // Time to compare its internal size.\r
+ PolicyEntry = (VARIABLE_POLICY_ENTRY*)((UINT8*)InternalCommBuffer + sizeof(VAR_CHECK_POLICY_COMM_HEADER));\r
+ if (PolicyEntry->Version != VARIABLE_POLICY_ENTRY_REVISION ||\r
+ PolicyEntry->Size < sizeof(VARIABLE_POLICY_ENTRY) ||\r
+ EFI_ERROR(SafeUintnAdd(sizeof(VAR_CHECK_POLICY_COMM_HEADER), PolicyEntry->Size, &ExpectedSize)) ||\r
+ InternalCommBufferSize < ExpectedSize) {\r
+ DEBUG(( DEBUG_INFO, "%a - Bad policy entry contents!\n", __FUNCTION__ ));\r
+ PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;\r
+ break;\r
+ }\r
+\r
+ PolicyCommmHeader->Result = RegisterVariablePolicy( PolicyEntry );\r
+ break;\r
+\r
+ case VAR_CHECK_POLICY_COMMAND_DUMP:\r
+ // Make sure that we're dealing with a reasonable size.\r
+ // This add should be safe because these are fixed sizes so far.\r
+ ExpectedSize += sizeof(VAR_CHECK_POLICY_COMM_DUMP_PARAMS) + VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;\r
+ if (InternalCommBufferSize < ExpectedSize) {\r
+ DEBUG(( DEBUG_INFO, "%a - Bad comm buffer size! %d < %d\n", __FUNCTION__, InternalCommBufferSize, ExpectedSize ));\r
+ PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;\r
+ break;\r
+ }\r
+\r
+ // Now that we know we've got a valid size, we can fill in the rest of the data.\r
+ DumpParamsIn = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS*)(InternalPolicyCommmHeader + 1);\r
+ DumpParamsOut = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS*)(PolicyCommmHeader + 1);\r
+\r
+ // If we're requesting the first page, initialize the cache and get the sizes.\r
+ if (DumpParamsIn->PageRequested == 0) {\r
+ if (mPaginationCache != NULL) {\r
+ FreePool (mPaginationCache);\r
+ mPaginationCache = NULL;\r
+ }\r
+\r
+ // Determine what the required size is going to be.\r
+ DumpParamsOut->TotalSize = 0;\r
+ DumpParamsOut->PageSize = 0;\r
+ DumpParamsOut->HasMore = FALSE;\r
+ SubCommandStatus = DumpVariablePolicy (NULL, &TempSize);\r
+ if (SubCommandStatus == EFI_BUFFER_TOO_SMALL && TempSize > 0) {\r
+ mCurrentPaginationCommand = VAR_CHECK_POLICY_COMMAND_DUMP;\r
+ mPaginationCacheSize = TempSize;\r
+ DumpParamsOut->TotalSize = TempSize;\r
+ mPaginationCache = AllocatePool (mPaginationCacheSize);\r
+ if (mPaginationCache == NULL) {\r
+ SubCommandStatus = EFI_OUT_OF_RESOURCES;\r
+ }\r
+ }\r
+\r
+ // If we've allocated our pagination cache, we're good to cache.\r
+ if (mPaginationCache != NULL) {\r
+ SubCommandStatus = DumpVariablePolicy (mPaginationCache, &TempSize);\r
+ }\r
+\r
+ // Populate the remaining fields and we can boogie.\r
+ if (!EFI_ERROR (SubCommandStatus) && mPaginationCache != NULL) {\r
+ DumpParamsOut->HasMore = TRUE;\r
+ }\r
+ } else if (mPaginationCache != NULL) {\r
+ DumpParamsOut->TotalSize = (UINT32)mPaginationCacheSize;\r
+ DumpOutputBuffer = (UINT8*)(DumpParamsOut + 1);\r
+\r
+ // Make sure that we don't over-index the cache.\r
+ DumpTotalPages = mPaginationCacheSize / VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;\r
+ if (mPaginationCacheSize % VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE != 0) {\r
+ DumpTotalPages++;\r
+ }\r
+ if (DumpParamsIn->PageRequested > DumpTotalPages) {\r
+ SubCommandStatus = EFI_INVALID_PARAMETER;\r
+ } else {\r
+ // Figure out how far into the page cache we need to go for our next page.\r
+ // We know the blind subtraction won't be bad because we already checked for page 0.\r
+ DumpInputBuffer = &mPaginationCache[VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE * (DumpParamsIn->PageRequested - 1)];\r
+ TempSize = VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;\r
+ // If we're getting the last page, adjust the PageSize.\r
+ if (DumpParamsIn->PageRequested == DumpTotalPages) {\r
+ TempSize = mPaginationCacheSize % VAR_CHECK_POLICY_MM_DUMP_BUFFER_SIZE;\r
+ }\r
+ CopyMem (DumpOutputBuffer, DumpInputBuffer, TempSize);\r
+ DumpParamsOut->PageSize = TempSize;\r
+ // If we just got the last page, settle up the cache.\r
+ if (DumpParamsIn->PageRequested == DumpTotalPages) {\r
+ DumpParamsOut->HasMore = FALSE;\r
+ FreePool (mPaginationCache);\r
+ mPaginationCache = NULL;\r
+ mPaginationCacheSize = 0;\r
+ mCurrentPaginationCommand = 0;\r
+ // Otherwise, we could do more here.\r
+ } else {\r
+ DumpParamsOut->HasMore = TRUE;\r
+ }\r
+\r
+ // If we made it this far, we're basically good.\r
+ SubCommandStatus = EFI_SUCCESS;\r
+ }\r
+ // If we've requested any other page than 0 and the cache is empty, we must have timed out.\r
+ } else {\r
+ DumpParamsOut->TotalSize = 0;\r
+ DumpParamsOut->PageSize = 0;\r
+ DumpParamsOut->HasMore = FALSE;\r
+ SubCommandStatus = EFI_TIMEOUT;\r
+ }\r
+\r
+ // There's currently no use for this, but it shouldn't be hard to implement.\r
+ PolicyCommmHeader->Result = SubCommandStatus;\r
+ break;\r
+\r
+ case VAR_CHECK_POLICY_COMMAND_LOCK:\r
+ PolicyCommmHeader->Result = LockVariablePolicy();\r
+ break;\r
+\r
+ default:\r
+ // Mark unknown requested command as EFI_UNSUPPORTED.\r
+ DEBUG(( DEBUG_INFO, "%a - Invalid command requested! %d\n", __FUNCTION__, PolicyCommmHeader->Command ));\r
+ PolicyCommmHeader->Result = EFI_UNSUPPORTED;\r
+ break;\r
+ }\r
+\r
+ DEBUG(( DEBUG_VERBOSE, "%a - Command %d returning %r.\n", __FUNCTION__,\r
+ PolicyCommmHeader->Command, PolicyCommmHeader->Result ));\r
+\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+ Constructor function of VarCheckPolicyLib to register VarCheck handler and\r
+ SW MMI handlers.\r
+\r
+ @param[in] ImageHandle The firmware allocated handle for the EFI image.\r
+ @param[in] SystemTable A pointer to the EFI System Table.\r
+\r
+ @retval EFI_SUCCESS The constructor executed correctly.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+VarCheckPolicyLibConstructor (\r
+ IN EFI_HANDLE ImageHandle,\r
+ IN EFI_SYSTEM_TABLE *SystemTable\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ EFI_HANDLE DiscardedHandle;\r
+\r
+ // Initialize the business logic with the internal GetVariable handler.\r
+ Status = InitVariablePolicyLib( VariableServiceGetVariable );\r
+\r
+ // Only proceed with init if the business logic could be initialized.\r
+ if (!EFI_ERROR( Status )) {\r
+ // Register the VarCheck handler for SetVariable filtering.\r
+ // Forward the check to the business logic of the library.\r
+ VarCheckLibRegisterSetVariableCheckHandler( ValidateSetVariable );\r
+\r
+ // Register the MMI handlers for receiving policy commands.\r
+ DiscardedHandle = NULL;\r
+ Status = gMmst->MmiHandlerRegister( VarCheckPolicyLibMmiHandler,\r
+ &gVarCheckPolicyLibMmiHandlerGuid,\r
+ &DiscardedHandle );\r
+ }\r
+ // Otherwise, there's not much we can do.\r
+ else {\r
+ DEBUG(( DEBUG_ERROR, "%a - Cannot Initialize VariablePolicyLib! %r\n", __FUNCTION__, Status ));\r
+ ASSERT_EFI_ERROR( Status );\r
+ }\r
+\r
+ return Status;\r
+}\r