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