/** @file Supports Fmp Capsule Dependency Expression. Copyright (c) Microsoft Corporation.
Copyright (c) 2020, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include #include // // Define the initial size of the dependency expression evaluation stack // #define DEPEX_STACK_SIZE_INCREMENT 0x1000 // // Type of stack element // typedef enum { BooleanType, VersionType } ELEMENT_TYPE; // // Value of stack element // typedef union { BOOLEAN Boolean; UINT32 Version; } ELEMENT_VALUE; // // Stack element used to evaluate dependency expressions // typedef struct { ELEMENT_VALUE Value; ELEMENT_TYPE Type; } DEPEX_ELEMENT; // // Global stack used to evaluate dependency expressions // DEPEX_ELEMENT *mDepexEvaluationStack = NULL; DEPEX_ELEMENT *mDepexEvaluationStackEnd = NULL; DEPEX_ELEMENT *mDepexEvaluationStackPointer = NULL; /** Grow size of the Depex stack @retval EFI_SUCCESS Stack successfully growed. @retval EFI_OUT_OF_RESOURCES There is not enough system memory to grow the stack. **/ EFI_STATUS GrowDepexStack ( VOID ) { DEPEX_ELEMENT *NewStack; UINTN Size; Size = DEPEX_STACK_SIZE_INCREMENT; if (mDepexEvaluationStack != NULL) { Size = Size + (mDepexEvaluationStackEnd - mDepexEvaluationStack); } NewStack = AllocatePool (Size * sizeof (DEPEX_ELEMENT)); if (NewStack == NULL) { DEBUG ((DEBUG_ERROR, "GrowDepexStack: Cannot allocate memory for dependency evaluation stack!\n")); return EFI_OUT_OF_RESOURCES; } if (mDepexEvaluationStack != NULL) { // // Copy to Old Stack to the New Stack // CopyMem ( NewStack, mDepexEvaluationStack, (mDepexEvaluationStackEnd - mDepexEvaluationStack) * sizeof (DEPEX_ELEMENT) ); // // Free The Old Stack // FreePool (mDepexEvaluationStack); } // // Make the Stack pointer point to the old data in the new stack // mDepexEvaluationStackPointer = NewStack + (mDepexEvaluationStackPointer - mDepexEvaluationStack); mDepexEvaluationStack = NewStack; mDepexEvaluationStackEnd = NewStack + Size; return EFI_SUCCESS; } /** Push an element onto the Stack. @param[in] Value Value to push. @param[in] Type Element Type @retval EFI_SUCCESS The value was pushed onto the stack. @retval EFI_OUT_OF_RESOURCES There is not enough system memory to grow the stack. @retval EFI_INVALID_PARAMETER Wrong stack element type. **/ EFI_STATUS Push ( IN UINT32 Value, IN UINTN Type ) { EFI_STATUS Status; DEPEX_ELEMENT Element; // // Check Type // if ((Type != BooleanType) && (Type != VersionType)) { return EFI_INVALID_PARAMETER; } // // Check for a stack overflow condition // if (mDepexEvaluationStackPointer == mDepexEvaluationStackEnd) { // // Grow the stack // Status = GrowDepexStack (); if (EFI_ERROR (Status)) { return Status; } } Element.Value.Version = Value; Element.Type = Type; // // Push the item onto the stack // *mDepexEvaluationStackPointer = Element; mDepexEvaluationStackPointer++; return EFI_SUCCESS; } /** Pop an element from the stack. @param[out] Element Element to pop. @param[in] Type Type of element. @retval EFI_SUCCESS The value was popped onto the stack. @retval EFI_ACCESS_DENIED The pop operation underflowed the stack. @retval EFI_INVALID_PARAMETER Type is mismatched. **/ EFI_STATUS Pop ( OUT DEPEX_ELEMENT *Element, IN ELEMENT_TYPE Type ) { // // Check for a stack underflow condition // if (mDepexEvaluationStackPointer == mDepexEvaluationStack) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: Stack underflow!\n")); return EFI_ACCESS_DENIED; } // // Pop the item off the stack // mDepexEvaluationStackPointer--; *Element = *mDepexEvaluationStackPointer; if ((*Element).Type != Type) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: Popped element type is mismatched!\n")); return EFI_INVALID_PARAMETER; } return EFI_SUCCESS; } /** Evaluate the dependencies. The caller must search all the Fmp instances and gather their versions into FmpVersions parameter. If there is PUSH_GUID opcode in dependency expression with no FmpVersions provided, the dependency will evaluate to FALSE. @param[in] Dependencies Dependency expressions. @param[in] DependenciesSize Size of Dependency expressions. @param[in] FmpVersions Array of Fmp ImageTypeId and version. This parameter is optional and can be set to NULL. @param[in] FmpVersionsCount Element count of the array. When FmpVersions is NULL, FmpVersionsCount must be 0. @param[out] LastAttemptStatus An optional pointer to a UINT32 that holds the last attempt status to report back to the caller. This function will set the value to LAST_ATTEMPT_STATUS_SUCCESS if an error code is not set. @retval TRUE Dependency expressions evaluate to TRUE. @retval FALSE Dependency expressions evaluate to FALSE. **/ BOOLEAN EFIAPI EvaluateDependency ( IN EFI_FIRMWARE_IMAGE_DEP *Dependencies, IN UINTN DependenciesSize, IN FMP_DEPEX_CHECK_VERSION_DATA *FmpVersions OPTIONAL, IN UINTN FmpVersionsCount, OUT UINT32 *LastAttemptStatus OPTIONAL ) { EFI_STATUS Status; UINT8 *Iterator; UINT8 Index; DEPEX_ELEMENT Element1; DEPEX_ELEMENT Element2; GUID ImageTypeId; UINT32 Version; UINT32 LocalLastAttemptStatus; UINT32 DeclaredLength; LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_SUCCESS; // // Check if parameter is valid. // if ((Dependencies == NULL) || (DependenciesSize == 0)) { return FALSE; } if ((FmpVersions == NULL) && (FmpVersionsCount > 0)) { return FALSE; } // // Clean out memory leaks in Depex Boolean stack. Leaks are only caused by // incorrectly formed DEPEX expressions // mDepexEvaluationStackPointer = mDepexEvaluationStack; Iterator = (UINT8 *)Dependencies->Dependencies; while (Iterator < (UINT8 *)Dependencies->Dependencies + DependenciesSize) { switch (*Iterator) { case EFI_FMP_DEP_PUSH_GUID: if (Iterator + sizeof (EFI_GUID) >= (UINT8 *)Dependencies->Dependencies + DependenciesSize) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: GUID extends beyond end of dependency expression!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_GUID_BEYOND_DEPEX; goto Error; } CopyGuid (&ImageTypeId, (EFI_GUID *)(Iterator + 1)); Iterator = Iterator + sizeof (EFI_GUID); for (Index = 0; Index < FmpVersionsCount; Index++) { if (CompareGuid (&FmpVersions[Index].ImageTypeId, &ImageTypeId)) { Status = Push (FmpVersions[Index].Version, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; } } if (Index == FmpVersionsCount) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: %g is not found!\n", &ImageTypeId)); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_FMP_NOT_FOUND; goto Error; } break; case EFI_FMP_DEP_PUSH_VERSION: if (Iterator + sizeof (UINT32) >= (UINT8 *)Dependencies->Dependencies + DependenciesSize ) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: VERSION extends beyond end of dependency expression!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_VERSION_BEYOND_DEPEX; goto Error; } Version = *(UINT32 *)(Iterator + 1); Status = Push (Version, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } Iterator = Iterator + sizeof (UINT32); break; case EFI_FMP_DEP_VERSION_STR: Iterator += AsciiStrnLenS ((CHAR8 *)Iterator, DependenciesSize - (Iterator - Dependencies->Dependencies)); if (Iterator == (UINT8 *)Dependencies->Dependencies + DependenciesSize) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: STRING extends beyond end of dependency expression!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_VERSION_STR_BEYOND_DEPEX; goto Error; } break; case EFI_FMP_DEP_AND: Status = Pop (&Element1, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Push (Element1.Value.Boolean & Element2.Value.Boolean, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_OR: Status = Pop (&Element1, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Push (Element1.Value.Boolean | Element2.Value.Boolean, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_NOT: Status = Pop (&Element1, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Push (!(Element1.Value.Boolean), BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_TRUE: Status = Push (TRUE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_FALSE: Status = Push (FALSE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_EQ: Status = Pop (&Element1, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = (Element1.Value.Version == Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_GT: Status = Pop (&Element1, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = (Element1.Value.Version > Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_GTE: Status = Pop (&Element1, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = (Element1.Value.Version >= Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_LT: Status = Pop (&Element1, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = (Element1.Value.Version < Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_LTE: Status = Pop (&Element1, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = Pop (&Element2, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } Status = (Element1.Value.Version <= Element2.Value.Version) ? Push (TRUE, BooleanType) : Push (FALSE, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } break; case EFI_FMP_DEP_END: Status = Pop (&Element1, BooleanType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_POP_FAILURE; goto Error; } return Element1.Value.Boolean; case EFI_FMP_DEP_DECLARE_LENGTH: if (Iterator + sizeof (UINT32) >= (UINT8 *)Dependencies->Dependencies + DependenciesSize ) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: DECLARE_LENGTH extends beyond end of dependency expression!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_DECLARE_LENGTH_BEYOND_DEPEX; goto Error; } // // This opcode must be the first one in a dependency expression. // if (Iterator != Dependencies->Dependencies) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: DECLARE_LENGTH is not the first opcode!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_DECLARE_LENGTH_NOT_FIRST_OPCODE; goto Error; } DeclaredLength = *(UINT32 *)(Iterator + 1); if (DeclaredLength != DependenciesSize) { DEBUG ((DEBUG_ERROR, "EvaluateDependency: DECLARE_LENGTH is not equal to length of dependency expression!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_DECLARE_LENGTH_INCORRECT; goto Error; } Status = Push (DeclaredLength, VersionType); if (EFI_ERROR (Status)) { LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_PUSH_FAILURE; goto Error; } Iterator = Iterator + sizeof (UINT32); break; default: DEBUG ((DEBUG_ERROR, "EvaluateDependency: Unknown Opcode - %02x!\n", *Iterator)); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_UNKNOWN_OPCODE; goto Error; } Iterator++; } DEBUG ((DEBUG_ERROR, "EvaluateDependency: No EFI_FMP_DEP_END Opcode in expression!\n")); LocalLastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_NO_END_OPCODE; Error: if (LastAttemptStatus != NULL) { *LastAttemptStatus = LocalLastAttemptStatus; } return FALSE; } /** Validate the dependency expression and output its size. @param[in] Dependencies Pointer to the EFI_FIRMWARE_IMAGE_DEP. @param[in] MaxDepexSize Max size of the dependency. @param[out] DepexSize Size of dependency. @param[out] LastAttemptStatus An optional pointer to a UINT32 that holds the last attempt status to report back to the caller. If a last attempt status error code is not returned, this function will not modify the LastAttemptStatus value. @retval TRUE The dependency expression is valid. @retval FALSE The dependency expression is invalid. **/ BOOLEAN EFIAPI ValidateDependency ( IN EFI_FIRMWARE_IMAGE_DEP *Dependencies, IN UINTN MaxDepexSize, OUT UINT32 *DepexSize, OUT UINT32 *LastAttemptStatus OPTIONAL ) { UINT8 *Depex; if (DepexSize != NULL) { *DepexSize = 0; } if (Dependencies == NULL) { return FALSE; } Depex = Dependencies->Dependencies; while (Depex < Dependencies->Dependencies + MaxDepexSize) { switch (*Depex) { case EFI_FMP_DEP_PUSH_GUID: Depex += sizeof (EFI_GUID) + 1; break; case EFI_FMP_DEP_PUSH_VERSION: Depex += sizeof (UINT32) + 1; break; case EFI_FMP_DEP_VERSION_STR: Depex += AsciiStrnLenS ((CHAR8 *)Depex, Dependencies->Dependencies + MaxDepexSize - Depex) + 1; break; case EFI_FMP_DEP_AND: case EFI_FMP_DEP_OR: case EFI_FMP_DEP_NOT: case EFI_FMP_DEP_TRUE: case EFI_FMP_DEP_FALSE: case EFI_FMP_DEP_EQ: case EFI_FMP_DEP_GT: case EFI_FMP_DEP_GTE: case EFI_FMP_DEP_LT: case EFI_FMP_DEP_LTE: Depex += 1; break; case EFI_FMP_DEP_END: Depex += 1; if (DepexSize != NULL) { *DepexSize = (UINT32)(Depex - Dependencies->Dependencies); } return TRUE; case EFI_FMP_DEP_DECLARE_LENGTH: Depex += sizeof (UINT32) + 1; break; default: return FALSE; } } if (LastAttemptStatus != NULL) { *LastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_NO_END_OPCODE; } return FALSE; } /** Get dependency from firmware image. @param[in] Image Points to the firmware image. @param[in] ImageSize Size, in bytes, of the firmware image. @param[out] DepexSize Size, in bytes, of the dependency. @param[out] LastAttemptStatus An optional pointer to a UINT32 that holds the last attempt status to report back to the caller. If a last attempt status error code is not returned, this function will not modify the LastAttemptStatus value. @retval The pointer to dependency. @retval Null **/ EFI_FIRMWARE_IMAGE_DEP * EFIAPI GetImageDependency ( IN EFI_FIRMWARE_IMAGE_AUTHENTICATION *Image, IN UINTN ImageSize, OUT UINT32 *DepexSize, OUT UINT32 *LastAttemptStatus OPTIONAL ) { EFI_FIRMWARE_IMAGE_DEP *Depex; UINTN MaxDepexSize; if (Image == NULL) { return NULL; } // // Check to make sure that operation can be safely performed. // if ((((UINTN)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength) < (UINTN)Image) || \ (((UINTN)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength) >= (UINTN)Image + ImageSize)) { // // Pointer overflow. Invalid image. // if (LastAttemptStatus != NULL) { *LastAttemptStatus = LAST_ATTEMPT_STATUS_DEPENDENCY_LIB_ERROR_GET_DEPEX_FAILURE; } return NULL; } Depex = (EFI_FIRMWARE_IMAGE_DEP *)((UINT8 *)Image + sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength); MaxDepexSize = ImageSize - (sizeof (Image->MonotonicCount) + Image->AuthInfo.Hdr.dwLength); // // Validate the dependency and get the size of dependency // if (ValidateDependency (Depex, MaxDepexSize, DepexSize, LastAttemptStatus)) { return Depex; } return NULL; }