X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=MdeModulePkg%2FCore%2FDxe%2FMisc%2FMemoryProtection.c;h=7689c794a8ad22601965303e4dfce255e3923c09;hb=7babb4372e6a34cbbc54249b25056272a5a9924c;hp=c36612a1b1f2c1b4f49fe26dfb3501db4aaaa9b2;hpb=d0e92aad46706643469cf3ecc235a9cee5b52323;p=mirror_edk2.git diff --git a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c index c36612a1b1..7689c794a8 100644 --- a/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c +++ b/MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c @@ -64,8 +64,16 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. #define DO_NOT_PROTECT 0x00000000 #define PROTECT_IF_ALIGNED_ELSE_ALLOW 0x00000001 +#define MEMORY_TYPE_OS_RESERVED_MIN 0x80000000 +#define MEMORY_TYPE_OEM_RESERVED_MIN 0x70000000 + +#define PREVIOUS_MEMORY_DESCRIPTOR(MemoryDescriptor, Size) \ + ((EFI_MEMORY_DESCRIPTOR *)((UINT8 *)(MemoryDescriptor) - (Size))) + UINT32 mImageProtectionPolicy; +extern LIST_ENTRY mGcdMemorySpaceMap; + /** Sort code section in image record, based upon CodeSegmentBase from low to high. @@ -336,12 +344,12 @@ IsMemoryProtectionSectionAligned ( switch (MemoryType) { case EfiRuntimeServicesCode: case EfiACPIMemoryNVS: - PageAlignment = EFI_ACPI_RUNTIME_PAGE_ALLOCATION_ALIGNMENT; + PageAlignment = RUNTIME_PAGE_ALLOCATION_GRANULARITY; break; case EfiRuntimeServicesData: case EfiACPIReclaimMemory: ASSERT (FALSE); - PageAlignment = EFI_ACPI_RUNTIME_PAGE_ALLOCATION_ALIGNMENT; + PageAlignment = RUNTIME_PAGE_ALLOCATION_GRANULARITY; break; case EfiBootServicesCode: case EfiLoaderCode: @@ -533,7 +541,15 @@ ProtectUefiImageCommon ( Name[7] )); - if ((Section[Index].Characteristics & EFI_IMAGE_SCN_CNT_CODE) != 0) { + // + // Instead of assuming that a PE/COFF section of type EFI_IMAGE_SCN_CNT_CODE + // can always be mapped read-only, classify a section as a code section only + // if it has the executable attribute set and the writable attribute cleared. + // + // This adheres more closely to the PE/COFF spec, and avoids issues with + // Linux OS loaders that may consist of a single read/write/execute section. + // + if ((Section[Index].Characteristics & (EFI_IMAGE_SCN_MEM_WRITE | EFI_IMAGE_SCN_MEM_EXECUTE)) == EFI_IMAGE_SCN_MEM_EXECUTE) { DEBUG ((DEBUG_VERBOSE, " VirtualSize - 0x%08x\n", Section[Index].Misc.VirtualSize)); DEBUG ((DEBUG_VERBOSE, " VirtualAddress - 0x%08x\n", Section[Index].VirtualAddress)); DEBUG ((DEBUG_VERBOSE, " SizeOfRawData - 0x%08x\n", Section[Index].SizeOfRawData)); @@ -638,6 +654,253 @@ UnprotectUefiImage ( } } +/** + Return the EFI memory permission attribute associated with memory + type 'MemoryType' under the configured DXE memory protection policy. + + @param MemoryType Memory type. +**/ +STATIC +UINT64 +GetPermissionAttributeForMemoryType ( + IN EFI_MEMORY_TYPE MemoryType + ) +{ + UINT64 TestBit; + + if ((UINT32)MemoryType >= MEMORY_TYPE_OS_RESERVED_MIN) { + TestBit = BIT63; + } else if ((UINT32)MemoryType >= MEMORY_TYPE_OEM_RESERVED_MIN) { + TestBit = BIT62; + } else { + TestBit = LShiftU64 (1, MemoryType); + } + + if ((PcdGet64 (PcdDxeNxMemoryProtectionPolicy) & TestBit) != 0) { + return EFI_MEMORY_XP; + } else { + return 0; + } +} + +/** + 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); + } +} + +/** + Merge adjacent memory map entries if they use the same memory protection policy + + @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 +MergeMemoryMapForProtectionPolicy ( + 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; + UINT64 Attributes; + + SortMemoryMap (MemoryMap, *MemoryMapSize, DescriptorSize); + + 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)); + Attributes = GetPermissionAttributeForMemoryType (MemoryMapEntry->Type); + + if (((UINTN)NextMemoryMapEntry < (UINTN)MemoryMapEnd) && + Attributes == GetPermissionAttributeForMemoryType (NextMemoryMapEntry->Type) && + ((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 ; +} + + +/** + Remove exec permissions from all regions whose type is identified by + PcdDxeNxMemoryProtectionPolicy. +**/ +STATIC +VOID +InitializeDxeNxMemoryProtectionPolicy ( + VOID + ) +{ + UINTN MemoryMapSize; + UINTN MapKey; + UINTN DescriptorSize; + UINT32 DescriptorVersion; + EFI_MEMORY_DESCRIPTOR *MemoryMap; + EFI_MEMORY_DESCRIPTOR *MemoryMapEntry; + EFI_MEMORY_DESCRIPTOR *MemoryMapEnd; + EFI_STATUS Status; + UINT64 Attributes; + LIST_ENTRY *Link; + EFI_GCD_MAP_ENTRY *Entry; + + // + // Get the EFI memory map. + // + MemoryMapSize = 0; + MemoryMap = NULL; + + Status = gBS->GetMemoryMap ( + &MemoryMapSize, + MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion + ); + ASSERT (Status == EFI_BUFFER_TOO_SMALL); + do { + MemoryMap = (EFI_MEMORY_DESCRIPTOR *) AllocatePool (MemoryMapSize); + ASSERT (MemoryMap != NULL); + Status = gBS->GetMemoryMap ( + &MemoryMapSize, + MemoryMap, + &MapKey, + &DescriptorSize, + &DescriptorVersion + ); + if (EFI_ERROR (Status)) { + FreePool (MemoryMap); + } + } while (Status == EFI_BUFFER_TOO_SMALL); + ASSERT_EFI_ERROR (Status); + + DEBUG((DEBUG_ERROR, "%a: applying strict permissions to active memory regions\n", + __FUNCTION__)); + + MergeMemoryMapForProtectionPolicy (MemoryMap, &MemoryMapSize, DescriptorSize); + + MemoryMapEntry = MemoryMap; + MemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) MemoryMap + MemoryMapSize); + while ((UINTN) MemoryMapEntry < (UINTN) MemoryMapEnd) { + + Attributes = GetPermissionAttributeForMemoryType (MemoryMapEntry->Type); + if (Attributes != 0) { + SetUefiImageMemoryAttributes ( + MemoryMapEntry->PhysicalStart, + LShiftU64 (MemoryMapEntry->NumberOfPages, EFI_PAGE_SHIFT), + Attributes); + } + MemoryMapEntry = NEXT_MEMORY_DESCRIPTOR (MemoryMapEntry, DescriptorSize); + } + FreePool (MemoryMap); + + // + // Apply the policy for RAM regions that we know are present and + // accessible, but have not been added to the UEFI memory map (yet). + // + if (GetPermissionAttributeForMemoryType (EfiConventionalMemory) != 0) { + DEBUG((DEBUG_ERROR, + "%a: applying strict permissions to inactive memory regions\n", + __FUNCTION__)); + + CoreAcquireGcdMemoryLock (); + + Link = mGcdMemorySpaceMap.ForwardLink; + while (Link != &mGcdMemorySpaceMap) { + + Entry = CR (Link, EFI_GCD_MAP_ENTRY, Link, EFI_GCD_MAP_SIGNATURE); + + if (Entry->GcdMemoryType == EfiGcdMemoryTypeReserved && + Entry->EndAddress < MAX_ADDRESS && + (Entry->Capabilities & (EFI_MEMORY_PRESENT | EFI_MEMORY_INITIALIZED | EFI_MEMORY_TESTED)) == + (EFI_MEMORY_PRESENT | EFI_MEMORY_INITIALIZED)) { + + Attributes = GetPermissionAttributeForMemoryType (EfiConventionalMemory) | + (Entry->Attributes & CACHE_ATTRIBUTE_MASK); + + DEBUG ((DEBUG_INFO, + "Untested GCD memory space region: - 0x%016lx - 0x%016lx (0x%016lx)\n", + Entry->BaseAddress, Entry->EndAddress - Entry->BaseAddress + 1, + Attributes)); + + ASSERT(gCpu != NULL); + gCpu->SetMemoryAttributes (gCpu, Entry->BaseAddress, + Entry->EndAddress - Entry->BaseAddress + 1, Attributes); + } + + Link = Link->ForwardLink; + } + CoreReleaseGcdMemoryLock (); + } +} + + /** A notification for CPU_ARCH protocol. @@ -666,6 +929,17 @@ MemoryProtectionCpuArchProtocolNotify ( return; } + // + // Apply the memory protection policy on non-BScode/RTcode regions. + // + if (PcdGet64 (PcdDxeNxMemoryProtectionPolicy) != 0) { + InitializeDxeNxMemoryProtectionPolicy (); + } + + if (mImageProtectionPolicy == 0) { + return; + } + Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiLoadedImageProtocolGuid, @@ -745,7 +1019,19 @@ CoreInitializeMemoryProtection ( mImageProtectionPolicy = PcdGet32(PcdImageProtectionPolicy); - if (mImageProtectionPolicy != 0) { + // + // Sanity check the PcdDxeNxMemoryProtectionPolicy setting: + // - code regions should have no EFI_MEMORY_XP attribute + // - EfiConventionalMemory and EfiBootServicesData should use the + // same attribute + // + ASSERT ((GetPermissionAttributeForMemoryType (EfiBootServicesCode) & EFI_MEMORY_XP) == 0); + ASSERT ((GetPermissionAttributeForMemoryType (EfiRuntimeServicesCode) & EFI_MEMORY_XP) == 0); + ASSERT ((GetPermissionAttributeForMemoryType (EfiLoaderCode) & EFI_MEMORY_XP) == 0); + ASSERT (GetPermissionAttributeForMemoryType (EfiBootServicesData) == + GetPermissionAttributeForMemoryType (EfiConventionalMemory)); + + if (mImageProtectionPolicy != 0 || PcdGet64 (PcdDxeNxMemoryProtectionPolicy) != 0) { Status = CoreCreateEvent ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, @@ -767,3 +1053,96 @@ CoreInitializeMemoryProtection ( } return ; } + +/** + Returns whether we are currently executing in SMM mode. +**/ +STATIC +BOOLEAN +IsInSmm ( + VOID + ) +{ + BOOLEAN InSmm; + + InSmm = FALSE; + if (gSmmBase2 != NULL) { + gSmmBase2->InSmm (gSmmBase2, &InSmm); + } + return InSmm; +} + +/** + Manage memory permission attributes on a memory range, according to the + configured DXE memory protection policy. + + @param OldType The old memory type of the range + @param NewType The new memory type of the range + @param Memory The base address of the range + @param Length The size of the range (in bytes) + + @return EFI_SUCCESS If we are executing in SMM mode. No permission attributes + are updated in this case + @return EFI_SUCCESS If the the CPU arch protocol is not installed yet + @return EFI_SUCCESS If no DXE memory protection policy has been configured + @return EFI_SUCCESS If OldType and NewType use the same permission attributes + @return other Return value of gCpu->SetMemoryAttributes() + +**/ +EFI_STATUS +EFIAPI +ApplyMemoryProtectionPolicy ( + IN EFI_MEMORY_TYPE OldType, + IN EFI_MEMORY_TYPE NewType, + IN EFI_PHYSICAL_ADDRESS Memory, + IN UINT64 Length + ) +{ + UINT64 OldAttributes; + UINT64 NewAttributes; + + // + // The policy configured in PcdDxeNxMemoryProtectionPolicy + // does not apply to allocations performed in SMM mode. + // + if (IsInSmm ()) { + return EFI_SUCCESS; + } + + // + // If the CPU arch protocol is not installed yet, we cannot manage memory + // permission attributes, and it is the job of the driver that installs this + // protocol to set the permissions on existing allocations. + // + if (gCpu == NULL) { + return EFI_SUCCESS; + } + + // + // Check if a DXE memory protection policy has been configured + // + if (PcdGet64 (PcdDxeNxMemoryProtectionPolicy) == 0) { + return EFI_SUCCESS; + } + + // + // Update the executable permissions according to the DXE memory + // protection policy, but only if + // - the policy is different between the old and the new type, or + // - this is a newly added region (OldType == EfiMaxMemoryType) + // + NewAttributes = GetPermissionAttributeForMemoryType (NewType); + + if (OldType != EfiMaxMemoryType) { + OldAttributes = GetPermissionAttributeForMemoryType (OldType); + if (OldAttributes == NewAttributes) { + // policy is the same between OldType and NewType + return EFI_SUCCESS; + } + } else if (NewAttributes == 0) { + // newly added region of a type that does not require protection + return EFI_SUCCESS; + } + + return gCpu->SetMemoryAttributes (gCpu, Memory, Length, NewAttributes); +}