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