edk2/ArmPkg/Library/ArmStandaloneMmCoreEntryPoint/SetPermissions.c
Yeoreum Yun c83db74426 ArmPkg, StandaloneMmPkg: optimise memory permission change
In StandaloneMm, the allowed memory permission combinations are ROX, RO,
and RW.

At the initial stage, all regions are mapped as ROX.
When drivers are loaded for relocation, the image is first mapped as RW.

However, the current mechanism introduces significant overhead
when changing memory permissions. For example, when changing permissions
from ROX to RW, the process is performed in two steps:

  1. Clear the execute (X) permission (ROX → RO)
  2. Add the write (W) permission (RO → RW)

To execute step (1), the system first retrieves the permissions of
the target region. This may trigger multiple SMC calls,
depending on the permissions of the pages within that region.

This permission retrieval operation is redundant, as there is no need
to maintain an intermediate state. Nevertheless,
it can cause additional SMC calls and coherence operations
from the SPMC (e.g., TLB flushes).

Therefore, this redundant operation should be removed, and in most cases,
the memory permission should be updated with a single
“set memory permission” operation.

Note:
The ArmxxxMemoryRegionxxx() interfaces e.g. ArmSetMemoryRegionNoExec(),
etc. return success when the Length parameter is 0 as
the SectionHeader.Misc.VirtualSize could be 0 Length
like .reloc section:

  UpdateMmFoundationPeCoffPermissions: Section 2 of image at 0x7004000
                                       has 0x42000040 permissions
  UpdateMmFoundationPeCoffPermissions: Section 2 of image at 0x7004000
                                       has .reloc name
  api_ffa_mem_perm_set page_count 0
  ERROR: FFA_MEM_PERM_SET: page_count was zero

Continuous-integration-options: PatchCheck.ignore-multi-package
Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
2026-05-29 18:44:57 +00:00

382 lines
12 KiB
C

