Skip to content

Commit 5ccb5ff

Browse files
os-dmergify[bot]
authored andcommitted
MdeModulePkg: DxeCore: Set Image Protections Through GCD
Today, SetUefiImageMemoryAttributes calls directly to the CPU Arch protocol to set EFI_MEMORY_XP or EFI_MEMORY_RO on image memory. However, this bypasses the GCD and so the GCD is out of sync with the actual state of memory. This can cause an issue in the scenario where a new attribute is being set (whether a virtual attribute or a real HW attribute), if the GCD attributes are queried for a region and the new attribute is appended to the existing GCD attributes (which are incorrect), then the incorrect attributes can get applied. This can result in setting EFI_MEMORY_XP on code sections of images and causing an execution fault. This patch updates SetUefiImageMemoryAttributes to call into the GCD to update the attributes there and let the GCD code call into the CPU Arch protocol to update the page table. Signed-off-by: Oliver Smith-Denny <[email protected]>
1 parent 6c6d6f4 commit 5ccb5ff

File tree

2 files changed

+112
-7
lines changed

2 files changed

+112
-7
lines changed

MdeModulePkg/Core/Dxe/Gcd/Gcd.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -976,7 +976,7 @@ CoreConvertSpace (
976976
// Set attributes operation
977977
//
978978
case GCD_SET_ATTRIBUTES_MEMORY_OPERATION:
979-
if (CpuArchAttributes == 0) {
979+
if ((CpuArchAttributes == 0) && (Attributes != 0)) {
980980
//
981981
// Keep original CPU arch attributes when caller just calls
982982
// SetMemorySpaceAttributes() with none CPU arch attributes (for example, RUNTIME).

MdeModulePkg/Core/Dxe/Misc/MemoryProtection.c

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -194,16 +194,108 @@ SetUefiImageMemoryAttributes (
194194
EFI_STATUS Status;
195195
EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor;
196196
UINT64 FinalAttributes;
197+
UINT64 CurrentAddress;
198+
UINT64 CurrentLength;
197199

198-
Status = CoreGetMemorySpaceDescriptor (BaseAddress, &Descriptor);
199-
ASSERT_EFI_ERROR (Status);
200+
CurrentAddress = BaseAddress;
201+
202+
// we loop here because we may have multiple memory space descriptors that overlap the requested range
203+
// this will definitely be the case for unprotecting an image, because that calls this function for the entire image,
204+
// which we split into different GCD descriptors when we protected it.
205+
while (CurrentAddress < BaseAddress + Length) {
206+
Status = CoreGetMemorySpaceDescriptor (CurrentAddress, &Descriptor);
207+
if (EFI_ERROR (Status)) {
208+
DEBUG ((
209+
DEBUG_ERROR,
210+
"%a - Failed to get memory space descriptor for address %llx with status %r. Cannot protect image.\n",
211+
__func__,
212+
CurrentAddress,
213+
Status
214+
));
215+
ASSERT_EFI_ERROR (Status);
216+
return;
217+
}
218+
219+
// ensure that we only change the attributes for the range that we are interested in, not the entire descriptor
220+
if (BaseAddress + Length > CurrentAddress + Descriptor.Length) {
221+
CurrentLength = Descriptor.Length;
222+
} else {
223+
CurrentLength = BaseAddress + Length - CurrentAddress;
224+
}
225+
226+
// Preserve the existing caching and virtual attributes, but remove the hardware access bits
227+
FinalAttributes = (Descriptor.Attributes & ~EFI_MEMORY_ACCESS_MASK) | (Attributes & EFI_MEMORY_ATTRIBUTE_MASK);
200228

201-
FinalAttributes = (Descriptor.Attributes & EFI_CACHE_ATTRIBUTE_MASK) | (Attributes & EFI_MEMORY_ATTRIBUTE_MASK);
229+
DEBUG ((DEBUG_VERBOSE, "SetUefiImageMemoryAttributes - 0x%016lx - 0x%016lx (0x%016lx)\n", CurrentAddress, CurrentLength, FinalAttributes));
202230

203-
DEBUG ((DEBUG_VERBOSE, "SetUefiImageMemoryAttributes - 0x%016lx - 0x%016lx (0x%016lx)\n", BaseAddress, Length, FinalAttributes));
231+
// check to see if the capabilities support the attributes we want to set. If not, set the capabilities appropriately
232+
if ((Descriptor.Capabilities & FinalAttributes) != FinalAttributes) {
233+
Status = CoreSetMemorySpaceCapabilities (
234+
CurrentAddress,
235+
CurrentLength,
236+
Descriptor.Capabilities | FinalAttributes
237+
);
204238

205-
ASSERT (gCpu != NULL);
206-
gCpu->SetMemoryAttributes (gCpu, BaseAddress, Length, FinalAttributes);
239+
// if we failed to set the capabilities, we should try to continue, it is possible we could succeed
240+
if (EFI_ERROR (Status)) {
241+
DEBUG ((
242+
DEBUG_ERROR,
243+
"%a failed setting capabilities on %llx of length %llx with capabilities %llx - %r\n",
244+
__func__,
245+
CurrentAddress,
246+
CurrentLength,
247+
Descriptor.Capabilities | FinalAttributes,
248+
Status
249+
));
250+
ASSERT_EFI_ERROR (Status);
251+
}
252+
}
253+
254+
// Call into the GCD to update the attributes there. It will call into the CPU Arch protocol to update the
255+
// page table attributes
256+
Status = CoreSetMemorySpaceAttributes (
257+
CurrentAddress,
258+
CurrentLength,
259+
FinalAttributes
260+
);
261+
262+
if (EFI_ERROR (Status)) {
263+
DEBUG ((
264+
DEBUG_ERROR,
265+
"%a failed on %llx of length %llx with attributes %llx - %r\n",
266+
__func__,
267+
CurrentAddress,
268+
CurrentLength,
269+
FinalAttributes,
270+
Status
271+
));
272+
ASSERT_EFI_ERROR (Status);
273+
}
274+
275+
if (((FinalAttributes & (EFI_MEMORY_ATTRIBUTE_MASK | EFI_CACHE_ATTRIBUTE_MASK)) == 0) && (gCpu != NULL)) {
276+
// if the passed hardware attributes are 0, CoreSetMemorySpaceAttributes() will not call into the CPU Arch protocol
277+
// to set the attributes, so we need to do it manually here. This can be the case when we are unprotecting an
278+
// image if no caching attributes are set. If gCpu has not been populated yet, we'll still have updated the GCD
279+
// descriptor and we should sync the attributes with the CPU Arch protocol when it is available.
280+
Status = gCpu->SetMemoryAttributes (gCpu, CurrentAddress, CurrentLength, 0);
281+
if (EFI_ERROR (Status)) {
282+
DEBUG ((
283+
DEBUG_ERROR,
284+
"%a failed to update page table for %llx of length %llx with attributes 0 - %r\n",
285+
__func__,
286+
CurrentAddress,
287+
CurrentLength,
288+
Status
289+
));
290+
ASSERT_EFI_ERROR (Status);
291+
}
292+
}
293+
294+
// we have CurrentLength, also, but that is just to handle the final descriptor case where we might take only
295+
// part of a descriptor, so we can use Descriptor.Length here to move to the next descriptor, which for the final
296+
// descriptor will exit the loop, regardless of whether we truncated or not
297+
CurrentAddress += Descriptor.Length;
298+
}
207299
}
208300

209301
/**
@@ -371,6 +463,19 @@ ProtectUefiImage (
371463

372464
if (EFI_ERROR (Status)) {
373465
DEBUG ((DEBUG_ERROR, "%a failed to create image properties record\n", __func__));
466+
467+
// if we failed to create the image properties record, this may mean that the image is not aligned properly
468+
// the GCD will believe that this memory is non-executable, because the NX initialization routine doesn't know what
469+
// memory is image memory or not, even though the page table has the correct attributes, so we need to set the
470+
// attributes here to RWX so that future updates to the GCD do not apply the NX attributes to this memory in the
471+
// page table (as can happen when applying virtual attributes). This may have the side effect of marking other
472+
// memory as RWX, since this image may not be page aligned, but that is safe to do, it may just remove some
473+
// page protections, but it already has to to execute this image.
474+
SetUefiImageMemoryAttributes (
475+
(UINT64)(UINTN)LoadedImage->ImageBase & ~EFI_PAGE_MASK,
476+
(LoadedImage->ImageSize + EFI_PAGE_MASK) & ~EFI_PAGE_MASK,
477+
0
478+
);
374479
FreePool (ImageRecord);
375480
goto Finish;
376481
}

0 commit comments

Comments
 (0)