mirror of
				https://gitlab.com/qemu-project/edk2.git
				synced 2025-11-03 07:59:00 +08:00 
			
		
		
		
	The purpose of writing CR3 in ConvertMemoryPageToNotPresent is just to flush TLB, because CR3 won't be changed in function ConvertMemoryPageToNotPresent. After ConvertMemoryPageToNotPresent, there is always a flush TLB function. Also, because ConvertMemoryPageToNotPresent in called in a loop, to improve performance, there is no need to flush TLB inside ConvertMemoryPageToNotPresent. Just flushing TLB after the loop is enough. Reviewed-by: Ray Ni <ray.ni@intel.com> Reviewed-by: Laszlo Ersek <lersek@redhat.com> Cc: Rahul Kumar <rahul1.kumar@intel.com> Cc: Gerd Hoffmann <kraxel@redhat.com> Signed-off-by: Zhiguang Liu <zhiguang.liu@intel.com>
		
			
				
	
	
		
			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. 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;
 | 
						|
}
 |