/** @file
Locate, get and update PE/COFF permissions during Standalone MM
Foundation Entry point on ARM platforms.
Copyright (c) 2017 - 2021, Arm Ltd. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include <PiMm.h>
#include <PiPei.h>
#include <Guid/MmramMemoryReserve.h>
#include <Guid/MpInformation.h>
#include <Library/ArmStandaloneMmCoreEntryPoint.h>
#include <Library/ArmMmuLib.h>
#include <Library/ArmSvcLib.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/SerialPortLib.h>
#include <IndustryStandard/ArmStdSmc.h>
/**
Privileged firmware assigns RO & Executable attributes to all memory occupied
by the Boot Firmware Volume. This function sets the correct permissions of
sections in the Standalone MM Core module to be able to access RO and RW data
and make further progress in the boot process.
@param [in] ImageContext Pointer to PE/COFF image context
@param [in] ImageBase Base of image in memory
@param [in] SectionHeaderOffset Offset of PE/COFF image section header
@param [in] NumberOfSections Number of Sections
@param [in] ReadOnlyUpdater Function to change RO permissions
@param [in] ReadWriteUpdater Function to change RW permissions
**/
EFI_STATUS
EFIAPI
UpdateMmFoundationPeCoffPermissions (
IN CONST PE_COFF_LOADER_IMAGE_CONTEXT *ImageContext,
IN EFI_PHYSICAL_ADDRESS ImageBase,
IN UINT32 SectionHeaderOffset,
IN CONST UINT16 NumberOfSections,
IN REGION_PERMISSION_UPDATE_FUNC ReadOnlyUpdater,
IN REGION_PERMISSION_UPDATE_FUNC ReadWriteUpdater
)
{
EFI_IMAGE_SECTION_HEADER SectionHeader;
RETURN_STATUS Status;
EFI_PHYSICAL_ADDRESS Base;
UINT64 SectionAlignment;
UINTN Size;
UINTN ReadSize;
UINTN Index;
ASSERT (ImageContext != NULL);
SectionAlignment = ImageContext->SectionAlignment;
//
// Iterate over the sections
//
for (Index = 0; Index < NumberOfSections; Index++) {
//
// Read section header from file
//
Size = sizeof (EFI_IMAGE_SECTION_HEADER);
ReadSize = Size;
Status = ImageContext->ImageRead (
ImageContext->Handle,
SectionHeaderOffset,
&Size,
&SectionHeader
);
if (RETURN_ERROR (Status) || (Size != ReadSize)) {
DEBUG ((
DEBUG_ERROR,
"%a: ImageContext->ImageRead () failed (Status = %r)\n",
__func__,
Status
));
return Status;
}
DEBUG ((
DEBUG_INFO,
"%a: Section %d of image at 0x%lx has 0x%x permissions\n",
__func__,
Index,
ImageContext->ImageAddress,
SectionHeader.Characteristics
));
DEBUG ((
DEBUG_INFO,
"%a: Section %d of image at 0x%lx has %a name\n",
__func__,
Index,
ImageContext->ImageAddress,
SectionHeader.Name
));
DEBUG ((
DEBUG_INFO,
"%a: Section %d of image at 0x%lx has 0x%x address\n",
__func__,
Index,
ImageContext->ImageAddress,
ImageContext->ImageAddress + SectionHeader.VirtualAddress
));
DEBUG ((
DEBUG_INFO,
"%a: Section %d of image at 0x%lx has 0x%x data\n",
__func__,
Index,
ImageContext->ImageAddress,
SectionHeader.PointerToRawData
));
//
// If the section is marked as XN then remove the X attribute. Furthermore,
// if it is a writeable section then mark it appropriately as well.
//
if ((SectionHeader.Characteristics & EFI_IMAGE_SCN_MEM_EXECUTE) == 0) {
Base = ImageBase + SectionHeader.VirtualAddress;
if ((SectionHeader.Characteristics & EFI_IMAGE_SCN_MEM_WRITE) != 0) {
ReadWriteUpdater (Base, ALIGN_VALUE (SectionHeader.Misc.VirtualSize, SectionAlignment));
DEBUG ((
DEBUG_INFO,
"%a: Mapping section %d of image at 0x%lx with RW-XN permissions\n",
__func__,
Index,
ImageContext->ImageAddress
));
} else {
ReadOnlyUpdater (Base, ALIGN_VALUE (SectionHeader.Misc.VirtualSize, SectionAlignment));
DEBUG ((
DEBUG_INFO,
"%a: Mapping section %d of image at 0x%lx with RO-XN permissions\n",
__func__,
Index,
ImageContext->ImageAddress
));
}
} else {
DEBUG ((
DEBUG_INFO,
"%a: Ignoring section %d of image at 0x%lx with 0x%x permissions\n",
__func__,
Index,
ImageContext->ImageAddress,
SectionHeader.Characteristics
));
}
SectionHeaderOffset += sizeof (EFI_IMAGE_SECTION_HEADER);
}
return RETURN_SUCCESS;
}
/**
Privileged firmware assigns RO & Executable attributes to all memory occupied
by the Boot Firmware Volume. This function locates the Standalone MM Core
module PE/COFF image in the BFV and returns this information.
@param [in] BfvAddress Base Address of Boot Firmware Volume
@param [in, out] TeData Pointer to address for allocating memory
for PE/COFF image data
@param [in, out] TeDataSize Pointer to size of PE/COFF image data
**/
EFI_STATUS
EFIAPI
LocateStandaloneMmCorePeCoffData (
IN EFI_FIRMWARE_VOLUME_HEADER *BfvAddress,
IN OUT VOID **TeData,
IN OUT UINTN *TeDataSize
)
{
EFI_FFS_FILE_HEADER *FileHeader;
EFI_STATUS Status;
FileHeader = NULL;
Status = FfsFindNextFile (
EFI_FV_FILETYPE_SECURITY_CORE,
BfvAddress,
&FileHeader
);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"Unable to locate Standalone MM FFS file - 0x%x\n",
Status
));
return Status;
}
Status = FfsFindSectionData (EFI_SECTION_PE32, FileHeader, TeData, TeDataSize);
if (EFI_ERROR (Status)) {
Status = FfsFindSectionData (EFI_SECTION_TE, FileHeader, TeData, TeDataSize);
if (EFI_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"Unable to locate Standalone MM Section data - %r\n",
Status
));
return Status;
}
}
DEBUG ((DEBUG_INFO, "Found Standalone MM PE data - 0x%x\n", *TeData));
return Status;
}
/**
Returns the PC COFF section information.
@param [in, out] ImageContext Pointer to PE/COFF image context
@param [out] ImageBase Base of image in memory
@param [out] SectionHeaderOffset Offset of PE/COFF image section header
@param [out] NumberOfSections Number of Sections
**/
STATIC
EFI_STATUS
GetPeCoffSectionInformation (
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *ImageContext,
OUT EFI_PHYSICAL_ADDRESS *ImageBase,
OUT UINT32 *SectionHeaderOffset,
OUT UINT16 *NumberOfSections
)
{
RETURN_STATUS Status;
EFI_IMAGE_OPTIONAL_HEADER_PTR_UNION Hdr;
EFI_IMAGE_OPTIONAL_HEADER_UNION HdrData;
UINTN Size;
UINTN ReadSize;
ASSERT (ImageContext != NULL);
ASSERT (SectionHeaderOffset != NULL);
ASSERT (NumberOfSections != NULL);
Status = PeCoffLoaderGetImageInfo (ImageContext);
if (RETURN_ERROR (Status)) {
DEBUG ((
DEBUG_ERROR,
"%a: PeCoffLoaderGetImageInfo () failed (Status == %r)\n",
__func__,
Status
));
return Status;
}
if (ImageContext->SectionAlignment < EFI_PAGE_SIZE) {
//
// The sections need to be at least 4 KB aligned, since that is the
// granularity at which we can tighten permissions.
//
if (!ImageContext->IsTeImage) {
DEBUG ((
DEBUG_WARN,
"%a: non-TE Image at 0x%lx has SectionAlignment < 4 KB (%lu)\n",
__func__,
ImageContext->ImageAddress,
ImageContext->SectionAlignment
));
return RETURN_UNSUPPORTED;
}
ImageContext->SectionAlignment = EFI_PAGE_SIZE;
}
//
// Read the PE/COFF Header. For PE32 (32-bit) this will read in too much
// data, but that should not hurt anything. Hdr.Pe32->OptionalHeader.Magic
// determines if this is a PE32 or PE32+ image. The magic is in the same
// location in both images.
//
Hdr.Union = &HdrData;
Size = sizeof (EFI_IMAGE_OPTIONAL_HEADER_UNION);
ReadSize = Size;
Status = ImageContext->ImageRead (
ImageContext->Handle,
ImageContext->PeCoffHeaderOffset,
&Size,
Hdr.Pe32
);
if (RETURN_ERROR (Status) || (Size != ReadSize)) {
DEBUG ((
DEBUG_ERROR,
"%a: TmpContext->ImageRead () failed (Status = %r)\n",
__func__,
Status
));
return Status;
}
*ImageBase = ImageContext->ImageAddress;
if (!ImageContext->IsTeImage) {
ASSERT (Hdr.Pe32->Signature == EFI_IMAGE_NT_SIGNATURE);
*SectionHeaderOffset = ImageContext->PeCoffHeaderOffset + sizeof (UINT32) +
sizeof (EFI_IMAGE_FILE_HEADER);
*NumberOfSections = Hdr.Pe32->FileHeader.NumberOfSections;
switch (Hdr.Pe32->OptionalHeader.Magic) {
case EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC:
*SectionHeaderOffset += Hdr.Pe32->FileHeader.SizeOfOptionalHeader;
break;
case EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC:
*SectionHeaderOffset += Hdr.Pe32Plus->FileHeader.SizeOfOptionalHeader;
break;
default:
ASSERT (FALSE);
}
} else {
*SectionHeaderOffset = (UINTN)(sizeof (EFI_TE_IMAGE_HEADER));
*NumberOfSections = Hdr.Te->NumberOfSections;
*ImageBase -= (UINT32)Hdr.Te->StrippedSize - sizeof (EFI_TE_IMAGE_HEADER);
}
return RETURN_SUCCESS;
}
/**
Privileged firmware assigns RO & Executable attributes to all memory occupied
by the Boot Firmware Volume. This function locates the section information of
the Standalone MM Core module to be able to change permissions of the
individual sections later in the boot process.
@param [in] TeData Pointer to PE/COFF image data
@param [in, out] ImageContext Pointer to PE/COFF image context
@param [out] ImageBase Pointer to ImageBase variable
@param [in, out] SectionHeaderOffset Offset of PE/COFF image section header
@param [in, out] NumberOfSections Number of Sections
**/
EFI_STATUS
EFIAPI
GetStandaloneMmCorePeCoffSections (
IN VOID *TeData,
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *ImageContext,
OUT EFI_PHYSICAL_ADDRESS *ImageBase,
IN OUT UINT32 *SectionHeaderOffset,
IN OUT UINT16 *NumberOfSections
)
{
EFI_STATUS Status;
// Initialize the Image Context
ZeroMem (ImageContext, sizeof (PE_COFF_LOADER_IMAGE_CONTEXT));
ImageContext->Handle = TeData;
ImageContext->ImageRead = PeCoffLoaderImageReadFromMemory;
DEBUG ((DEBUG_INFO, "Found Standalone MM PE data - 0x%x\n", TeData));
Status = GetPeCoffSectionInformation (
ImageContext,
ImageBase,
SectionHeaderOffset,
NumberOfSections
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Unable to locate Standalone MM Core PE-COFF Section information - %r\n", Status));
return Status;
}
DEBUG ((
DEBUG_INFO,
"Standalone MM Core PE-COFF SectionHeaderOffset - 0x%x, NumberOfSections - %d\n",
*SectionHeaderOffset,
*NumberOfSections
));
return Status;
}