blob: 5de46133bb261008d14b5e20fc3c24d1084bac0a [file] [log] [blame]
/** @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 <Library/VarCheckLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/SafeIntLib.h>
#include <Library/MmServicesTableLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/MmCommunication.h>
#include <Protocol/VariablePolicy.h>
#include <Library/VariablePolicyLib.h>
#include <Guid/VarCheckPolicyMmi.h>
#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 *PolicyCommmHeader;
VAR_CHECK_POLICY_COMM_HEADER *InternalPolicyCommmHeader;
VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS *IsEnabledParams;
VAR_CHECK_POLICY_COMM_DUMP_PARAMS *DumpParamsIn;
VAR_CHECK_POLICY_COMM_DUMP_PARAMS *DumpParamsOut;
UINT8 *DumpInputBuffer;
UINT8 *DumpOutputBuffer;
UINTN DumpTotalPages;
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);
PolicyCommmHeader = CommBuffer;
InternalPolicyCommmHeader = InternalCommBuffer;
// Check the revision and the signature of the comm header.
if ((InternalPolicyCommmHeader->Signature != VAR_CHECK_POLICY_COMM_SIG) ||
(InternalPolicyCommmHeader->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.
PolicyCommmHeader->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) && (InternalPolicyCommmHeader->Command != mCurrentPaginationCommand)) {
FreePool (mPaginationCache);
mPaginationCache = NULL;
mPaginationCacheSize = 0;
mCurrentPaginationCommand = 0;
}
//
// Now we can process the command as it was sent.
//
PolicyCommmHeader->Result = EFI_ABORTED; // Set a default return for incomplete commands.
switch (InternalPolicyCommmHeader->Command) {
case VAR_CHECK_POLICY_COMMAND_DISABLE:
PolicyCommmHeader->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));
PolicyCommmHeader->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 ();
PolicyCommmHeader->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));
PolicyCommmHeader->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__));
PolicyCommmHeader->Result = EFI_INVALID_PARAMETER;
break;
}
PolicyCommmHeader->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));
PolicyCommmHeader->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 *)(InternalPolicyCommmHeader + 1);
DumpParamsOut = (VAR_CHECK_POLICY_COMM_DUMP_PARAMS *)(PolicyCommmHeader + 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.
PolicyCommmHeader->Result = SubCommandStatus;
break;
case VAR_CHECK_POLICY_COMMAND_LOCK:
PolicyCommmHeader->Result = LockVariablePolicy ();
break;
default:
// Mark unknown requested command as EFI_UNSUPPORTED.
DEBUG ((DEBUG_INFO, "%a - Invalid command requested! %d\n", __func__, PolicyCommmHeader->Command));
PolicyCommmHeader->Result = EFI_UNSUPPORTED;
break;
}
DEBUG ((
DEBUG_VERBOSE,
"%a - Command %d returning %r.\n",
__func__,
PolicyCommmHeader->Command,
PolicyCommmHeader->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;
}