/** @file
TCG MOR (Memory Overwrite Request) Lock Control support (SMM version).
This module initilizes MemoryOverwriteRequestControlLock variable.
This module adds Variable Hook and check MemoryOverwriteRequestControlLock.
Copyright (c) 2016 - 2018, Intel Corporation. All rights reserved.
Copyright (c) Microsoft Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include "Variable.h"
#include
#include
#include
typedef struct {
CHAR16 *VariableName;
EFI_GUID *VendorGuid;
} VARIABLE_TYPE;
VARIABLE_TYPE mMorVariableType[] = {
{ MEMORY_OVERWRITE_REQUEST_VARIABLE_NAME, &gEfiMemoryOverwriteControlDataGuid },
{ MEMORY_OVERWRITE_REQUEST_CONTROL_LOCK_NAME, &gEfiMemoryOverwriteRequestControlLockGuid },
};
BOOLEAN mMorPassThru = FALSE;
#define MOR_LOCK_DATA_UNLOCKED 0x0
#define MOR_LOCK_DATA_LOCKED_WITHOUT_KEY 0x1
#define MOR_LOCK_DATA_LOCKED_WITH_KEY 0x2
#define MOR_LOCK_V1_SIZE 1
#define MOR_LOCK_V2_KEY_SIZE 8
typedef enum {
MorLockStateUnlocked = 0,
MorLockStateLocked = 1,
} MOR_LOCK_STATE;
BOOLEAN mMorLockInitializationRequired = FALSE;
UINT8 mMorLockKey[MOR_LOCK_V2_KEY_SIZE];
BOOLEAN mMorLockKeyEmpty = TRUE;
BOOLEAN mMorLockPassThru = FALSE;
MOR_LOCK_STATE mMorLockState = MorLockStateUnlocked;
/**
Returns if this is MOR related variable.
@param VariableName the name of the vendor's variable, it's a Null-Terminated Unicode String
@param VendorGuid Unify identifier for vendor.
@retval TRUE The variable is MOR related.
@retval FALSE The variable is NOT MOR related.
**/
BOOLEAN
IsAnyMorVariable (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid
)
{
UINTN Index;
for (Index = 0; Index < sizeof (mMorVariableType)/sizeof (mMorVariableType[0]); Index++) {
if ((StrCmp (VariableName, mMorVariableType[Index].VariableName) == 0) &&
(CompareGuid (VendorGuid, mMorVariableType[Index].VendorGuid)))
{
return TRUE;
}
}
return FALSE;
}
/**
Returns if this is MOR lock variable.
@param VariableName the name of the vendor's variable, it's a Null-Terminated Unicode String
@param VendorGuid Unify identifier for vendor.
@retval TRUE The variable is MOR lock variable.
@retval FALSE The variable is NOT MOR lock variable.
**/
BOOLEAN
IsMorLockVariable (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid
)
{
if ((StrCmp (VariableName, MEMORY_OVERWRITE_REQUEST_CONTROL_LOCK_NAME) == 0) &&
(CompareGuid (VendorGuid, &gEfiMemoryOverwriteRequestControlLockGuid)))
{
return TRUE;
}
return FALSE;
}
/**
Set MOR lock variable.
@param Data MOR Lock variable data.
@retval EFI_SUCCESS The firmware has successfully stored the variable and its data as
defined by the Attributes.
@retval EFI_INVALID_PARAMETER An invalid combination of attribute bits was supplied, or the
DataSize exceeds the maximum allowed.
@retval EFI_INVALID_PARAMETER VariableName is an empty Unicode string.
@retval EFI_OUT_OF_RESOURCES Not enough storage is available to hold the variable and its data.
@retval EFI_DEVICE_ERROR The variable could not be saved due to a hardware failure.
@retval EFI_WRITE_PROTECTED The variable in question is read-only.
@retval EFI_WRITE_PROTECTED The variable in question cannot be deleted.
@retval EFI_SECURITY_VIOLATION The variable could not be written due to EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
set but the AuthInfo does NOT pass the validation check carried
out by the firmware.
@retval EFI_NOT_FOUND The variable trying to be updated or deleted was not found.
**/
EFI_STATUS
SetMorLockVariable (
IN UINT8 Data
)
{
EFI_STATUS Status;
mMorLockPassThru = TRUE;
Status = VariableServiceSetVariable (
MEMORY_OVERWRITE_REQUEST_CONTROL_LOCK_NAME,
&gEfiMemoryOverwriteRequestControlLockGuid,
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
sizeof (Data),
&Data
);
mMorLockPassThru = FALSE;
return Status;
}
/**
This service is an MorLock checker handler for the SetVariable().
@param VariableName the name of the vendor's variable, as a
Null-Terminated Unicode String
@param VendorGuid Unify identifier for vendor.
@param Attributes Point to memory location to return the attributes of variable. If the point
is NULL, the parameter would be ignored.
@param DataSize The size in bytes of Data-Buffer.
@param Data Point to the content of the variable.
@retval EFI_SUCCESS The MorLock check pass, and Variable driver can store the variable data.
@retval EFI_INVALID_PARAMETER The MorLock data or data size or attributes is not allowed.
@retval EFI_ACCESS_DENIED The MorLock is locked.
@retval EFI_WRITE_PROTECTED The MorLock deletion is not allowed.
@retval EFI_ALREADY_STARTED The MorLock variable is handled inside this function.
Variable driver can just return EFI_SUCCESS.
**/
EFI_STATUS
SetVariableCheckHandlerMorLock (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN UINT32 Attributes,
IN UINTN DataSize,
IN VOID *Data
)
{
EFI_STATUS Status;
//
// Basic Check
//
if ((Attributes == 0) || (DataSize == 0) || (Data == NULL)) {
//
// Permit deletion for passthru request, deny it otherwise.
//
return mMorLockPassThru ? EFI_SUCCESS : EFI_WRITE_PROTECTED;
}
if ((Attributes != (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS)) ||
((DataSize != MOR_LOCK_V1_SIZE) && (DataSize != MOR_LOCK_V2_KEY_SIZE)))
{
return EFI_INVALID_PARAMETER;
}
//
// Do not check if the request is passthru.
//
if (mMorLockPassThru) {
return EFI_SUCCESS;
}
if (mMorLockState == MorLockStateUnlocked) {
//
// In Unlocked State
//
if (DataSize == MOR_LOCK_V1_SIZE) {
//
// V1 - lock permanently
//
if (*(UINT8 *)Data == MOR_LOCK_DATA_UNLOCKED) {
//
// Unlock
//
Status = SetMorLockVariable (MOR_LOCK_DATA_UNLOCKED);
if (!EFI_ERROR (Status)) {
//
// return EFI_ALREADY_STARTED to skip variable set.
//
return EFI_ALREADY_STARTED;
} else {
//
// SetVar fail
//
return Status;
}
} else if (*(UINT8 *)Data == MOR_LOCK_DATA_LOCKED_WITHOUT_KEY) {
//
// Lock without key
//
Status = SetMorLockVariable (MOR_LOCK_DATA_LOCKED_WITHOUT_KEY);
if (!EFI_ERROR (Status)) {
//
// Lock success
//
mMorLockState = MorLockStateLocked;
//
// return EFI_ALREADY_STARTED to skip variable set.
//
return EFI_ALREADY_STARTED;
} else {
//
// SetVar fail
//
return Status;
}
} else {
return EFI_INVALID_PARAMETER;
}
} else if (DataSize == MOR_LOCK_V2_KEY_SIZE) {
//
// V2 lock and provision the key
//
//
// Need set here because the data value on flash is different
//
Status = SetMorLockVariable (MOR_LOCK_DATA_LOCKED_WITH_KEY);
if (EFI_ERROR (Status)) {
//
// SetVar fail, do not provision the key
//
return Status;
} else {
//
// Lock success, provision the key
//
mMorLockKeyEmpty = FALSE;
CopyMem (mMorLockKey, Data, MOR_LOCK_V2_KEY_SIZE);
mMorLockState = MorLockStateLocked;
//
// return EFI_ALREADY_STARTED to skip variable set.
//
return EFI_ALREADY_STARTED;
}
} else {
ASSERT (FALSE);
return EFI_OUT_OF_RESOURCES;
}
} else {
//
// In Locked State
//
if (mMorLockKeyEmpty || (DataSize != MOR_LOCK_V2_KEY_SIZE)) {
return EFI_ACCESS_DENIED;
}
if ((CompareMem (Data, mMorLockKey, MOR_LOCK_V2_KEY_SIZE) == 0)) {
//
// Key match - unlock
//
//
// Need set here because the data value on flash is different
//
Status = SetMorLockVariable (MOR_LOCK_DATA_UNLOCKED);
if (EFI_ERROR (Status)) {
//
// SetVar fail
//
return Status;
} else {
//
// Unlock Success
//
mMorLockState = MorLockStateUnlocked;
mMorLockKeyEmpty = TRUE;
ZeroMem (mMorLockKey, sizeof (mMorLockKey));
//
// return EFI_ALREADY_STARTED to skip variable set.
//
return EFI_ALREADY_STARTED;
}
} else {
//
// Key mismatch - Prevent Dictionary Attack
//
mMorLockState = MorLockStateLocked;
mMorLockKeyEmpty = TRUE;
ZeroMem (mMorLockKey, sizeof (mMorLockKey));
//
// Update value to reflect locked without key
//
Status = SetMorLockVariable (MOR_LOCK_DATA_LOCKED_WITHOUT_KEY);
ASSERT_EFI_ERROR (Status);
return EFI_ACCESS_DENIED;
}
}
}
/**
This service is an MOR/MorLock checker handler for the SetVariable().
@param[in] VariableName the name of the vendor's variable, as a
Null-Terminated Unicode String
@param[in] VendorGuid Unify identifier for vendor.
@param[in] Attributes Attributes bitmask to set for the variable.
@param[in] DataSize The size in bytes of Data-Buffer.
@param[in] Data Point to the content of the variable.
@retval EFI_SUCCESS The MOR/MorLock check pass, and Variable
driver can store the variable data.
@retval EFI_INVALID_PARAMETER The MOR/MorLock data or data size or
attributes is not allowed for MOR variable.
@retval EFI_ACCESS_DENIED The MOR/MorLock is locked.
@retval EFI_ALREADY_STARTED The MorLock variable is handled inside this
function. Variable driver can just return
EFI_SUCCESS.
**/
EFI_STATUS
SetVariableCheckHandlerMor (
IN CHAR16 *VariableName,
IN EFI_GUID *VendorGuid,
IN UINT32 Attributes,
IN UINTN DataSize,
IN VOID *Data
)
{
//
// do not handle non-MOR variable
//
if (!IsAnyMorVariable (VariableName, VendorGuid)) {
return EFI_SUCCESS;
}
// Permit deletion when policy is disabled.
if (!IsVariablePolicyEnabled () && ((Attributes == 0) || (DataSize == 0))) {
return EFI_SUCCESS;
}
//
// MorLock variable
//
if (IsMorLockVariable (VariableName, VendorGuid)) {
return SetVariableCheckHandlerMorLock (
VariableName,
VendorGuid,
Attributes,
DataSize,
Data
);
}
//
// Mor Variable
//
//
// Permit deletion for passthru request.
//
if (((Attributes == 0) || (DataSize == 0)) && mMorPassThru) {
return EFI_SUCCESS;
}
//
// Basic Check
//
if ((Attributes != (EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS)) ||
(DataSize != sizeof (UINT8)) ||
(Data == NULL))
{
return EFI_INVALID_PARAMETER;
}
if (mMorLockState == MorLockStateLocked) {
//
// If lock, deny access
//
return EFI_ACCESS_DENIED;
}
//
// grant access
//
return EFI_SUCCESS;
}
/**
Initialization for MOR Control Lock.
@retval EFI_SUCCESS MorLock initialization success.
@return Others Some error occurs.
**/
EFI_STATUS
MorLockInit (
VOID
)
{
mMorLockInitializationRequired = TRUE;
return EFI_SUCCESS;
}
/**
Delayed initialization for MOR Control Lock at EndOfDxe.
This function performs any operations queued by MorLockInit().
**/
VOID
MorLockInitAtEndOfDxe (
VOID
)
{
UINTN MorSize;
EFI_STATUS MorStatus;
EFI_STATUS Status;
VARIABLE_POLICY_ENTRY *NewPolicy;
if (!mMorLockInitializationRequired) {
//
// The EFI_SMM_FAULT_TOLERANT_WRITE_PROTOCOL has never been installed, thus
// the variable write service is unavailable. This should never happen.
//
ASSERT (FALSE);
return;
}
//
// Check if the MOR variable exists.
//
MorSize = 0;
MorStatus = VariableServiceGetVariable (
MEMORY_OVERWRITE_REQUEST_VARIABLE_NAME,
&gEfiMemoryOverwriteControlDataGuid,
NULL, // Attributes
&MorSize,
NULL // Data
);
//
// We provided a zero-sized buffer, so the above call can never succeed.
//
ASSERT (EFI_ERROR (MorStatus));
if (MorStatus == EFI_BUFFER_TOO_SMALL) {
//
// The MOR variable exists.
//
// Some OSes don't follow the TCG's Platform Reset Attack Mitigation spec
// in that the OS should never create the MOR variable, only read and write
// it -- these OSes (unintentionally) create MOR if the platform firmware
// does not produce it. Whether this is the case (from the last OS boot)
// can be deduced from the absence of the TCG / TCG2 protocols, as edk2's
// MOR implementation depends on (one of) those protocols.
//
if (VariableHaveTcgProtocols ()) {
//
// The MOR variable originates from the platform firmware; set the MOR
// Control Lock variable to report the locking capability to the OS.
//
SetMorLockVariable (0);
return;
}
//
// The MOR variable's origin is inexplicable; delete it.
//
DEBUG ((
DEBUG_WARN,
"%a: deleting unexpected / unsupported variable %g:%s\n",
__func__,
&gEfiMemoryOverwriteControlDataGuid,
MEMORY_OVERWRITE_REQUEST_VARIABLE_NAME
));
mMorPassThru = TRUE;
VariableServiceSetVariable (
MEMORY_OVERWRITE_REQUEST_VARIABLE_NAME,
&gEfiMemoryOverwriteControlDataGuid,
0, // Attributes
0, // DataSize
NULL // Data
);
mMorPassThru = FALSE;
}
//
// The MOR variable is absent; the platform firmware does not support it.
// Lock the variable so that no other module may create it.
//
NewPolicy = NULL;
Status = CreateBasicVariablePolicy (
&gEfiMemoryOverwriteControlDataGuid,
MEMORY_OVERWRITE_REQUEST_VARIABLE_NAME,
VARIABLE_POLICY_NO_MIN_SIZE,
VARIABLE_POLICY_NO_MAX_SIZE,
VARIABLE_POLICY_NO_MUST_ATTR,
VARIABLE_POLICY_NO_CANT_ATTR,
VARIABLE_POLICY_TYPE_LOCK_NOW,
&NewPolicy
);
if (!EFI_ERROR (Status)) {
Status = RegisterVariablePolicy (NewPolicy);
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a - Failed to lock variable %s! %r\n", __func__, MEMORY_OVERWRITE_REQUEST_VARIABLE_NAME, Status));
ASSERT_EFI_ERROR (Status);
}
if (NewPolicy != NULL) {
FreePool (NewPolicy);
}
//
// Delete the MOR Control Lock variable too (should it exists for some
// reason) and prevent other modules from creating it.
//
mMorLockPassThru = TRUE;
VariableServiceSetVariable (
MEMORY_OVERWRITE_REQUEST_CONTROL_LOCK_NAME,
&gEfiMemoryOverwriteRequestControlLockGuid,
0, // Attributes
0, // DataSize
NULL // Data
);
mMorLockPassThru = FALSE;
NewPolicy = NULL;
Status = CreateBasicVariablePolicy (
&gEfiMemoryOverwriteRequestControlLockGuid,
MEMORY_OVERWRITE_REQUEST_CONTROL_LOCK_NAME,
VARIABLE_POLICY_NO_MIN_SIZE,
VARIABLE_POLICY_NO_MAX_SIZE,
VARIABLE_POLICY_NO_MUST_ATTR,
VARIABLE_POLICY_NO_CANT_ATTR,
VARIABLE_POLICY_TYPE_LOCK_NOW,
&NewPolicy
);
if (!EFI_ERROR (Status)) {
Status = RegisterVariablePolicy (NewPolicy);
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "%a - Failed to lock variable %s! %r\n", __func__, MEMORY_OVERWRITE_REQUEST_CONTROL_LOCK_NAME, Status));
ASSERT_EFI_ERROR (Status);
}
if (NewPolicy != NULL) {
FreePool (NewPolicy);
}
}