/** @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); } }