| /** @file | |
| Recovery module. | |
| Caution: This module requires additional review when modified. | |
| This module will have external input - Capsule-on-Disk Temp Relocation image. | |
| This external input must be validated carefully to avoid security issue like | |
| buffer overflow, integer overflow. | |
| RetrieveRelocatedCapsule() will receive untrusted input and do basic validation. | |
| Copyright (c) 2019, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| // | |
| // The package level header files this module uses | |
| // | |
| #include <Uefi.h> | |
| #include <PiPei.h> | |
| // | |
| // The protocols, PPI and GUID defintions for this module | |
| // | |
| #include <Ppi/MasterBootMode.h> | |
| #include <Ppi/FirmwareVolumeInfo.h> | |
| #include <Ppi/ReadOnlyVariable2.h> | |
| #include <Ppi/Capsule.h> | |
| #include <Ppi/CapsuleOnDisk.h> | |
| #include <Ppi/DeviceRecoveryModule.h> | |
| #include <Guid/FirmwareFileSystem2.h> | |
| // | |
| // The Library classes this module consumes | |
| // | |
| #include <Library/DebugLib.h> | |
| #include <Library/PeimEntryPoint.h> | |
| #include <Library/PeiServicesLib.h> | |
| #include <Library/HobLib.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/PcdLib.h> | |
| #include <Library/CapsuleLib.h> | |
| #include <Library/ReportStatusCodeLib.h> | |
| /** | |
| Loads a DXE capsule from some media into memory and updates the HOB table | |
| with the DXE firmware volume information. | |
| @param[in] PeiServices General-purpose services that are available to every PEIM. | |
| @param[in] This Indicates the EFI_PEI_RECOVERY_MODULE_PPI instance. | |
| @retval EFI_SUCCESS The capsule was loaded correctly. | |
| @retval EFI_DEVICE_ERROR A device error occurred. | |
| @retval EFI_NOT_FOUND A recovery DXE capsule cannot be found. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| LoadCapsuleOnDisk ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN EDKII_PEI_CAPSULE_ON_DISK_PPI *This | |
| ); | |
| static EDKII_PEI_CAPSULE_ON_DISK_PPI mCapsuleOnDiskPpi = { | |
| LoadCapsuleOnDisk | |
| }; | |
| static EFI_PEI_PPI_DESCRIPTOR mCapsuleOnDiskPpiList = { | |
| (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), | |
| &gEdkiiPeiCapsuleOnDiskPpiGuid, | |
| &mCapsuleOnDiskPpi | |
| }; | |
| /** | |
| Determine if capsule comes from memory by checking Capsule PPI. | |
| @param[in] PeiServices General purpose services available to every PEIM. | |
| @retval TRUE Capsule comes from memory. | |
| @retval FALSE No capsule comes from memory. | |
| **/ | |
| static | |
| BOOLEAN | |
| CheckCapsuleFromRam ( | |
| IN CONST EFI_PEI_SERVICES **PeiServices | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| PEI_CAPSULE_PPI *Capsule; | |
| Status = PeiServicesLocatePpi ( | |
| &gEfiPeiCapsulePpiGuid, | |
| 0, | |
| NULL, | |
| (VOID **)&Capsule | |
| ); | |
| if (!EFI_ERROR (Status)) { | |
| Status = Capsule->CheckCapsuleUpdate ((EFI_PEI_SERVICES **)PeiServices); | |
| if (!EFI_ERROR (Status)) { | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| Determine if it is a Capsule On Disk mode. | |
| @retval TRUE Capsule On Disk mode. | |
| @retval FALSE Not capsule On Disk mode. | |
| **/ | |
| static | |
| BOOLEAN | |
| IsCapsuleOnDiskMode ( | |
| VOID | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN Size; | |
| EFI_PEI_READ_ONLY_VARIABLE2_PPI *PPIVariableServices; | |
| BOOLEAN CodRelocInfo; | |
| Status = PeiServicesLocatePpi ( | |
| &gEfiPeiReadOnlyVariable2PpiGuid, | |
| 0, | |
| NULL, | |
| (VOID **)&PPIVariableServices | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| Size = sizeof (BOOLEAN); | |
| Status = PPIVariableServices->GetVariable ( | |
| PPIVariableServices, | |
| COD_RELOCATION_INFO_VAR_NAME, | |
| &gEfiCapsuleVendorGuid, | |
| NULL, | |
| &Size, | |
| &CodRelocInfo | |
| ); | |
| if (EFI_ERROR (Status) || (Size != sizeof (BOOLEAN)) || !CodRelocInfo) { | |
| DEBUG ((DEBUG_ERROR, "Error Get CodRelocationInfo variable %r!\n", Status)); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| /** | |
| Gets capsule images from relocated capsule buffer. | |
| Create Capsule hob for each Capsule. | |
| Caution: This function may receive untrusted input. | |
| Capsule-on-Disk Temp Relocation image is external input, so this function | |
| will validate Capsule-on-Disk Temp Relocation image to make sure the content | |
| is read within the buffer. | |
| @param[in] RelocCapsuleBuf Buffer pointer to the relocated capsule. | |
| @param[in] RelocCapsuleTotalSize Total size of the relocated capsule. | |
| @retval EFI_SUCCESS Succeed to get capsules and create hob. | |
| @retval Others Fail to get capsules and create hob. | |
| **/ | |
| static | |
| EFI_STATUS | |
| RetrieveRelocatedCapsule ( | |
| IN UINT8 *RelocCapsuleBuf, | |
| IN UINTN RelocCapsuleTotalSize | |
| ) | |
| { | |
| UINTN Index; | |
| UINT8 *CapsuleDataBufEnd; | |
| UINT8 *CapsulePtr; | |
| UINT32 CapsuleSize; | |
| UINT64 TotalImageSize; | |
| UINTN CapsuleNum; | |
| // | |
| // Temp file contains at least 2 capsule (including 1 capsule name capsule) & 1 UINT64 | |
| // | |
| if (RelocCapsuleTotalSize < sizeof (UINT64) + sizeof (EFI_CAPSULE_HEADER) * 2) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| CopyMem (&TotalImageSize, RelocCapsuleBuf, sizeof (UINT64)); | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "ProcessRelocatedCapsule CapsuleBuf %x TotalCapSize %lx\n", | |
| RelocCapsuleBuf, | |
| TotalImageSize | |
| )); | |
| RelocCapsuleBuf += sizeof (UINT64); | |
| // | |
| // TempCaspule file length check | |
| // | |
| if ((MAX_ADDRESS - TotalImageSize <= sizeof (UINT64)) || | |
| ((UINT64)RelocCapsuleTotalSize != TotalImageSize + sizeof (UINT64)) || | |
| ((UINTN)(MAX_ADDRESS - (PHYSICAL_ADDRESS)(UINTN)RelocCapsuleBuf) <= TotalImageSize)) | |
| { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| CapsuleDataBufEnd = RelocCapsuleBuf + TotalImageSize; | |
| // | |
| // TempCapsule file integrity check over Capsule Header to ensure no data corruption in NV Var & Relocation storage | |
| // | |
| CapsulePtr = RelocCapsuleBuf; | |
| CapsuleNum = 0; | |
| while (CapsulePtr < CapsuleDataBufEnd) { | |
| if (((CapsuleDataBufEnd - CapsulePtr) < sizeof (EFI_CAPSULE_HEADER)) || | |
| (((EFI_CAPSULE_HEADER *)CapsulePtr)->CapsuleImageSize < sizeof (EFI_CAPSULE_HEADER)) || | |
| ((UINTN)(MAX_ADDRESS - (PHYSICAL_ADDRESS)(UINTN)CapsulePtr) < ((EFI_CAPSULE_HEADER *)CapsulePtr)->CapsuleImageSize) | |
| ) | |
| { | |
| break; | |
| } | |
| CapsulePtr += ((EFI_CAPSULE_HEADER *)CapsulePtr)->CapsuleImageSize; | |
| CapsuleNum++; | |
| } | |
| if (CapsulePtr != CapsuleDataBufEnd) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Capsule count must be less than PcdCapsuleMax, avoid building too many CvHobs to occupy all the free space in HobList. | |
| // | |
| if (CapsuleNum > PcdGet16 (PcdCapsuleMax)) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| // | |
| // Re-iterate the capsule buffer to create Capsule hob & Capsule Name Str Hob for each Capsule saved in relocated capsule file | |
| // | |
| CapsulePtr = RelocCapsuleBuf; | |
| Index = 0; | |
| while (CapsulePtr < CapsuleDataBufEnd) { | |
| CapsuleSize = ((EFI_CAPSULE_HEADER *)CapsulePtr)->CapsuleImageSize; | |
| BuildCvHob ((EFI_PHYSICAL_ADDRESS)(UINTN)CapsulePtr, CapsuleSize); | |
| DEBUG ((DEBUG_INFO, "Capsule saved in address %x size %x\n", CapsulePtr, CapsuleSize)); | |
| CapsulePtr += CapsuleSize; | |
| Index++; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Recovery module entrypoint | |
| @param[in] FileHandle Handle of the file being invoked. | |
| @param[in] PeiServices Describes the list of possible PEI Services. | |
| @return EFI_SUCCESS Recovery module is initialized. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| InitializeCapsuleOnDiskLoad ( | |
| IN EFI_PEI_FILE_HANDLE FileHandle, | |
| IN CONST EFI_PEI_SERVICES **PeiServices | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN BootMode; | |
| UINTN FileNameSize; | |
| BootMode = GetBootModeHob (); | |
| ASSERT (BootMode == BOOT_ON_FLASH_UPDATE); | |
| // | |
| // If there are capsules provisioned in memory, quit. | |
| // Only one capsule resource is accept, CapsuleOnRam's priority is higher than CapsuleOnDisk. | |
| // | |
| if (CheckCapsuleFromRam (PeiServices)) { | |
| DEBUG ((DEBUG_ERROR, "Capsule On Memory Detected! Quit.\n")); | |
| return EFI_ABORTED; | |
| } | |
| DEBUG_CODE_BEGIN (); | |
| VOID *CapsuleOnDiskModePpi; | |
| if (!IsCapsuleOnDiskMode ()) { | |
| return EFI_NOT_FOUND; | |
| } | |
| // | |
| // Check Capsule On Disk Relocation flag. If exists, load capsule & create Capsule Hob | |
| // | |
| Status = PeiServicesLocatePpi ( | |
| &gEdkiiPeiBootInCapsuleOnDiskModePpiGuid, | |
| 0, | |
| NULL, | |
| (VOID **)&CapsuleOnDiskModePpi | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((DEBUG_ERROR, "Locate CapsuleOnDiskModePpi error %x\n", Status)); | |
| return Status; | |
| } | |
| DEBUG_CODE_END (); | |
| Status = PeiServicesInstallPpi (&mCapsuleOnDiskPpiList); | |
| ASSERT_EFI_ERROR (Status); | |
| FileNameSize = PcdGetSize (PcdCoDRelocationFileName); | |
| Status = PcdSetPtrS (PcdRecoveryFileName, &FileNameSize, (VOID *)PcdGetPtr (PcdCoDRelocationFileName)); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } | |
| /** | |
| Loads a DXE capsule from some media into memory and updates the HOB table | |
| with the DXE firmware volume information. | |
| @param[in] PeiServices General-purpose services that are available to every PEIM. | |
| @param[in] This Indicates the EFI_PEI_RECOVERY_MODULE_PPI instance. | |
| @retval EFI_SUCCESS The capsule was loaded correctly. | |
| @retval EFI_DEVICE_ERROR A device error occurred. | |
| @retval EFI_NOT_FOUND A recovery DXE capsule cannot be found. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| LoadCapsuleOnDisk ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN EDKII_PEI_CAPSULE_ON_DISK_PPI *This | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| EFI_PEI_DEVICE_RECOVERY_MODULE_PPI *DeviceRecoveryPpi; | |
| UINTN NumberRecoveryCapsules; | |
| UINTN Instance; | |
| UINTN CapsuleInstance; | |
| UINTN CapsuleSize; | |
| EFI_GUID CapsuleType; | |
| VOID *CapsuleBuffer; | |
| DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Load Capsule On Disk Entry\n")); | |
| for (Instance = 0; ; Instance++) { | |
| Status = PeiServicesLocatePpi ( | |
| &gEfiPeiDeviceRecoveryModulePpiGuid, | |
| Instance, | |
| NULL, | |
| (VOID **)&DeviceRecoveryPpi | |
| ); | |
| DEBUG ((DEBUG_INFO, "LoadCapsuleOnDisk - LocateRecoveryPpi (%d) - %r\n", Instance, Status)); | |
| if (EFI_ERROR (Status)) { | |
| if (Instance == 0) { | |
| REPORT_STATUS_CODE ( | |
| EFI_ERROR_CODE | EFI_ERROR_MAJOR, | |
| (EFI_SOFTWARE_PEI_MODULE | EFI_SW_PEI_EC_RECOVERY_PPI_NOT_FOUND) | |
| ); | |
| } | |
| break; | |
| } | |
| NumberRecoveryCapsules = 0; | |
| Status = DeviceRecoveryPpi->GetNumberRecoveryCapsules ( | |
| (EFI_PEI_SERVICES **)PeiServices, | |
| DeviceRecoveryPpi, | |
| &NumberRecoveryCapsules | |
| ); | |
| DEBUG ((DEBUG_INFO, "LoadCapsuleOnDisk - GetNumberRecoveryCapsules (%d) - %r\n", NumberRecoveryCapsules, Status)); | |
| if (EFI_ERROR (Status)) { | |
| continue; | |
| } | |
| for (CapsuleInstance = 1; CapsuleInstance <= NumberRecoveryCapsules; CapsuleInstance++) { | |
| CapsuleSize = 0; | |
| Status = DeviceRecoveryPpi->GetRecoveryCapsuleInfo ( | |
| (EFI_PEI_SERVICES **)PeiServices, | |
| DeviceRecoveryPpi, | |
| CapsuleInstance, | |
| &CapsuleSize, | |
| &CapsuleType | |
| ); | |
| DEBUG ((DEBUG_INFO, "LoadCapsuleOnDisk - GetRecoveryCapsuleInfo (%d - %x) - %r\n", CapsuleInstance, CapsuleSize, Status)); | |
| if (EFI_ERROR (Status)) { | |
| break; | |
| } | |
| // | |
| // Allocate the memory so that it gets preserved into DXE. | |
| // Capsule is special because it may need to populate to system table | |
| // | |
| CapsuleBuffer = AllocateRuntimePages (EFI_SIZE_TO_PAGES (CapsuleSize)); | |
| if (CapsuleBuffer == NULL) { | |
| DEBUG ((DEBUG_ERROR, "LoadCapsuleOnDisk - AllocateRuntimePages fail\n")); | |
| continue; | |
| } | |
| Status = DeviceRecoveryPpi->LoadRecoveryCapsule ( | |
| (EFI_PEI_SERVICES **)PeiServices, | |
| DeviceRecoveryPpi, | |
| CapsuleInstance, | |
| CapsuleBuffer | |
| ); | |
| DEBUG ((DEBUG_INFO, "LoadCapsuleOnDisk - LoadRecoveryCapsule (%d) - %r\n", CapsuleInstance, Status)); | |
| if (EFI_ERROR (Status)) { | |
| FreePages (CapsuleBuffer, EFI_SIZE_TO_PAGES (CapsuleSize)); | |
| break; | |
| } | |
| // | |
| // Capsule Update Mode, Split relocated Capsule buffer into different capsule vehical hobs. | |
| // | |
| Status = RetrieveRelocatedCapsule (CapsuleBuffer, CapsuleSize); | |
| break; | |
| } | |
| if (EFI_ERROR (Status)) { | |
| REPORT_STATUS_CODE ( | |
| EFI_ERROR_CODE | EFI_ERROR_MAJOR, | |
| (EFI_SOFTWARE_PEI_MODULE | EFI_SW_PEI_EC_NO_RECOVERY_CAPSULE) | |
| ); | |
| } | |
| return Status; | |
| } | |
| // | |
| // Any attack against GPT, Relocation Info Variable or temp relocation file will result in no Capsule HOB and return EFI_NOT_FOUND. | |
| // After flow to DXE phase. since no capsule hob is detected. Platform will clear Info flag and force restart. | |
| // No volunerability will be exposed | |
| // | |
| return EFI_NOT_FOUND; | |
| } |