DllBlocklistInit.cpp (9190B)
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 "nsWindowsDllInterceptor.h" 8 #include "mozilla/ImportDir.h" 9 #include "mozilla/NativeNt.h" 10 #include "mozilla/PolicyChecks.h" 11 #include "mozilla/ScopeExit.h" 12 #include "mozilla/WindowsDllBlocklist.h" 13 #include "mozilla/WindowsStackCookie.h" 14 #include "mozilla/WinHeaderOnlyUtils.h" 15 16 #include "DllBlocklistInit.h" 17 #include "freestanding/DllBlocklist.h" 18 #include "freestanding/SharedSection.h" 19 20 namespace mozilla { 21 22 #if defined(MOZ_ASAN) || defined(_M_ARM64) 23 24 // This DLL blocking code is incompatible with ASAN because 25 // it is able to execute before ASAN itself has even initialized. 26 // Also, AArch64 has not been tested with this. 27 LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP( 28 const wchar_t* aFullImagePath, HANDLE aChildProcess, 29 const IMAGE_THUNK_DATA*, const GeckoProcessType aProcessType) { 30 return mozilla::Ok(); 31 } 32 33 LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( 34 const wchar_t* aFullImagePath, HANDLE aChildProcess, 35 const bool aDisableDynamicBlocklist, 36 Maybe<std::wstring> aBlocklistFileName) { 37 return mozilla::Ok(); 38 } 39 40 #else 41 42 static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal( 43 const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr, 44 const IMAGE_THUNK_DATA* aCachedNtdllThunk, 45 const GeckoProcessType aProcessType) { 46 CrossProcessDllInterceptor intcpt(aTransferMgr.RemoteProcess()); 47 intcpt.Init(L"ntdll.dll"); 48 49 # if defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__) 50 // This debug check preserves compatibility with third-parties (see bug 51 // 1733532). 52 MOZ_ASSERT(!HasStackCookieCheck( 53 reinterpret_cast<uintptr_t>(&freestanding::patched_NtMapViewOfSection))); 54 # endif // #if defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__) 55 56 bool ok = freestanding::stub_NtMapViewOfSection.SetDetour( 57 aTransferMgr, intcpt, "NtMapViewOfSection", 58 &freestanding::patched_NtMapViewOfSection); 59 if (!ok) { 60 return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError()); 61 } 62 63 ok = freestanding::stub_LdrLoadDll.SetDetour( 64 aTransferMgr, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll); 65 if (!ok) { 66 return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError()); 67 } 68 69 // Because aChildProcess has just been created in a suspended state, its 70 // dynamic linker has not yet been initialized, thus its executable has 71 // not yet been linked with ntdll.dll. If the blocklist hook intercepts a 72 // library load prior to the link, the hook will be unable to invoke any 73 // ntdll.dll functions. 74 // 75 // We know that the executable for our *current* process's binary is already 76 // linked into ntdll, so we obtain the IAT from our own executable and graft 77 // it onto the child process's IAT, thus enabling the child process's hook to 78 // safely make its ntdll calls. 79 80 const nt::PEHeaders& ourExeImage = aTransferMgr.LocalPEHeaders(); 81 82 // As part of our mitigation of binary tampering, copy our import directory 83 // from the original in our executable file. 84 LauncherVoidResult importDirRestored = 85 RestoreImportDirectory(aFullImagePath, aTransferMgr); 86 if (importDirRestored.isErr()) { 87 return importDirRestored; 88 } 89 90 mozilla::nt::PEHeaders ntdllImage(::GetModuleHandleW(L"ntdll.dll")); 91 if (!ntdllImage) { 92 return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); 93 } 94 95 // If we have a cached IAT i.e. |aCachedNtdllThunk| is non-null, we can 96 // safely copy it to |aChildProcess| even if the local IAT has been modified. 97 // If |aCachedNtdllThunk| is null, we've failed to cache the IAT or we're in 98 // the launcher process where there is no chance to cache the IAT. In those 99 // cases, we retrieve the IAT with the boundary check to avoid a modified IAT 100 // from being copied into |aChildProcess|. 101 Maybe<Span<IMAGE_THUNK_DATA> > ntdllThunks; 102 if (aCachedNtdllThunk) { 103 ntdllThunks = ourExeImage.GetIATThunksForModule("ntdll.dll"); 104 } else { 105 Maybe<Range<const uint8_t> > ntdllBoundaries = ntdllImage.GetBounds(); 106 if (!ntdllBoundaries) { 107 return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); 108 } 109 110 // We can use GetIATThunksForModule() to check whether IAT is modified 111 // or not because no functions exported from ntdll.dll is forwarded. 112 ntdllThunks = 113 ourExeImage.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr()); 114 } 115 if (!ntdllThunks) { 116 return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA); 117 } 118 119 { // Scope for prot 120 PIMAGE_THUNK_DATA firstIatThunkDst = ntdllThunks.value().data(); 121 const IMAGE_THUNK_DATA* firstIatThunkSrc = 122 aCachedNtdllThunk ? aCachedNtdllThunk : firstIatThunkDst; 123 SIZE_T iatLength = ntdllThunks.value().LengthBytes(); 124 125 AutoVirtualProtect prot = 126 aTransferMgr.Protect(firstIatThunkDst, iatLength, PAGE_READWRITE); 127 if (!prot) { 128 return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError()); 129 } 130 131 LauncherVoidResult writeResult = 132 aTransferMgr.Transfer(firstIatThunkDst, firstIatThunkSrc, iatLength); 133 if (writeResult.isErr()) { 134 return writeResult.propagateErr(); 135 } 136 } 137 138 // Tell the mozglue blocklist that we have bootstrapped 139 uint32_t newFlags = eDllBlocklistInitFlagWasBootstrapped; 140 141 if (gBlocklistInitFlags & eDllBlocklistInitFlagWasBootstrapped) { 142 // If we ourselves were bootstrapped, then we are starting a child process 143 // and need to set the appropriate flag. 144 newFlags |= eDllBlocklistInitFlagIsChildProcess; 145 } 146 147 SetDllBlocklistProcessTypeFlags(newFlags, aProcessType); 148 149 LauncherVoidResult writeResult = 150 aTransferMgr.Transfer(&gBlocklistInitFlags, &newFlags, sizeof(newFlags)); 151 if (writeResult.isErr()) { 152 return writeResult.propagateErr(); 153 } 154 155 return Ok(); 156 } 157 158 LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP( 159 const wchar_t* aFullImagePath, HANDLE aChildProcess, 160 const IMAGE_THUNK_DATA* aCachedNtdllThunk, 161 const GeckoProcessType aProcessType) { 162 nt::CrossExecTransferManager transferMgr(aChildProcess); 163 if (!transferMgr) { 164 return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); 165 } 166 167 // We come here when the browser process launches a sandbox process. 168 // If the launcher process already failed to bootstrap the browser process, 169 // we should not attempt to bootstrap a child process because it's likely 170 // to fail again. Instead, we only restore the import directory entry. 171 if (!(gBlocklistInitFlags & eDllBlocklistInitFlagWasBootstrapped)) { 172 return RestoreImportDirectory(aFullImagePath, transferMgr); 173 } 174 175 // Transfer a readonly handle to the child processes because all information 176 // are already written to the section by the launcher and main process. 177 LauncherVoidResult transferResult = 178 freestanding::gSharedSection.TransferHandle(transferMgr, GENERIC_READ); 179 if (transferResult.isErr()) { 180 return transferResult.propagateErr(); 181 } 182 183 return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr, 184 aCachedNtdllThunk, aProcessType); 185 } 186 187 LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( 188 const wchar_t* aFullImagePath, HANDLE aChildProcess, 189 const bool aDisableDynamicBlocklist, 190 Maybe<std::wstring> aBlocklistFileName) { 191 nt::CrossExecTransferManager transferMgr(aChildProcess); 192 if (!transferMgr) { 193 return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); 194 } 195 196 // The launcher process initializes a section object, whose handle is 197 // transferred to the browser process, and that transferred handle in 198 // the browser process is transferred to the sandbox processes. 199 LauncherVoidResultWithLineInfo result = freestanding::gSharedSection.Init(); 200 if (result.isErr()) { 201 return result; 202 } 203 204 if (aBlocklistFileName.isSome() && 205 !PolicyCheckBoolean(L"DisableThirdPartyModuleBlocking")) { 206 DynamicBlockList blockList(aBlocklistFileName->c_str()); 207 result = freestanding::gSharedSection.SetBlocklist( 208 blockList, aDisableDynamicBlocklist); 209 if (result.isErr()) { 210 return result; 211 } 212 } 213 214 // Transfer a writable handle to the main process because it needs to append 215 // dependent module paths to the section. 216 LauncherVoidResult transferResult = 217 freestanding::gSharedSection.TransferHandle(transferMgr, 218 GENERIC_READ | GENERIC_WRITE); 219 if (transferResult.isErr()) { 220 return transferResult.propagateErr(); 221 } 222 223 auto clearInstance = MakeScopeExit([]() { 224 // After transfer, the launcher process does not need the object anymore. 225 freestanding::gSharedSection.Reset(nullptr); 226 }); 227 return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr, nullptr, 228 GeckoProcessType_Default); 229 } 230 231 #endif // defined(MOZ_ASAN) || defined(_M_ARM64) 232 233 } // namespace mozilla