| /** @file | |
| Basic paging support for the CPU to enable Stack Guard. | |
| Copyright (c) 2018 - 2019, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| #include <Register/Intel/Cpuid.h> | |
| #include <Register/Intel/Msr.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/CpuLib.h> | |
| #include <Library/BaseLib.h> | |
| #include <Guid/MigratedFvInfo.h> | |
| #include "CpuMpPei.h" | |
| #define PAGING_4K_ADDRESS_MASK_64 0x000FFFFFFFFFF000ull | |
| EFI_PEI_NOTIFY_DESCRIPTOR mPostMemNotifyList[] = { | |
| { | |
| (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), | |
| &gEfiPeiMemoryDiscoveredPpiGuid, | |
| MemoryDiscoveredPpiNotifyCallback | |
| } | |
| }; | |
| /** | |
| The function will check if IA32 PAE is supported. | |
| @retval TRUE IA32 PAE is supported. | |
| @retval FALSE IA32 PAE is not supported. | |
| **/ | |
| BOOLEAN | |
| IsIa32PaeSupported ( | |
| VOID | |
| ) | |
| { | |
| UINT32 RegEax; | |
| CPUID_VERSION_INFO_EDX RegEdx; | |
| AsmCpuid (CPUID_SIGNATURE, &RegEax, NULL, NULL, NULL); | |
| if (RegEax >= CPUID_VERSION_INFO) { | |
| AsmCpuid (CPUID_VERSION_INFO, NULL, NULL, NULL, &RegEdx.Uint32); | |
| if (RegEdx.Bits.PAE != 0) { | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| /** | |
| This API provides a way to allocate memory for page table. | |
| @param Pages The number of 4 KB pages to allocate. | |
| @return A pointer to the allocated buffer or NULL if allocation fails. | |
| **/ | |
| VOID * | |
| AllocatePageTableMemory ( | |
| IN UINTN Pages | |
| ) | |
| { | |
| VOID *Address; | |
| Address = AllocatePages (Pages); | |
| if (Address != NULL) { | |
| ZeroMem (Address, EFI_PAGES_TO_SIZE (Pages)); | |
| } | |
| return Address; | |
| } | |
| /** | |
| This function modifies the page attributes for the memory region specified | |
| by BaseAddress and Length to not present. This function only change page | |
| table, but not flush TLB. Caller have the responsbility to flush TLB. | |
| Caller should make sure BaseAddress and Length is at page boundary. | |
| @param[in] BaseAddress Start address of a memory region. | |
| @param[in] Length Size in bytes of the memory region. | |
| @retval RETURN_SUCCESS The memory region is changed to not present. | |
| @retval RETURN_OUT_OF_RESOURCES There are not enough system resources to modify | |
| the attributes. | |
| @retval RETURN_UNSUPPORTED Cannot modify the attributes of given memory. | |
| **/ | |
| RETURN_STATUS | |
| ConvertMemoryPageToNotPresent ( | |
| IN PHYSICAL_ADDRESS BaseAddress, | |
| IN UINT64 Length | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN PageTable; | |
| EFI_PHYSICAL_ADDRESS Buffer; | |
| UINTN BufferSize; | |
| IA32_MAP_ATTRIBUTE MapAttribute; | |
| IA32_MAP_ATTRIBUTE MapMask; | |
| PAGING_MODE PagingMode; | |
| IA32_CR4 Cr4; | |
| BOOLEAN Page5LevelSupport; | |
| UINT32 RegEax; | |
| BOOLEAN Page1GSupport; | |
| CPUID_EXTENDED_CPU_SIG_EDX RegEdx; | |
| if (sizeof (UINTN) == sizeof (UINT64)) { | |
| // | |
| // Check Page5Level Support or not. | |
| // | |
| Cr4.UintN = AsmReadCr4 (); | |
| Page5LevelSupport = (Cr4.Bits.LA57 ? TRUE : FALSE); | |
| // | |
| // Check Page1G Support or not. | |
| // | |
| Page1GSupport = FALSE; | |
| AsmCpuid (CPUID_EXTENDED_FUNCTION, &RegEax, NULL, NULL, NULL); | |
| if (RegEax >= CPUID_EXTENDED_CPU_SIG) { | |
| AsmCpuid (CPUID_EXTENDED_CPU_SIG, NULL, NULL, NULL, &RegEdx.Uint32); | |
| if (RegEdx.Bits.Page1GB != 0) { | |
| Page1GSupport = TRUE; | |
| } | |
| } | |
| // | |
| // Decide Paging Mode according Page5LevelSupport & Page1GSupport. | |
| // | |
| if (Page5LevelSupport) { | |
| PagingMode = Page1GSupport ? Paging5Level1GB : Paging5Level; | |
| } else { | |
| PagingMode = Page1GSupport ? Paging4Level1GB : Paging4Level; | |
| } | |
| } else { | |
| PagingMode = PagingPae; | |
| } | |
| MapAttribute.Uint64 = 0; | |
| MapMask.Uint64 = 0; | |
| MapMask.Bits.Present = 1; | |
| PageTable = AsmReadCr3 () & PAGING_4K_ADDRESS_MASK_64; | |
| BufferSize = 0; | |
| // | |
| // Get required buffer size for the pagetable that will be created. | |
| // | |
| Status = PageTableMap (&PageTable, PagingMode, 0, &BufferSize, BaseAddress, Length, &MapAttribute, &MapMask, NULL); | |
| if (Status == EFI_BUFFER_TOO_SMALL) { | |
| // | |
| // Allocate required Buffer. | |
| // | |
| Status = PeiServicesAllocatePages ( | |
| EfiBootServicesData, | |
| EFI_SIZE_TO_PAGES (BufferSize), | |
| &Buffer | |
| ); | |
| ASSERT_EFI_ERROR (Status); | |
| if (EFI_ERROR (Status)) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Status = PageTableMap (&PageTable, PagingMode, (VOID *)(UINTN)Buffer, &BufferSize, BaseAddress, Length, &MapAttribute, &MapMask, NULL); | |
| } | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } | |
| /** | |
| Enable PAE Page Table. | |
| @retval EFI_SUCCESS The PAE Page Table was enabled successfully. | |
| @retval EFI_OUT_OF_RESOURCES The PAE Page Table could not be enabled due to lack of available memory. | |
| **/ | |
| EFI_STATUS | |
| EnablePaePageTable ( | |
| VOID | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| UINTN PageTable; | |
| VOID *Buffer; | |
| UINTN BufferSize; | |
| IA32_MAP_ATTRIBUTE MapAttribute; | |
| IA32_MAP_ATTRIBUTE MapMask; | |
| PageTable = 0; | |
| Buffer = NULL; | |
| BufferSize = 0; | |
| MapAttribute.Uint64 = 0; | |
| MapMask.Uint64 = MAX_UINT64; | |
| MapAttribute.Bits.Present = 1; | |
| MapAttribute.Bits.ReadWrite = 1; | |
| // | |
| // 1:1 map 4GB in 32bit mode | |
| // | |
| Status = PageTableMap (&PageTable, PagingPae, 0, &BufferSize, 0, SIZE_4GB, &MapAttribute, &MapMask, NULL); | |
| ASSERT (Status == EFI_BUFFER_TOO_SMALL); | |
| if (Status != EFI_BUFFER_TOO_SMALL) { | |
| return Status; | |
| } | |
| // | |
| // Allocate required Buffer. | |
| // | |
| Buffer = AllocatePageTableMemory (EFI_SIZE_TO_PAGES (BufferSize)); | |
| ASSERT (Buffer != NULL); | |
| if (Buffer == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Status = PageTableMap (&PageTable, PagingPae, Buffer, &BufferSize, 0, SIZE_4GB, &MapAttribute, &MapMask, NULL); | |
| ASSERT_EFI_ERROR (Status); | |
| if (EFI_ERROR (Status) || (PageTable == 0)) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Write the Pagetable to CR3. | |
| // | |
| AsmWriteCr3 (PageTable); | |
| // | |
| // Enable CR4.PAE | |
| // | |
| AsmWriteCr4 (AsmReadCr4 () | BIT5); | |
| // | |
| // Enable CR0.PG | |
| // | |
| AsmWriteCr0 (AsmReadCr0 () | BIT31); | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "EnablePaePageTable: Created PageTable = 0x%x, BufferSize = %x\n", | |
| PageTable, | |
| BufferSize | |
| )); | |
| return Status; | |
| } | |
| /** | |
| Get the base address of current AP's stack. | |
| This function is called in AP's context and assumes that whole calling stacks | |
| (till this function) consumed by AP's wakeup procedure will not exceed 4KB. | |
| PcdCpuApStackSize must be configured with value taking the Guard page into | |
| account. | |
| @param[in,out] Buffer The pointer to private data buffer. | |
| **/ | |
| VOID | |
| EFIAPI | |
| GetStackBase ( | |
| IN OUT VOID *Buffer | |
| ) | |
| { | |
| EFI_PHYSICAL_ADDRESS StackBase; | |
| UINTN Index; | |
| MpInitLibWhoAmI (&Index); | |
| StackBase = (EFI_PHYSICAL_ADDRESS)(UINTN)&StackBase; | |
| StackBase += BASE_4KB; | |
| StackBase &= ~((EFI_PHYSICAL_ADDRESS)BASE_4KB - 1); | |
| StackBase -= PcdGet32 (PcdCpuApStackSize); | |
| *((EFI_PHYSICAL_ADDRESS *)Buffer + Index) = StackBase; | |
| } | |
| /** | |
| Setup stack Guard page at the stack base of each processor. BSP and APs have | |
| different way to get stack base address. | |
| **/ | |
| VOID | |
| SetupStackGuardPage ( | |
| VOID | |
| ) | |
| { | |
| EFI_PEI_HOB_POINTERS Hob; | |
| EFI_PHYSICAL_ADDRESS *StackBase; | |
| UINTN NumberOfProcessors; | |
| UINTN Bsp; | |
| UINTN Index; | |
| EFI_STATUS Status; | |
| // | |
| // One extra page at the bottom of the stack is needed for Guard page. | |
| // | |
| if (PcdGet32 (PcdCpuApStackSize) <= EFI_PAGE_SIZE) { | |
| DEBUG ((DEBUG_ERROR, "PcdCpuApStackSize is not big enough for Stack Guard!\n")); | |
| ASSERT (FALSE); | |
| } | |
| Status = MpInitLibGetNumberOfProcessors (&NumberOfProcessors, NULL); | |
| ASSERT_EFI_ERROR (Status); | |
| if (EFI_ERROR (Status)) { | |
| NumberOfProcessors = 1; | |
| } | |
| StackBase = (EFI_PHYSICAL_ADDRESS *)AllocatePages (EFI_SIZE_TO_PAGES (sizeof (EFI_PHYSICAL_ADDRESS) * NumberOfProcessors)); | |
| ASSERT (StackBase != NULL); | |
| if (StackBase == NULL) { | |
| return; | |
| } | |
| ZeroMem (StackBase, sizeof (EFI_PHYSICAL_ADDRESS) * NumberOfProcessors); | |
| MpInitLibStartupAllAPs (GetStackBase, FALSE, NULL, 0, (VOID *)StackBase, NULL); | |
| MpInitLibWhoAmI (&Bsp); | |
| Hob.Raw = GetHobList (); | |
| while ((Hob.Raw = GetNextHob (EFI_HOB_TYPE_MEMORY_ALLOCATION, Hob.Raw)) != NULL) { | |
| if (CompareGuid ( | |
| &gEfiHobMemoryAllocStackGuid, | |
| &(Hob.MemoryAllocationStack->AllocDescriptor.Name) | |
| )) | |
| { | |
| StackBase[Bsp] = Hob.MemoryAllocationStack->AllocDescriptor.MemoryBaseAddress; | |
| break; | |
| } | |
| Hob.Raw = GET_NEXT_HOB (Hob); | |
| } | |
| for (Index = 0; Index < NumberOfProcessors; ++Index) { | |
| ASSERT (StackBase[Index] != 0); | |
| // | |
| // Set Guard page at stack base address. | |
| // | |
| ConvertMemoryPageToNotPresent (StackBase[Index], EFI_PAGE_SIZE); | |
| DEBUG (( | |
| DEBUG_INFO, | |
| "Stack Guard set at %lx [cpu%lu]!\n", | |
| (UINT64)StackBase[Index], | |
| (UINT64)Index | |
| )); | |
| } | |
| FreePages (StackBase, EFI_SIZE_TO_PAGES (sizeof (EFI_PHYSICAL_ADDRESS) * NumberOfProcessors)); | |
| // | |
| // Publish the changes of page table. | |
| // | |
| CpuFlushTlb (); | |
| } | |
| /** | |
| Enable/setup stack guard for each processor if PcdCpuStackGuard is set to TRUE. | |
| Doing this in the memory-discovered callback is to make sure the Stack Guard | |
| feature to cover as most PEI code as possible. | |
| @param[in] PeiServices General purpose services available to every PEIM. | |
| @param[in] NotifyDescriptor The notification structure this PEIM registered on install. | |
| @param[in] Ppi The memory discovered PPI. Not used. | |
| @retval EFI_SUCCESS The function completed successfully. | |
| @retval others There's error in MP initialization. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| MemoryDiscoveredPpiNotifyCallback ( | |
| IN EFI_PEI_SERVICES **PeiServices, | |
| IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDescriptor, | |
| IN VOID *Ppi | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| BOOLEAN InitStackGuard; | |
| EDKII_MIGRATED_FV_INFO *MigratedFvInfo; | |
| EFI_PEI_HOB_POINTERS Hob; | |
| IA32_CR0 Cr0; | |
| // | |
| // Paging must be setup first. Otherwise the exception TSS setup during MP | |
| // initialization later will not contain paging information and then fail | |
| // the task switch (for the sake of stack switch). | |
| // | |
| InitStackGuard = FALSE; | |
| Hob.Raw = NULL; | |
| if (IsIa32PaeSupported ()) { | |
| Hob.Raw = GetFirstGuidHob (&gEdkiiMigratedFvInfoGuid); | |
| InitStackGuard = PcdGetBool (PcdCpuStackGuard); | |
| } | |
| // | |
| // Some security features depend on the page table enabling. So, here | |
| // is to enable paging if it is not enabled (only in 32bit mode). | |
| // | |
| Cr0.UintN = AsmReadCr0 (); | |
| if ((Cr0.Bits.PG == 0) && (InitStackGuard || (Hob.Raw != NULL))) { | |
| ASSERT (sizeof (UINTN) == sizeof (UINT32)); | |
| Status = EnablePaePageTable (); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG ((DEBUG_ERROR, "MemoryDiscoveredPpiNotifyCallback: Failed to enable PAE page table: %r.\n", Status)); | |
| CpuDeadLoop (); | |
| } | |
| } | |
| Status = InitializeCpuMpWorker ((CONST EFI_PEI_SERVICES **)PeiServices); | |
| ASSERT_EFI_ERROR (Status); | |
| if (InitStackGuard) { | |
| SetupStackGuardPage (); | |
| } | |
| while (Hob.Raw != NULL) { | |
| MigratedFvInfo = GET_GUID_HOB_DATA (Hob); | |
| // | |
| // Enable #PF exception, so if the code access SPI after disable NEM, it will generate | |
| // the exception to avoid potential vulnerability. | |
| // | |
| ConvertMemoryPageToNotPresent (MigratedFvInfo->FvOrgBase, MigratedFvInfo->FvLength); | |
| Hob.Raw = GET_NEXT_HOB (Hob); | |
| Hob.Raw = GetNextGuidHob (&gEdkiiMigratedFvInfoGuid, Hob.Raw); | |
| } | |
| CpuFlushTlb (); | |
| return Status; | |
| } |