mirror of https://github.com/acidanthera/audk.git
435 lines
12 KiB
C
435 lines
12 KiB
C
/** @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.
|
|
|
|
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);
|
|
AsmWriteCr3 (PageTable);
|
|
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;
|
|
}
|