DllBlocklist.cpp (23175B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/Attributes.h" 8 #include "mozilla/NativeNt.h" 9 #include "mozilla/Types.h" 10 #include "mozilla/WindowsDllBlocklist.h" 11 12 #include "DllBlocklist.h" 13 #include "LoaderPrivateAPI.h" 14 #include "ModuleLoadFrame.h" 15 #include "SharedSection.h" 16 17 using mozilla::DllBlockInfoFlags; 18 19 #define DLL_BLOCKLIST_ENTRY(name, ...) \ 20 {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__}, 21 #define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING 22 23 #if defined(MOZ_LAUNCHER_PROCESS) || defined(NIGHTLY_BUILD) 24 # include "mozilla/WindowsDllBlocklistLauncherDefs.h" 25 #else 26 # include "mozilla/WindowsDllBlocklistCommon.h" 27 DLL_BLOCKLIST_DEFINITIONS_BEGIN 28 DLL_BLOCKLIST_DEFINITIONS_END 29 #endif 30 31 using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>; 32 33 class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final { 34 struct NativeNtBlockSetEntry { 35 NativeNtBlockSetEntry() = default; 36 ~NativeNtBlockSetEntry() = default; 37 NativeNtBlockSetEntry(const UNICODE_STRING& aName, uint64_t aVersion, 38 NativeNtBlockSetEntry* aNext) 39 : mName(aName), mVersion(aVersion), mNext(aNext) {} 40 UNICODE_STRING mName; 41 uint64_t mVersion; 42 NativeNtBlockSetEntry* mNext; 43 }; 44 45 public: 46 // Constructor and destructor MUST be trivial 47 constexpr NativeNtBlockSet() : mFirstEntry(nullptr) {} 48 ~NativeNtBlockSet() = default; 49 50 void Add(const UNICODE_STRING& aName, uint64_t aVersion); 51 void Write(WritableBuffer& buffer); 52 53 private: 54 static NativeNtBlockSetEntry* NewEntry(const UNICODE_STRING& aName, 55 uint64_t aVersion, 56 NativeNtBlockSetEntry* aNextEntry); 57 58 private: 59 NativeNtBlockSetEntry* mFirstEntry; 60 mozilla::nt::SRWLock mLock; 61 }; 62 63 NativeNtBlockSet::NativeNtBlockSetEntry* NativeNtBlockSet::NewEntry( 64 const UNICODE_STRING& aName, uint64_t aVersion, 65 NativeNtBlockSet::NativeNtBlockSetEntry* aNextEntry) { 66 return mozilla::freestanding::RtlNew<NativeNtBlockSetEntry>(aName, aVersion, 67 aNextEntry); 68 } 69 70 void NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion) { 71 mozilla::nt::AutoExclusiveLock lock(mLock); 72 73 for (NativeNtBlockSetEntry* entry = mFirstEntry; entry; 74 entry = entry->mNext) { 75 if (::RtlEqualUnicodeString(&entry->mName, &aName, TRUE) && 76 aVersion == entry->mVersion) { 77 return; 78 } 79 } 80 81 // Not present, add it 82 NativeNtBlockSetEntry* newEntry = NewEntry(aName, aVersion, mFirstEntry); 83 if (newEntry) { 84 mFirstEntry = newEntry; 85 } 86 } 87 88 void NativeNtBlockSet::Write(WritableBuffer& aBuffer) { 89 // NB: If this function is called, it is long after kernel32 is initialized, 90 // so it is safe to use Win32 calls here. 91 char buf[MAX_PATH]; 92 93 // It would be nicer to use RAII here. However, its destructor 94 // might not run if an exception occurs, in which case we would never release 95 // the lock (MSVC warns about this possibility). So we acquire and release 96 // manually. 97 ::AcquireSRWLockExclusive(&mLock); 98 99 MOZ_SEH_TRY { 100 for (auto entry = mFirstEntry; entry; entry = entry->mNext) { 101 int convOk = ::WideCharToMultiByte(CP_UTF8, 0, entry->mName.Buffer, 102 entry->mName.Length / sizeof(wchar_t), 103 buf, sizeof(buf), nullptr, nullptr); 104 if (!convOk) { 105 continue; 106 } 107 108 // write name[,v.v.v.v]; 109 aBuffer.Write(buf, convOk); 110 111 if (entry->mVersion != DllBlockInfo::ALL_VERSIONS) { 112 aBuffer.Write(",", 1); 113 uint16_t parts[4]; 114 parts[0] = entry->mVersion >> 48; 115 parts[1] = (entry->mVersion >> 32) & 0xFFFF; 116 parts[2] = (entry->mVersion >> 16) & 0xFFFF; 117 parts[3] = entry->mVersion & 0xFFFF; 118 for (size_t p = 0; p < std::size(parts); ++p) { 119 _ltoa_s(parts[p], buf, sizeof(buf), 10); 120 aBuffer.Write(buf, strlen(buf)); 121 if (p != std::size(parts) - 1) { 122 aBuffer.Write(".", 1); 123 } 124 } 125 } 126 aBuffer.Write(";", 1); 127 } 128 } 129 MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {} 130 131 ::ReleaseSRWLockExclusive(&mLock); 132 } 133 134 static NativeNtBlockSet gBlockSet; 135 136 extern "C" void MOZ_EXPORT NativeNtBlockSet_Write(WritableBuffer& aBuffer) { 137 gBlockSet.Write(aBuffer); 138 } 139 140 enum class BlockAction { 141 // Allow the DLL to be loaded. 142 Allow, 143 // Substitute in a different DLL to be loaded instead of this one? 144 // This is intended to be used for Layered Service Providers, which 145 // cannot be blocked in the normal way. Note that this doesn't seem 146 // to be actually implemented right now, and no entries in the blocklist 147 // use it. 148 SubstituteLSP, 149 // There was an error in determining whether we should block this DLL. 150 // It will be blocked. 151 Error, 152 // Block the DLL from loading. 153 Deny, 154 // Effectively block the DLL from loading by redirecting its DllMain 155 // to a stub version. This is needed for DLLs that add themselves to 156 // the executable's Import Table, since failing to load would mean the 157 // executable would fail to launch. 158 NoOpEntryPoint, 159 }; 160 161 static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo, 162 const mozilla::nt::PEHeaders& aHeaders, 163 uint64_t& aVersion) { 164 aVersion = DllBlockInfo::ALL_VERSIONS; 165 166 if ((aInfo->mFlags & DllBlockInfoFlags::CHILD_PROCESSES_ONLY) && 167 !(gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) { 168 return BlockAction::Allow; 169 } 170 171 if ((aInfo->mFlags & DllBlockInfoFlags::UTILITY_PROCESSES_ONLY) && 172 !(gBlocklistInitFlags & eDllBlocklistInitFlagIsUtilityProcess)) { 173 return BlockAction::Allow; 174 } 175 176 if ((aInfo->mFlags & DllBlockInfoFlags::SOCKET_PROCESSES_ONLY) && 177 !(gBlocklistInitFlags & eDllBlocklistInitFlagIsSocketProcess)) { 178 return BlockAction::Allow; 179 } 180 181 if ((aInfo->mFlags & DllBlockInfoFlags::GPU_PROCESSES_ONLY) && 182 !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGPUProcess)) { 183 return BlockAction::Allow; 184 } 185 186 if ((aInfo->mFlags & DllBlockInfoFlags::BROWSER_PROCESS_ONLY) && 187 (gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) { 188 return BlockAction::Allow; 189 } 190 191 if ((aInfo->mFlags & DllBlockInfoFlags::GMPLUGIN_PROCESSES_ONLY) && 192 !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGMPluginProcess)) { 193 return BlockAction::Allow; 194 } 195 196 if ((aInfo->mFlags & DllBlockInfoFlags::RDD_PROCESSES_ONLY) && 197 !(gBlocklistInitFlags & eDllBlocklistInitFlagIsRDDProcess)) { 198 return BlockAction::Allow; 199 } 200 201 if (aInfo->mMaxVersion == DllBlockInfo::ALL_VERSIONS) { 202 return BlockAction::Deny; 203 } 204 205 if (!aHeaders) { 206 return BlockAction::Error; 207 } 208 209 if (aInfo->mFlags & DllBlockInfoFlags::USE_TIMESTAMP) { 210 DWORD timestamp; 211 if (!aHeaders.GetTimeStamp(timestamp)) { 212 return BlockAction::Error; 213 } 214 215 if (timestamp > aInfo->mMaxVersion) { 216 return BlockAction::Allow; 217 } 218 219 return BlockAction::Deny; 220 } 221 222 // Else we try to get the file version information. Note that we don't have 223 // access to GetFileVersionInfo* APIs. 224 if (!aHeaders.GetVersionInfo(aVersion)) { 225 return BlockAction::Error; 226 } 227 228 if (aInfo->IsVersionBlocked(aVersion)) { 229 return BlockAction::Deny; 230 } 231 232 return BlockAction::Allow; 233 } 234 235 static BOOL WINAPI NoOp_DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; } 236 237 // This helper function checks whether a given module is included 238 // in the executable's Import Table. Because an injected module's 239 // DllMain may revert the Import Table to the original state, we parse 240 // the Import Table every time a module is loaded without creating a cache. 241 static bool IsInjectedDependentModule( 242 const UNICODE_STRING& aModuleLeafName, 243 mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) { 244 mozilla::nt::PEHeaders exeHeaders(aK32Exports.mGetModuleHandleW(nullptr)); 245 if (!exeHeaders || !exeHeaders.IsImportDirectoryTampered()) { 246 // If no tampering is detected, no need to enumerate the Import Table. 247 return false; 248 } 249 250 bool isDependent = false; 251 exeHeaders.EnumImportChunks( 252 [&isDependent, &aModuleLeafName, &exeHeaders](const char* aDepModule) { 253 // If |aDepModule| is within the PE image, it's not an injected module 254 // but a legitimate dependent module. 255 if (isDependent || exeHeaders.IsWithinImage(aDepModule)) { 256 return; 257 } 258 259 UNICODE_STRING depModuleLeafName; 260 mozilla::nt::AllocatedUnicodeString depModuleName(aDepModule); 261 mozilla::nt::GetLeafName(&depModuleLeafName, depModuleName); 262 isDependent = (::RtlCompareUnicodeString( 263 &aModuleLeafName, &depModuleLeafName, TRUE) == 0); 264 }); 265 return isDependent; 266 } 267 268 // Allowing a module to be loaded but detour the entrypoint to NoOp_DllMain 269 // so that the module has no chance to interact with our code. We need this 270 // technique to safely block a module injected by IAT tampering because 271 // blocking such a module makes a process fail to launch. 272 static bool RedirectToNoOpEntryPoint( 273 const mozilla::nt::PEHeaders& aModule, 274 mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) { 275 mozilla::interceptor::WindowsDllEntryPointInterceptor interceptor( 276 aK32Exports); 277 if (!interceptor.Set(aModule, NoOp_DllMain)) { 278 return false; 279 } 280 281 return true; 282 } 283 284 static BlockAction DetermineBlockAction( 285 const UNICODE_STRING& aLeafName, void* aBaseAddress, 286 mozilla::freestanding::Kernel32ExportsSolver* aK32Exports) { 287 if (mozilla::nt::Contains12DigitHexString(aLeafName) || 288 mozilla::nt::IsFileNameAtLeast16HexDigits(aLeafName)) { 289 return BlockAction::Deny; 290 } 291 292 mozilla::nt::PEHeaders headers(aBaseAddress); 293 DWORD checksum = 0; 294 DWORD timestamp = 0; 295 DWORD imageSize = 0; 296 uint64_t version = 0; 297 298 // Block some malicious DLLs known for crashing our process (bug 1841751), 299 // based on matching the combination of version number + timestamp + image 300 // size. We further reduce the chances of collision with legit DLLs by 301 // checking for a checksum of 0 and the absence of debug information, both of 302 // which are unusual for production-ready DLLs. 303 if (headers.GetCheckSum(checksum) && checksum == 0 && !headers.GetPdbInfo() && 304 headers.GetTimeStamp(timestamp)) { 305 struct KnownMaliciousCombination { 306 uint64_t mVersion; 307 uint32_t mTimestamp; 308 uint32_t mImageSize; 309 }; 310 const KnownMaliciousCombination instances[]{ 311 // 1.0.0.26638 312 {0x000100000000680e, 0x570B8A90, 0x62000}, 313 // 1.0.0.26793 314 {0x00010000000068a9, 0x572B4CE4, 0x62000}, 315 // 1.0.0.27567 316 {0x0001000000006baf, 0x57A725AC, 0x61000}, 317 // 1.0.0.29915 318 {0x00010000000074db, 0x5A115D81, 0x5D000}, 319 // 1.0.0.31122 320 {0x0001000000007992, 0x5CFF88B8, 0x5D000}}; 321 322 // We iterate over timestamps, because they are unique and it is a quick 323 // field to fetch 324 for (const auto& instance : instances) { 325 if (instance.mTimestamp == timestamp) { 326 // Only fetch other fields in case we have a match. Then, we can exit 327 // the loop. 328 if (headers.GetImageSize(imageSize) && 329 instance.mImageSize == imageSize && 330 headers.GetVersionInfo(version) && instance.mVersion == version) { 331 return BlockAction::Deny; 332 } 333 break; 334 } 335 } 336 } 337 338 DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info); 339 DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(infoNumEntries); 340 341 mozilla::freestanding::DllBlockInfoComparator comp(aLeafName); 342 343 size_t match = LowerBound(info, 0, infoNumEntries, comp); 344 bool builtinListHasLowerBound = match != infoNumEntries; 345 const DllBlockInfo* entry = nullptr; 346 BlockAction checkResult = BlockAction::Allow; 347 if (builtinListHasLowerBound) { 348 // There may be multiple entries on the list. Since LowerBound() returns 349 // the first entry that matches (if there are any matches), 350 // search forward from there. 351 while (match < infoNumEntries && (comp(info[match]) == 0)) { 352 entry = &info[match]; 353 checkResult = CheckBlockInfo(entry, headers, version); 354 if (checkResult != BlockAction::Allow) { 355 break; 356 } 357 ++match; 358 } 359 } 360 mozilla::DebugOnly<bool> blockedByDynamicBlocklist = false; 361 // Make sure we handle a case that older versions are blocked by the static 362 // list, but the dynamic list blocks all versions. 363 if (checkResult == BlockAction::Allow) { 364 if (!mozilla::freestanding::gSharedSection.IsDisabled()) { 365 entry = mozilla::freestanding::gSharedSection.SearchBlocklist(aLeafName); 366 if (entry) { 367 checkResult = CheckBlockInfo(entry, headers, version); 368 blockedByDynamicBlocklist = checkResult != BlockAction::Allow; 369 } 370 } 371 } 372 if (checkResult == BlockAction::Allow) { 373 return BlockAction::Allow; 374 } 375 376 gBlockSet.Add(entry->mName, version); 377 378 if ((entry->mFlags & DllBlockInfoFlags::REDIRECT_TO_NOOP_ENTRYPOINT) && 379 aK32Exports && RedirectToNoOpEntryPoint(headers, *aK32Exports)) { 380 MOZ_ASSERT(!blockedByDynamicBlocklist, "dynamic blocklist has redirect?"); 381 return BlockAction::NoOpEntryPoint; 382 } 383 384 return checkResult; 385 } 386 387 namespace mozilla { 388 namespace freestanding { 389 390 CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll; 391 392 NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags, 393 PUNICODE_STRING aDllName, 394 PHANDLE aOutHandle) { 395 ModuleLoadFrame frame(aDllName); 396 397 NTSTATUS ntStatus = stub_LdrLoadDll(aDllPath, aFlags, aDllName, aOutHandle); 398 399 return frame.SetLoadStatus(ntStatus, aOutHandle); 400 } 401 402 CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr> 403 stub_NtMapViewOfSection; 404 405 // All the code for patched_NtMapViewOfSection that relies on checked stack 406 // buffers (e.g. mbi, sectionFileName) should be put in this helper function 407 // (see bug 1733532). 408 MOZ_NEVER_INLINE NTSTATUS AfterMapViewOfExecutableSection( 409 HANDLE aProcess, PVOID* aBaseAddress, NTSTATUS aStubStatus) { 410 // We don't care about mappings that aren't MEM_IMAGE. 411 MEMORY_BASIC_INFORMATION mbi; 412 NTSTATUS ntStatus = 413 ::NtQueryVirtualMemory(aProcess, *aBaseAddress, MemoryBasicInformation, 414 &mbi, sizeof(mbi), nullptr); 415 if (!NT_SUCCESS(ntStatus)) { 416 ::NtUnmapViewOfSection(aProcess, *aBaseAddress); 417 return STATUS_ACCESS_DENIED; 418 } 419 if (!(mbi.Type & MEM_IMAGE)) { 420 return aStubStatus; 421 } 422 423 // Get the section name 424 nt::MemorySectionNameBuf sectionFileName( 425 gLoaderPrivateAPI.GetSectionNameBuffer(*aBaseAddress)); 426 if (sectionFileName.IsEmpty()) { 427 ::NtUnmapViewOfSection(aProcess, *aBaseAddress); 428 return STATUS_ACCESS_DENIED; 429 } 430 431 // Find the leaf name 432 UNICODE_STRING leafOnStack; 433 nt::GetLeafName(&leafOnStack, sectionFileName); 434 435 bool isInjectedDependent = false; 436 const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll"); 437 Kernel32ExportsSolver* k32Exports = nullptr; 438 BlockAction blockAction; 439 // Trying to get the Kernel32Exports while loading kernel32.dll causes Firefox 440 // to crash. (but only during a profile-guided optimization run, oddly) We 441 // know we're never going to block kernel32.dll, so skip all this 442 if (::RtlCompareUnicodeString(&k32Name, &leafOnStack, TRUE) == 0) { 443 blockAction = BlockAction::Allow; 444 } else { 445 auto noSharedSectionReset{SharedSection::AutoNoReset()}; 446 k32Exports = gSharedSection.GetKernel32Exports(); 447 // Small optimization: Since loading a dependent module does not involve 448 // LdrLoadDll, we know isInjectedDependent is false if we hold a top frame. 449 if (k32Exports && !ModuleLoadFrame::ExistsTopFrame()) { 450 // Note that if a module is dependent but not injected, this means that 451 // the executable built against it, and it should be signed by Mozilla 452 // or Microsoft, so we don't need to worry about adding it to the list 453 // for CIG. (and users won't be able to block it) So the only special 454 // case here is a dependent module that has been injected. 455 isInjectedDependent = IsInjectedDependentModule(leafOnStack, *k32Exports); 456 } 457 458 if (isInjectedDependent) { 459 // Add an NT dv\path to the shared section so that a sandbox process can 460 // use it to bypass CIG. In a sandbox process, this addition fails 461 // because we cannot map the section to a writable region, but it's 462 // ignorable because the paths have been added by the browser process. 463 (void)SharedSection::AddDependentModule(sectionFileName); 464 465 bool attemptToBlockViaRedirect; 466 #if defined(NIGHTLY_BUILD) 467 // We enable automatic DLL blocking only in Nightly for now 468 // because it caused a compat issue (bug 1682304 and 1704373). 469 attemptToBlockViaRedirect = true; 470 // We will set blockAction below in the if (attemptToBlockViaRedirect) 471 // block, but I guess the compiler isn't smart enough to figure 472 // that out and complains about an uninitialized variable :-( 473 blockAction = BlockAction::NoOpEntryPoint; 474 #else 475 // Check blocklist 476 blockAction = 477 DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports); 478 // If we were going to block this dependent module, try redirection 479 // instead of blocking it, since blocking it would cause the .exe not to 480 // launch. 481 // Note tht Deny and Error both end up blocking the module in a 482 // straightforward way, so those are the cases in which we need 483 // to redirect instead. 484 attemptToBlockViaRedirect = 485 blockAction == BlockAction::Deny || blockAction == BlockAction::Error; 486 #endif 487 if (attemptToBlockViaRedirect) { 488 // For a dependent module, try redirection instead of blocking it. 489 // If we fail, we reluctantly allow the module for free. 490 mozilla::nt::PEHeaders headers(*aBaseAddress); 491 blockAction = RedirectToNoOpEntryPoint(headers, *k32Exports) 492 ? BlockAction::NoOpEntryPoint 493 : BlockAction::Allow; 494 } 495 } else { 496 // Check blocklist 497 blockAction = 498 DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports); 499 } 500 } 501 502 ModuleLoadInfo::Status loadStatus = ModuleLoadInfo::Status::Blocked; 503 504 switch (blockAction) { 505 case BlockAction::Allow: 506 loadStatus = ModuleLoadInfo::Status::Loaded; 507 break; 508 509 case BlockAction::NoOpEntryPoint: 510 loadStatus = ModuleLoadInfo::Status::Redirected; 511 break; 512 513 case BlockAction::SubstituteLSP: 514 // The process heap needs to be available here because 515 // NotifyLSPSubstitutionRequired below copies a given string into 516 // the heap. We use a soft assert here, assuming LSP load always 517 // occurs after the heap is initialized. 518 MOZ_ASSERT(nt::RtlGetProcessHeap()); 519 520 // Notify patched_LdrLoadDll that it will be necessary to perform 521 // a substitution before returning. 522 ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leafOnStack); 523 break; 524 525 default: 526 break; 527 } 528 529 if (nt::RtlGetProcessHeap()) { 530 ModuleLoadFrame::NotifySectionMap( 531 nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, aStubStatus, 532 loadStatus, isInjectedDependent); 533 } 534 535 if (loadStatus == ModuleLoadInfo::Status::Loaded || 536 loadStatus == ModuleLoadInfo::Status::Redirected) { 537 return aStubStatus; 538 } 539 540 ::NtUnmapViewOfSection(aProcess, *aBaseAddress); 541 return STATUS_ACCESS_DENIED; 542 } 543 544 // To preserve compatibility with third-parties, calling into this function 545 // must not use checked stack buffers when reached through Thread32Next (see 546 // bug 1733532). Hence this function is declared as MOZ_NO_STACK_PROTECTOR. 547 // Ideally, all code relying on stack buffers should be put in the dedicated 548 // helper function AfterMapViewOfExecutableImageSection, which does not have 549 // the MOZ_NO_STACK_PROTECTOR attribute. The obi variable below is an 550 // exception to this rule, as it is required to collect the information that 551 // lets us decide whether we really need to go through the helper function. 552 NTSTATUS NTAPI patched_NtMapViewOfSection( 553 HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits, 554 SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize, 555 SECTION_INHERIT aInheritDisposition, ULONG aAllocationType, 556 ULONG aProtectionFlags) { 557 // Save off the values currently in the out-pointers for later restoration if 558 // we decide not to permit this mapping. 559 auto const rollback = 560 [ 561 // Avoid taking a reference to the stack frame, mostly out of 562 // paranoia. (The values of `aBaseAddress` et al. may have been 563 // crafted to point to our return address anyway...) 564 =, 565 // `NtMapViewOfSection` itself is mildly robust to invalid pointers; 566 // we can't easily do that, but we can at least check for `nullptr`. 567 baseAddress = aBaseAddress ? *aBaseAddress : nullptr, 568 sectionOffset = aSectionOffset ? *aSectionOffset : LARGE_INTEGER{}, 569 viewSize = aViewSize ? *aViewSize : 0]() { 570 if (aBaseAddress) *aBaseAddress = baseAddress; 571 if (aSectionOffset) *aSectionOffset = sectionOffset; 572 if (aViewSize) *aViewSize = viewSize; 573 }; 574 575 // We always map first, then we check for additional info after. 576 NTSTATUS stubStatus = stub_NtMapViewOfSection( 577 aSection, aProcess, aBaseAddress, aZeroBits, aCommitSize, aSectionOffset, 578 aViewSize, aInheritDisposition, aAllocationType, aProtectionFlags); 579 if (!NT_SUCCESS(stubStatus)) { 580 return stubStatus; 581 } 582 583 if (aProcess != nt::kCurrentProcess) { 584 // We're only interested in mapping for the current process. 585 return stubStatus; 586 } 587 588 PUBLIC_OBJECT_BASIC_INFORMATION obi; 589 NTSTATUS ntStatus = ::NtQueryObject(aSection, ObjectBasicInformation, &obi, 590 sizeof(obi), nullptr); 591 if (!NT_SUCCESS(ntStatus)) { 592 ::NtUnmapViewOfSection(aProcess, *aBaseAddress); 593 rollback(); 594 return STATUS_ACCESS_DENIED; 595 } 596 597 // We don't care about sections for which the permission to map executable 598 // views was not asked at creation time. This early exit path is notably 599 // taken for: 600 // - calls to LoadLibraryExW using LOAD_LIBRARY_AS_DATAFILE or 601 // LOAD_LIBRARY_AS_IMAGE_RESOURCE (bug 1842088), thus allowing us to load 602 // blocked DLLs for analysis without executing them; 603 // - calls to Thread32Next (bug 1733532), thus avoiding the helper function 604 // with stack cookie checks. 605 if (!(obi.GrantedAccess & SECTION_MAP_EXECUTE)) { 606 return stubStatus; 607 } 608 609 NTSTATUS rv = 610 AfterMapViewOfExecutableSection(aProcess, aBaseAddress, stubStatus); 611 if (FAILED(rv)) { 612 rollback(); 613 } 614 return rv; 615 } 616 617 } // namespace freestanding 618 } // namespace mozilla