@@ -194,16 +194,108 @@ SetUefiImageMemoryAttributes (
194
194
EFI_STATUS Status ;
195
195
EFI_GCD_MEMORY_SPACE_DESCRIPTOR Descriptor ;
196
196
UINT64 FinalAttributes ;
197
+ UINT64 CurrentAddress ;
198
+ UINT64 CurrentLength ;
197
199
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 );
200
228
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 ) );
202
230
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
+ );
204
238
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
+ }
207
299
}
208
300
209
301
/**
@@ -371,6 +463,19 @@ ProtectUefiImage (
371
463
372
464
if (EFI_ERROR (Status )) {
373
465
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
+ );
374
479
FreePool (ImageRecord );
375
480
goto Finish ;
376
481
}
0 commit comments