UefiCpuPkg/PiSmmCpu: Add SMM Comm Buffer Paging Protection.

This patch sets the normal OS buffer EfiLoaderCode/Data,
EfiBootServicesCode/Data, EfiConventionalMemory, EfiACPIReclaimMemory
to be not present after SmmReadyToLock.

To access these region in OS runtime phase is not a good solution.

Previously, we did similar check in SmmMemLib to help SMI handler
do the check. But if SMI handler forgets the check, it can still
access these OS region and bring risk.

So here we enforce the policy to prevent it happening.

Cc: Jeff Fan <jeff.fan@intel.com>
Cc: Michael D Kinney <michael.d.kinney@intel.com>
Cc: Laszlo Ersek <lersek@redhat.com>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Jiewen Yao <jiewen.yao@intel.com>
Reviewed-by: Jeff Fan <jeff.fan@intel.com>
This commit is contained in:
Jiewen Yao 2016-11-24 13:36:56 +08:00
parent 09119a00cc
commit d2fc771113
5 changed files with 324 additions and 12 deletions

View File

@ -149,6 +149,13 @@ SmiPFHandler (
);
CpuDeadLoop ();
}
if (IsSmmCommBufferForbiddenAddress (PFAddress)) {
DEBUG ((DEBUG_ERROR, "Access SMM communication forbidden address (0x%x)!\n", PFAddress));
DEBUG_CODE (
DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextIa32->Eip);
);
CpuDeadLoop ();
}
}
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {

View File

@ -503,6 +503,11 @@ SmmReadyToLockEventNotify (
{
GetAcpiCpuData ();
//
// Cache a copy of UEFI memory map before we start profiling feature.
//
GetUefiMemoryMap ();
//
// Set SMM ready to lock flag and return
//
@ -1153,17 +1158,6 @@ ConfigSmmCodeAccessCheck (
}
}
/**
Set code region to be read only and data region to be execute disable.
**/
VOID
SetRegionAttributes (
VOID
)
{
SetMemMapAttributes ();
}
/**
This API provides a way to allocate memory for page table.
@ -1320,7 +1314,12 @@ PerformRemainingTasks (
//
// Mark critical region to be read-only in page table
//
SetRegionAttributes ();
SetMemMapAttributes ();
//
// For outside SMRAM, we only map SMM communication buffer or MMIO.
//
SetUefiMemMapAttributes ();
//
// Set page table itself to be read-only

View File

@ -838,6 +838,35 @@ SetMemMapAttributes (
VOID
);
/**
This function sets UEFI memory attribute according to UEFI memory map.
**/
VOID
SetUefiMemMapAttributes (
VOID
);
/**
Return if the Address is forbidden as SMM communication buffer.
@param[in] Address the address to be checked
@return TRUE The address is forbidden as SMM communication buffer.
@return FALSE The address is allowed as SMM communication buffer.
**/
BOOLEAN
IsSmmCommBufferForbiddenAddress (
IN UINT64 Address
);
/**
This function caches the UEFI memory map information.
**/
VOID
GetUefiMemoryMap (
VOID
);
/**
This function sets memory attribute for page table.
**/

View File

@ -16,6 +16,13 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#define NEXT_MEMORY_DESCRIPTOR(MemoryDescriptor, Size) \
((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(MemoryDescriptor) + (Size)))
#define PREVIOUS_MEMORY_DESCRIPTOR(MemoryDescriptor, Size) \
((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(MemoryDescriptor) - (Size)))
EFI_MEMORY_DESCRIPTOR *mUefiMemoryMap;
UINTN mUefiMemoryMapSize;
UINTN mUefiDescriptorSize;
PAGE_ATTRIBUTE_TABLE mPageAttributeTable[] = {
{Page4K, SIZE_4KB, PAGING_4K_ADDRESS_MASK_64},
{Page2M, SIZE_2MB, PAGING_2M_ADDRESS_MASK_64},
@ -823,3 +830,266 @@ SetMemMapAttributes (
return ;
}
/**
Sort memory map entries based upon PhysicalStart, from low to high.
@param MemoryMap A pointer to the buffer in which firmware places
the current memory map.
@param MemoryMapSize Size, in bytes, of the MemoryMap buffer.
@param DescriptorSize Size, in bytes, of an individual EFI_MEMORY_DESCRIPTOR.
**/
STATIC
VOID
SortMemoryMap (
IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
IN UINTN MemoryMapSize,
IN UINTN DescriptorSize
)
{
EFI_MEMORY_DESCRIPTOR *MemoryMapEntry;
EFI_MEMORY_DESCRIPTOR *NextMemoryMapEntry;
EFI_MEMORY_DESCRIPTOR *MemoryMapEnd;
EFI_MEMORY_DESCRIPTOR TempMemoryMap;
MemoryMapEntry = MemoryMap;
NextMemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
MemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + MemoryMapSize);
while (MemoryMapEntry < MemoryMapEnd) {
while (NextMemoryMapEntry < MemoryMapEnd) {
if (MemoryMapEntry->PhysicalStart > NextMemoryMapEntry->PhysicalStart) {
CopyMem (&TempMemoryMap, MemoryMapEntry, sizeof(EFI_MEMORY_DESCRIPTOR));
CopyMem (MemoryMapEntry, NextMemoryMapEntry, sizeof(EFI_MEMORY_DESCRIPTOR));
CopyMem (NextMemoryMapEntry, &TempMemoryMap, sizeof(EFI_MEMORY_DESCRIPTOR));
}
NextMemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (NextMemoryMapEntry, DescriptorSize);
}
MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
NextMemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
}
}
/**
Return if a UEFI memory page should be marked as not present in SMM page table.
If the memory map entries type is
EfiLoaderCode/Data, EfiBootServicesCode/Data, EfiConventionalMemory,
EfiUnusableMemory, EfiACPIReclaimMemory, return TRUE.
Or return FALSE.
@param[in] MemoryMap A pointer to the memory descriptor.
@return TRUE The memory described will be marked as not present in SMM page table.
@return FALSE The memory described will not be marked as not present in SMM page table.
**/
BOOLEAN
IsUefiPageNotPresent (
IN EFI_MEMORY_DESCRIPTOR *MemoryMap
)
{
switch (MemoryMap->Type) {
case EfiLoaderCode:
case EfiLoaderData:
case EfiBootServicesCode:
case EfiBootServicesData:
case EfiConventionalMemory:
case EfiUnusableMemory:
case EfiACPIReclaimMemory:
return TRUE;
default:
return FALSE;
}
}
/**
Merge continous memory map entries whose type is
EfiLoaderCode/Data, EfiBootServicesCode/Data, EfiConventionalMemory,
EfiUnusableMemory, EfiACPIReclaimMemory, because the memory described by
these entries will be set as NOT present in SMM page table.
@param[in, out] MemoryMap A pointer to the buffer in which firmware places
the current memory map.
@param[in, out] MemoryMapSize A pointer to the size, in bytes, of the
MemoryMap buffer. On input, this is the size of
the current memory map. On output,
it is the size of new memory map after merge.
@param[in] DescriptorSize Size, in bytes, of an individual EFI_MEMORY_DESCRIPTOR.
**/
STATIC
VOID
MergeMemoryMapForNotPresentEntry (
IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
IN OUT UINTN *MemoryMapSize,
IN UINTN DescriptorSize
)
{
EFI_MEMORY_DESCRIPTOR *MemoryMapEntry;
EFI_MEMORY_DESCRIPTOR *MemoryMapEnd;
UINT64 MemoryBlockLength;
EFI_MEMORY_DESCRIPTOR *NewMemoryMapEntry;
EFI_MEMORY_DESCRIPTOR *NextMemoryMapEntry;
MemoryMapEntry = MemoryMap;
NewMemoryMapEntry = MemoryMap;
MemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + *MemoryMapSize);
while ((UINTN)MemoryMapEntry < (UINTN)MemoryMapEnd) {
CopyMem (NewMemoryMapEntry, MemoryMapEntry, sizeof(EFI_MEMORY_DESCRIPTOR));
NextMemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
do {
MemoryBlockLength = (UINT64) (EFI_PAGES_TO_SIZE((UINTN)MemoryMapEntry->NumberOfPages));
if (((UINTN)NextMemoryMapEntry < (UINTN)MemoryMapEnd) &&
IsUefiPageNotPresent(MemoryMapEntry) && IsUefiPageNotPresent(NextMemoryMapEntry) &&
((MemoryMapEntry->PhysicalStart + MemoryBlockLength) == NextMemoryMapEntry->PhysicalStart)) {
MemoryMapEntry->NumberOfPages += NextMemoryMapEntry->NumberOfPages;
if (NewMemoryMapEntry != MemoryMapEntry) {
NewMemoryMapEntry->NumberOfPages += NextMemoryMapEntry->NumberOfPages;
}
NextMemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (NextMemoryMapEntry, DescriptorSize);
continue;
} else {
MemoryMapEntry = PREVIOUS_MEMORY_DESCRIPTOR (NextMemoryMapEntry, DescriptorSize);
break;
}
} while (TRUE);
MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize);
NewMemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (NewMemoryMapEntry, DescriptorSize);
}
*MemoryMapSize = (UINTN)NewMemoryMapEntry - (UINTN)MemoryMap;
return ;
}
/**
This function caches the UEFI memory map information.
**/
VOID
GetUefiMemoryMap (
VOID
)
{
EFI_STATUS Status;
UINTN MapKey;
UINT32 DescriptorVersion;
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN UefiMemoryMapSize;
DEBUG ((DEBUG_INFO, "GetUefiMemoryMap\n"));
UefiMemoryMapSize = 0;
MemoryMap = NULL;
Status = gBS->GetMemoryMap (
&UefiMemoryMapSize,
MemoryMap,
&MapKey,
&mUefiDescriptorSize,
&DescriptorVersion
);
ASSERT (Status == EFI_BUFFER_TOO_SMALL);
do {
Status = gBS->AllocatePool (EfiBootServicesData, UefiMemoryMapSize, (VOID **)&MemoryMap);
ASSERT (MemoryMap != NULL);
if (MemoryMap == NULL) {
return ;
}
Status = gBS->GetMemoryMap (
&UefiMemoryMapSize,
MemoryMap,
&MapKey,
&mUefiDescriptorSize,
&DescriptorVersion
);
if (EFI_ERROR (Status)) {
gBS->FreePool (MemoryMap);
MemoryMap = NULL;
}
} while (Status == EFI_BUFFER_TOO_SMALL);
SortMemoryMap (MemoryMap, UefiMemoryMapSize, mUefiDescriptorSize);
MergeMemoryMapForNotPresentEntry (MemoryMap, &UefiMemoryMapSize, mUefiDescriptorSize);
mUefiMemoryMapSize = UefiMemoryMapSize;
mUefiMemoryMap = AllocateCopyPool (UefiMemoryMapSize, MemoryMap);
ASSERT (mUefiMemoryMap != NULL);
gBS->FreePool (MemoryMap);
}
/**
This function sets UEFI memory attribute according to UEFI memory map.
The normal memory region is marked as not present, such as
EfiLoaderCode/Data, EfiBootServicesCode/Data, EfiConventionalMemory,
EfiUnusableMemory, EfiACPIReclaimMemory.
**/
VOID
SetUefiMemMapAttributes (
VOID
)
{
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN MemoryMapEntryCount;
UINTN Index;
DEBUG ((DEBUG_INFO, "SetUefiMemMapAttributes\n"));
if (mUefiMemoryMap == NULL) {
DEBUG ((DEBUG_INFO, "UefiMemoryMap - NULL\n"));
return ;
}
MemoryMapEntryCount = mUefiMemoryMapSize/mUefiDescriptorSize;
MemoryMap = mUefiMemoryMap;
for (Index = 0; Index < MemoryMapEntryCount; Index++) {
if (IsUefiPageNotPresent(MemoryMap)) {
DEBUG ((DEBUG_INFO, "UefiMemory protection: 0x%lx - 0x%lx\n", MemoryMap->PhysicalStart, MemoryMap->PhysicalStart + (UINT64)EFI_PAGES_TO_SIZE((UINTN)MemoryMap->NumberOfPages)));
SmmSetMemoryAttributes (
MemoryMap->PhysicalStart,
EFI_PAGES_TO_SIZE((UINTN)MemoryMap->NumberOfPages),
EFI_MEMORY_RP
);
}
MemoryMap = NEXT_MEMORY_DESCRIPTOR(MemoryMap, mUefiDescriptorSize);
}
//
// Do free mUefiMemoryMap, it will be checked in IsSmmCommBufferForbiddenAddress().
//
}
/**
Return if the Address is forbidden as SMM communication buffer.
@param[in] Address the address to be checked
@return TRUE The address is forbidden as SMM communication buffer.
@return FALSE The address is allowed as SMM communication buffer.
**/
BOOLEAN
IsSmmCommBufferForbiddenAddress (
IN UINT64 Address
)
{
EFI_MEMORY_DESCRIPTOR *MemoryMap;
UINTN MemoryMapEntryCount;
UINTN Index;
MemoryMap = mUefiMemoryMap;
MemoryMapEntryCount = mUefiMemoryMapSize/mUefiDescriptorSize;
for (Index = 0; Index < MemoryMapEntryCount; Index++) {
if (IsUefiPageNotPresent (MemoryMap)) {
if ((Address >= MemoryMap->PhysicalStart) &&
(Address < MemoryMap->PhysicalStart + EFI_PAGES_TO_SIZE((UINTN)MemoryMap->NumberOfPages)) ) {
return TRUE;
}
}
MemoryMap = NEXT_MEMORY_DESCRIPTOR(MemoryMap, mUefiDescriptorSize);
}
return FALSE;
}

View File

@ -867,6 +867,13 @@ SmiPFHandler (
);
CpuDeadLoop ();
}
if (IsSmmCommBufferForbiddenAddress (PFAddress)) {
DEBUG ((DEBUG_ERROR, "Access SMM communication forbidden address (0x%lx)!\n", PFAddress));
DEBUG_CODE (
DumpModuleInfoByIp ((UINTN)SystemContext.SystemContextX64->Rip);
);
CpuDeadLoop ();
}
}
if (FeaturePcdGet (PcdCpuSmmProfileEnable)) {