| /** @file | |
| DXE capsule process. | |
| Caution: This module requires additional review when modified. | |
| This module will have external input - capsule image. | |
| This external input must be validated carefully to avoid security issue like | |
| buffer overflow, integer overflow. | |
| ProcessCapsules(), ProcessTheseCapsules() will receive untrusted | |
| input and do basic validation. | |
| Copyright (c) 2016, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include <PiDxe.h> | |
| #include <Protocol/EsrtManagement.h> | |
| #include <Library/BaseLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/UefiRuntimeServicesTableLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/UefiLib.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/HobLib.h> | |
| #include <Library/ReportStatusCodeLib.h> | |
| #include <Library/CapsuleLib.h> | |
| #include <IndustryStandard/WindowsUxCapsule.h> | |
| /** | |
| Return if this FMP is a system FMP or a device FMP, based upon CapsuleHeader. | |
| @param[in] CapsuleHeader A pointer to EFI_CAPSULE_HEADER | |
| @retval TRUE It is a system FMP. | |
| @retval FALSE It is a device FMP. | |
| **/ | |
| BOOLEAN | |
| IsFmpCapsule ( | |
| IN EFI_CAPSULE_HEADER *CapsuleHeader | |
| ); | |
| /** | |
| Validate Fmp capsules layout. | |
| Caution: This function may receive untrusted input. | |
| This function assumes the caller validated the capsule by using | |
| IsValidCapsuleHeader(), so that all fields in EFI_CAPSULE_HEADER are correct. | |
| The capsule buffer size is CapsuleHeader->CapsuleImageSize. | |
| This function validates the fields in EFI_FIRMWARE_MANAGEMENT_CAPSULE_HEADER | |
| and EFI_FIRMWARE_MANAGEMENT_CAPSULE_IMAGE_HEADER. | |
| This function need support nested FMP capsule. | |
| @param[in] CapsuleHeader Points to a capsule header. | |
| @param[out] EmbeddedDriverCount The EmbeddedDriverCount in the FMP capsule. | |
| @retval EFI_SUCESS Input capsule is a correct FMP capsule. | |
| @retval EFI_INVALID_PARAMETER Input capsule is not a correct FMP capsule. | |
| **/ | |
| EFI_STATUS | |
| ValidateFmpCapsule ( | |
| IN EFI_CAPSULE_HEADER *CapsuleHeader, | |
| OUT UINT16 *EmbeddedDriverCount OPTIONAL | |
| ); | |
| /** | |
| Validate if it is valid capsule header | |
| This function assumes the caller provided correct CapsuleHeader pointer | |
| and CapsuleSize. | |
| This function validates the fields in EFI_CAPSULE_HEADER. | |
| @param[in] CapsuleHeader Points to a capsule header. | |
| @param[in] CapsuleSize Size of the whole capsule image. | |
| **/ | |
| BOOLEAN | |
| IsValidCapsuleHeader ( | |
| IN EFI_CAPSULE_HEADER *CapsuleHeader, | |
| IN UINT64 CapsuleSize | |
| ); | |
| extern BOOLEAN mDxeCapsuleLibEndOfDxe; | |
| BOOLEAN mNeedReset; | |
| VOID **mCapsulePtr; | |
| EFI_STATUS *mCapsuleStatusArray; | |
| UINT32 mCapsuleTotalNumber; | |
| /** | |
| This function initializes the mCapsulePtr, mCapsuleStatusArray and mCapsuleTotalNumber. | |
| **/ | |
| VOID | |
| InitCapsulePtr ( | |
| VOID | |
| ) | |
| { | |
| EFI_PEI_HOB_POINTERS HobPointer; | |
| UINTN Index; | |
| // | |
| // Find all capsule images from hob | |
| // | |
| HobPointer.Raw = GetHobList (); | |
| while ((HobPointer.Raw = GetNextHob (EFI_HOB_TYPE_UEFI_CAPSULE, HobPointer.Raw)) != NULL) { | |
| if (!IsValidCapsuleHeader((VOID *)(UINTN)HobPointer.Capsule->BaseAddress, HobPointer.Capsule->Length)) { | |
| HobPointer.Header->HobType = EFI_HOB_TYPE_UNUSED; // Mark this hob as invalid | |
| } else { | |
| mCapsuleTotalNumber++; | |
| } | |
| HobPointer.Raw = GET_NEXT_HOB (HobPointer); | |
| } | |
| DEBUG ((DEBUG_INFO, "mCapsuleTotalNumber - 0x%x\n", mCapsuleTotalNumber)); | |
| if (mCapsuleTotalNumber == 0) { | |
| return ; | |
| } | |
| // | |
| // Init temp Capsule Data table. | |
| // | |
| mCapsulePtr = (VOID **) AllocateZeroPool (sizeof (VOID *) * mCapsuleTotalNumber); | |
| if (mCapsulePtr == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Allocate mCapsulePtr fail!\n")); | |
| mCapsuleTotalNumber = 0; | |
| return ; | |
| } | |
| mCapsuleStatusArray = (EFI_STATUS *) AllocateZeroPool (sizeof (EFI_STATUS) * mCapsuleTotalNumber); | |
| if (mCapsuleStatusArray == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Allocate mCapsuleStatusArray fail!\n")); | |
| FreePool (mCapsulePtr); | |
| mCapsulePtr = NULL; | |
| mCapsuleTotalNumber = 0; | |
| return ; | |
| } | |
| SetMemN (mCapsuleStatusArray, sizeof (EFI_STATUS) * mCapsuleTotalNumber, EFI_NOT_READY); | |
| // | |
| // Find all capsule images from hob | |
| // | |
| HobPointer.Raw = GetHobList (); | |
| Index = 0; | |
| while ((HobPointer.Raw = GetNextHob (EFI_HOB_TYPE_UEFI_CAPSULE, HobPointer.Raw)) != NULL) { | |
| mCapsulePtr [Index++] = (VOID *) (UINTN) HobPointer.Capsule->BaseAddress; | |
| HobPointer.Raw = GET_NEXT_HOB (HobPointer); | |
| } | |
| } | |
| /** | |
| This function returns if all capsule images are processed. | |
| @retval TRUE All capsule images are processed. | |
| @retval FALSE Not all capsule images are processed. | |
| **/ | |
| BOOLEAN | |
| AreAllImagesProcessed ( | |
| VOID | |
| ) | |
| { | |
| UINTN Index; | |
| for (Index = 0; Index < mCapsuleTotalNumber; Index++) { | |
| if (mCapsuleStatusArray[Index] == EFI_NOT_READY) { | |
| return FALSE; | |
| } | |
| } | |
| return TRUE; | |
| } | |
| /** | |
| This function populates capsule in the configuration table. | |
| **/ | |
| VOID | |
| PopulateCapsuleInConfigurationTable ( | |
| VOID | |
| ) | |
| { | |
| VOID **CapsulePtrCache; | |
| EFI_GUID *CapsuleGuidCache; | |
| EFI_CAPSULE_HEADER *CapsuleHeader; | |
| EFI_CAPSULE_TABLE *CapsuleTable; | |
| UINT32 CacheIndex; | |
| UINT32 CacheNumber; | |
| UINT32 CapsuleNumber; | |
| UINTN Index; | |
| UINTN Size; | |
| EFI_STATUS Status; | |
| if (mCapsuleTotalNumber == 0) { | |
| return ; | |
| } | |
| CapsulePtrCache = NULL; | |
| CapsuleGuidCache = NULL; | |
| CacheIndex = 0; | |
| CacheNumber = 0; | |
| CapsulePtrCache = (VOID **) AllocateZeroPool (sizeof (VOID *) * mCapsuleTotalNumber); | |
| if (CapsulePtrCache == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Allocate CapsulePtrCache fail!\n")); | |
| return ; | |
| } | |
| CapsuleGuidCache = (EFI_GUID *) AllocateZeroPool (sizeof (EFI_GUID) * mCapsuleTotalNumber); | |
| if (CapsuleGuidCache == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Allocate CapsuleGuidCache fail!\n")); | |
| FreePool (CapsulePtrCache); | |
| return ; | |
| } | |
| // | |
| // Capsules who have CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE always are used for operating | |
| // System to have information persist across a system reset. EFI System Table must | |
| // point to an array of capsules that contains the same CapsuleGuid value. And agents | |
| // searching for this type capsule will look in EFI System Table and search for the | |
| // capsule's Guid and associated pointer to retrieve the data. Two steps below describes | |
| // how to sorting the capsules by the unique guid and install the array to EFI System Table. | |
| // Firstly, Loop for all coalesced capsules, record unique CapsuleGuids and cache them in an | |
| // array for later sorting capsules by CapsuleGuid. | |
| // | |
| for (Index = 0; Index < mCapsuleTotalNumber; Index++) { | |
| CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index]; | |
| if ((CapsuleHeader->Flags & CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE) != 0) { | |
| // | |
| // For each capsule, we compare it with known CapsuleGuid in the CacheArray. | |
| // If already has the Guid, skip it. Whereas, record it in the CacheArray as | |
| // an additional one. | |
| // | |
| CacheIndex = 0; | |
| while (CacheIndex < CacheNumber) { | |
| if (CompareGuid(&CapsuleGuidCache[CacheIndex],&CapsuleHeader->CapsuleGuid)) { | |
| break; | |
| } | |
| CacheIndex++; | |
| } | |
| if (CacheIndex == CacheNumber) { | |
| CopyMem(&CapsuleGuidCache[CacheNumber++],&CapsuleHeader->CapsuleGuid,sizeof(EFI_GUID)); | |
| } | |
| } | |
| } | |
| // | |
| // Secondly, for each unique CapsuleGuid in CacheArray, gather all coalesced capsules | |
| // whose guid is the same as it, and malloc memory for an array which preceding | |
| // with UINT32. The array fills with entry point of capsules that have the same | |
| // CapsuleGuid, and UINT32 represents the size of the array of capsules. Then install | |
| // this array into EFI System Table, so that agents searching for this type capsule | |
| // will look in EFI System Table and search for the capsule's Guid and associated | |
| // pointer to retrieve the data. | |
| // | |
| for (CacheIndex = 0; CacheIndex < CacheNumber; CacheIndex++) { | |
| CapsuleNumber = 0; | |
| for (Index = 0; Index < mCapsuleTotalNumber; Index++) { | |
| CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index]; | |
| if ((CapsuleHeader->Flags & CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE) != 0) { | |
| if (CompareGuid (&CapsuleGuidCache[CacheIndex], &CapsuleHeader->CapsuleGuid)) { | |
| // | |
| // Cache Caspuleheader to the array, this array is uniqued with certain CapsuleGuid. | |
| // | |
| CapsulePtrCache[CapsuleNumber++] = (VOID*)CapsuleHeader; | |
| } | |
| } | |
| } | |
| if (CapsuleNumber != 0) { | |
| Size = sizeof(EFI_CAPSULE_TABLE) + (CapsuleNumber - 1) * sizeof(VOID*); | |
| CapsuleTable = AllocateRuntimePool (Size); | |
| if (CapsuleTable == NULL) { | |
| DEBUG ((DEBUG_ERROR, "Allocate CapsuleTable (%g) fail!\n", &CapsuleGuidCache[CacheIndex])); | |
| continue; | |
| } | |
| CapsuleTable->CapsuleArrayNumber = CapsuleNumber; | |
| CopyMem(&CapsuleTable->CapsulePtr[0], CapsulePtrCache, CapsuleNumber * sizeof(VOID*)); | |
| Status = gBS->InstallConfigurationTable (&CapsuleGuidCache[CacheIndex], (VOID*)CapsuleTable); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((DEBUG_ERROR, "InstallConfigurationTable (%g) fail!\n", &CapsuleGuidCache[CacheIndex])); | |
| } | |
| } | |
| } | |
| FreePool(CapsuleGuidCache); | |
| FreePool(CapsulePtrCache); | |
| } | |
| /** | |
| This routine is called to process capsules. | |
| Caution: This function may receive untrusted input. | |
| Each individual capsule result is recorded in capsule record variable. | |
| @param[in] FirstRound TRUE: First round. Need skip the FMP capsules with non zero EmbeddedDriverCount. | |
| FALSE: Process rest FMP capsules. | |
| @retval EFI_SUCCESS There is no error when processing capsules. | |
| @retval EFI_OUT_OF_RESOURCES No enough resource to process capsules. | |
| **/ | |
| EFI_STATUS | |
| ProcessTheseCapsules ( | |
| IN BOOLEAN FirstRound | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_CAPSULE_HEADER *CapsuleHeader; | |
| UINT32 Index; | |
| BOOLEAN DisplayCapsuleExist; | |
| ESRT_MANAGEMENT_PROTOCOL *EsrtManagement; | |
| UINT16 EmbeddedDriverCount; | |
| REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeProcessCapsulesBegin))); | |
| if (FirstRound) { | |
| InitCapsulePtr (); | |
| } | |
| if (mCapsuleTotalNumber == 0) { | |
| // | |
| // We didn't find a hob, so had no errors. | |
| // | |
| DEBUG ((DEBUG_ERROR, "We can not find capsule data in capsule update boot mode.\n")); | |
| return EFI_SUCCESS; | |
| } | |
| if (AreAllImagesProcessed ()) { | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Check the capsule flags,if contains CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE, install | |
| // capsuleTable to configure table with EFI_CAPSULE_GUID | |
| // | |
| if (FirstRound) { | |
| PopulateCapsuleInConfigurationTable (); | |
| } | |
| REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeUpdatingFirmware))); | |
| // | |
| // If Windows UX capsule exist, process it first | |
| // | |
| DisplayCapsuleExist = FALSE; | |
| for (Index = 0; Index < mCapsuleTotalNumber; Index++) { | |
| CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index]; | |
| if (CompareGuid (&CapsuleHeader->CapsuleGuid, &gWindowsUxCapsuleGuid)) { | |
| DEBUG ((DEBUG_INFO, "ProcessCapsuleImage (Ux) - 0x%x\n", CapsuleHeader)); | |
| DisplayCapsuleExist = TRUE; | |
| DEBUG ((DEBUG_INFO, "Display logo capsule is found.\n")); | |
| Status = ProcessCapsuleImage (CapsuleHeader); | |
| mCapsuleStatusArray [Index] = EFI_SUCCESS; | |
| DEBUG((DEBUG_INFO, "ProcessCapsuleImage (Ux) - %r\n", Status)); | |
| break; | |
| } | |
| } | |
| if (!DisplayCapsuleExist) { | |
| // | |
| // Display Capsule not found. Display the default string. | |
| // | |
| Print (L"Updating the firmware ......\r\n"); | |
| } | |
| // | |
| // All capsules left are recognized by platform. | |
| // | |
| for (Index = 0; Index < mCapsuleTotalNumber; Index++) { | |
| if (mCapsuleStatusArray [Index] != EFI_NOT_READY) { | |
| // already processed | |
| continue; | |
| } | |
| CapsuleHeader = (EFI_CAPSULE_HEADER*) mCapsulePtr [Index]; | |
| if (!CompareGuid (&CapsuleHeader->CapsuleGuid, &gWindowsUxCapsuleGuid)) { | |
| // | |
| // Call capsule library to process capsule image. | |
| // | |
| EmbeddedDriverCount = 0; | |
| if (IsFmpCapsule(CapsuleHeader)) { | |
| Status = ValidateFmpCapsule (CapsuleHeader, &EmbeddedDriverCount); | |
| if (EFI_ERROR(Status)) { | |
| DEBUG((DEBUG_ERROR, "ValidateFmpCapsule failed. Ignore!\n")); | |
| mCapsuleStatusArray [Index] = EFI_ABORTED; | |
| continue; | |
| } | |
| } else { | |
| mCapsuleStatusArray [Index] = EFI_ABORTED; | |
| continue; | |
| } | |
| if ((!FirstRound) || (EmbeddedDriverCount == 0)) { | |
| DEBUG((DEBUG_INFO, "ProcessCapsuleImage - 0x%x\n", CapsuleHeader)); | |
| Status = ProcessCapsuleImage (CapsuleHeader); | |
| mCapsuleStatusArray [Index] = Status; | |
| DEBUG((DEBUG_INFO, "ProcessCapsuleImage - %r\n", Status)); | |
| if (Status != EFI_NOT_READY) { | |
| if (EFI_ERROR(Status)) { | |
| REPORT_STATUS_CODE(EFI_ERROR_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeUpdateFirmwareFailed))); | |
| DEBUG ((DEBUG_ERROR, "Capsule process failed!\n")); | |
| Print (L"Firmware update failed...\r\n"); | |
| } else { | |
| REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeUpdateFirmwareSuccess))); | |
| } | |
| if ((CapsuleHeader->Flags & PcdGet16(PcdSystemRebootAfterCapsuleProcessFlag)) != 0 || | |
| IsFmpCapsule(CapsuleHeader)) { | |
| mNeedReset = TRUE; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Status = gBS->LocateProtocol(&gEsrtManagementProtocolGuid, NULL, (VOID **)&EsrtManagement); | |
| // | |
| // Always sync ESRT Cache from FMP Instance | |
| // | |
| if (!EFI_ERROR(Status)) { | |
| EsrtManagement->SyncEsrtFmp(); | |
| } | |
| Status = EFI_SUCCESS; | |
| REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeProcessCapsulesEnd))); | |
| return Status; | |
| } | |
| /** | |
| Do reset system. | |
| **/ | |
| VOID | |
| DoResetSystem ( | |
| VOID | |
| ) | |
| { | |
| UINTN Index; | |
| REPORT_STATUS_CODE(EFI_PROGRESS_CODE, (EFI_SOFTWARE | PcdGet32(PcdStatusCodeSubClassCapsule) | PcdGet32(PcdCapsuleStatusCodeResettingSystem))); | |
| Print(L"Capsule Request Cold Reboot.\n"); | |
| DEBUG((DEBUG_INFO, "Capsule Request Cold Reboot.")); | |
| for (Index = 5; Index > 0; Index--) { | |
| Print(L"\rResetting system in %d seconds ...", Index); | |
| DEBUG((DEBUG_INFO, "\rResetting system in %d seconds ...", Index)); | |
| gBS->Stall(1000000); | |
| } | |
| gRT->ResetSystem(EfiResetCold, EFI_SUCCESS, 0, NULL); | |
| CpuDeadLoop(); | |
| } | |
| /** | |
| This routine is called to process capsules. | |
| Caution: This function may receive untrusted input. | |
| The capsules reported in EFI_HOB_UEFI_CAPSULE are processed. | |
| If there is no EFI_HOB_UEFI_CAPSULE, this routine does nothing. | |
| This routine should be called twice in BDS. | |
| 1) The first call must be before EndOfDxe. The system capsules is processed. | |
| If device capsule FMP protocols are exposted at this time and device FMP | |
| capsule has zero EmbeddedDriverCount, the device capsules are processed. | |
| Each individual capsule result is recorded in capsule record variable. | |
| System may reset in this function, if reset is required by capsule and | |
| all capsules are processed. | |
| If not all capsules are processed, reset will be defered to second call. | |
| 2) The second call must be after EndOfDxe and after ConnectAll, so that all | |
| device capsule FMP protocols are exposed. | |
| The system capsules are skipped. If the device capsules are NOT processed | |
| in first call, they are processed here. | |
| Each individual capsule result is recorded in capsule record variable. | |
| System may reset in this function, if reset is required by capsule | |
| processed in first call and second call. | |
| @retval EFI_SUCCESS There is no error when processing capsules. | |
| @retval EFI_OUT_OF_RESOURCES No enough resource to process capsules. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| ProcessCapsules ( | |
| VOID | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| if (!mDxeCapsuleLibEndOfDxe) { | |
| Status = ProcessTheseCapsules(TRUE); | |
| // | |
| // Reboot System if and only if all capsule processed. | |
| // If not, defer reset to 2nd process. | |
| // | |
| if (mNeedReset && AreAllImagesProcessed()) { | |
| DoResetSystem(); | |
| } | |
| } else { | |
| Status = ProcessTheseCapsules(FALSE); | |
| // | |
| // Reboot System if required after all capsule processed | |
| // | |
| if (mNeedReset) { | |
| DoResetSystem(); | |
| } | |
| } | |
| return Status; | |
| } |