SharedSection.cpp (12887B)
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 "SharedSection.h" 8 9 #include <algorithm> 10 #include "CheckForCaller.h" 11 #include "mozilla/BinarySearch.h" 12 13 namespace { 14 15 bool AddString(mozilla::Span<wchar_t> aBuffer, const UNICODE_STRING& aStr) { 16 size_t offsetElements = 0; 17 while (offsetElements < aBuffer.Length()) { 18 UNICODE_STRING uniStr; 19 ::RtlInitUnicodeString(&uniStr, aBuffer.data() + offsetElements); 20 21 if (uniStr.Length == 0) { 22 // Reached to the array's last item. 23 break; 24 } 25 26 if (::RtlCompareUnicodeString(&uniStr, &aStr, TRUE) == 0) { 27 // Already included in the array. 28 return true; 29 } 30 31 // Go to the next string. 32 offsetElements += uniStr.MaximumLength / sizeof(wchar_t); 33 } 34 35 // Ensure enough space including the last empty string at the end. 36 if (offsetElements * sizeof(wchar_t) + aStr.Length + sizeof(wchar_t) + 37 sizeof(wchar_t) > 38 aBuffer.LengthBytes()) { 39 return false; 40 } 41 42 auto newStr = aBuffer.Subspan(offsetElements); 43 memcpy(newStr.data(), aStr.Buffer, aStr.Length); 44 memset(newStr.data() + aStr.Length / sizeof(wchar_t), 0, sizeof(wchar_t)); 45 return true; 46 } 47 48 } // anonymous namespace 49 50 namespace mozilla { 51 namespace freestanding { 52 53 SharedSection gSharedSection; 54 55 // Why don't we use ::GetProcAddress? 56 // If the export table of kernel32.dll is tampered in the current process, 57 // we cannot transfer an RVA because the function pointed by the RVA may not 58 // exist in a target process. 59 // We can use ::GetProcAddress with additional check to detect tampering, but 60 // FindExportAddressTableEntry fits perfectly here because it returns nullptr 61 // if the target entry is outside the image, which means it's tampered or 62 // forwarded to another DLL. 63 #define INIT_FUNCTION(exports, name) \ 64 do { \ 65 auto rvaToFunction = exports.FindExportAddressTableEntry(#name); \ 66 if (!rvaToFunction) { \ 67 return; \ 68 } \ 69 m##name = reinterpret_cast<decltype(m##name)>(*rvaToFunction); \ 70 } while (0) 71 72 #define RESOLVE_FUNCTION(base, name) \ 73 m##name = reinterpret_cast<decltype(m##name)>( \ 74 base + reinterpret_cast<uintptr_t>(m##name)) 75 76 void Kernel32ExportsSolver::Init() { 77 interceptor::MMPolicyInProcess policy; 78 auto k32Exports = nt::PEExportSection<interceptor::MMPolicyInProcess>::Get( 79 ::GetModuleHandleW(L"kernel32.dll"), policy); 80 if (!k32Exports) { 81 return; 82 } 83 84 // Please make sure these functions are not forwarded to another DLL. 85 INIT_FUNCTION(k32Exports, FlushInstructionCache); 86 INIT_FUNCTION(k32Exports, GetModuleHandleW); 87 INIT_FUNCTION(k32Exports, GetSystemInfo); 88 INIT_FUNCTION(k32Exports, VirtualProtect); 89 } 90 91 bool Kernel32ExportsSolver::Resolve() { 92 const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll"); 93 94 // We cannot use GetModuleHandleW because this code can be called 95 // before IAT is resolved. 96 auto k32Module = nt::GetModuleHandleFromLeafName(k32Name); 97 if (k32Module.isErr()) { 98 // Probably this is called before kernel32.dll is loaded. 99 return false; 100 } 101 102 uintptr_t k32Base = 103 nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(k32Module.unwrap()); 104 105 RESOLVE_FUNCTION(k32Base, FlushInstructionCache); 106 RESOLVE_FUNCTION(k32Base, GetModuleHandleW); 107 RESOLVE_FUNCTION(k32Base, GetSystemInfo); 108 RESOLVE_FUNCTION(k32Base, VirtualProtect); 109 110 return true; 111 } 112 113 HANDLE SharedSection::sSectionHandle = nullptr; 114 SharedSection::Layout* SharedSection::sWriteCopyView = nullptr; 115 RTL_RUN_ONCE SharedSection::sEnsureOnce = RTL_RUN_ONCE_INIT; 116 nt::SRWLock SharedSection::sLock; 117 118 void SharedSection::Reset(HANDLE aNewSectionObject) { 119 nt::AutoExclusiveLock{sLock}; 120 if (sWriteCopyView) { 121 nt::AutoMappedView view(sWriteCopyView); 122 sWriteCopyView = nullptr; 123 ::RtlRunOnceInitialize(&sEnsureOnce); 124 } 125 126 if (sSectionHandle != aNewSectionObject) { 127 if (sSectionHandle) { 128 ::CloseHandle(sSectionHandle); 129 } 130 sSectionHandle = aNewSectionObject; 131 } 132 } 133 134 void SharedSection::ConvertToReadOnly() { 135 if (!sSectionHandle) { 136 return; 137 } 138 139 HANDLE readonlyHandle; 140 if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle, 141 nt::kCurrentProcess, &readonlyHandle, GENERIC_READ, 142 FALSE, 0)) { 143 return; 144 } 145 146 Reset(readonlyHandle); 147 } 148 149 LauncherVoidResult SharedSection::Init() { 150 static_assert( 151 kSharedViewSize >= sizeof(Layout), 152 "kSharedViewSize is too small to represent SharedSection::Layout."); 153 154 HANDLE section = 155 ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, 156 kSharedViewSize, nullptr); 157 if (!section) { 158 return LAUNCHER_ERROR_FROM_LAST(); 159 } 160 Reset(section); 161 162 // The initial contents of the pages in a file mapping object backed by 163 // the operating system paging file are 0 (zero). No need to zero it out 164 // ourselves. 165 // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw 166 nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE); 167 if (!writableView) { 168 return LAUNCHER_ERROR_FROM_LAST(); 169 } 170 171 Layout* view = writableView.as<Layout>(); 172 view->mK32Exports.Init(); 173 view->mState = Layout::State::kInitialized; 174 // Leave view->mDependentModulePathArrayStart to be zero to indicate 175 // we can add blocklist entries 176 return Ok(); 177 } 178 179 LauncherVoidResult SharedSection::AddDependentModule(PCUNICODE_STRING aNtPath) { 180 nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE); 181 if (!writableView) { 182 return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error()); 183 } 184 185 Layout* view = writableView.as<Layout>(); 186 if (!view->mDependentModulePathArrayStart) { 187 // This is the first time AddDependentModule is called. We set the initial 188 // value to mDependentModulePathArrayStart, which *closes* the blocklist. 189 // After this, AddBlocklist is no longer allowed. 190 view->mDependentModulePathArrayStart = 191 FIELD_OFFSET(Layout, mFirstBlockEntry) + sizeof(DllBlockInfo); 192 } 193 194 if (!AddString(view->GetDependentModules(), *aNtPath)) { 195 return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); 196 } 197 198 return Ok(); 199 } 200 201 LauncherVoidResult SharedSection::SetBlocklist( 202 const DynamicBlockList& aBlocklist, bool isDisabled) { 203 if (!aBlocklist.GetPayloadSize()) { 204 return Ok(); 205 } 206 207 nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE); 208 if (!writableView) { 209 return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error()); 210 } 211 212 Layout* view = writableView.as<Layout>(); 213 if (view->mDependentModulePathArrayStart > 0) { 214 // If the dependent module array is already available, we must not update 215 // the blocklist. 216 return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_STATE); 217 } 218 219 view->mBlocklistIsDisabled = isDisabled ? 1 : 0; 220 uintptr_t bufferEnd = reinterpret_cast<uintptr_t>(view) + kSharedViewSize; 221 size_t bytesCopied = aBlocklist.CopyTo( 222 view->mFirstBlockEntry, 223 bufferEnd - reinterpret_cast<uintptr_t>(view->mFirstBlockEntry)); 224 if (!bytesCopied) { 225 return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); 226 } 227 228 // Setting mDependentModulePathArrayStart to a non-zero value means 229 // we no longer accept blocklist entries 230 // Just to be safe, make sure we don't overwrite mFirstBlockEntry even 231 // if there are no entries. 232 view->mDependentModulePathArrayStart = 233 FIELD_OFFSET(Layout, mFirstBlockEntry) + 234 std::max(bytesCopied, sizeof(DllBlockInfo)); 235 return Ok(); 236 } 237 238 /* static */ 239 ULONG NTAPI SharedSection::EnsureWriteCopyViewOnce(PRTL_RUN_ONCE, PVOID, 240 PVOID*) { 241 if (!sWriteCopyView) { 242 nt::AutoMappedView view(sSectionHandle, PAGE_WRITECOPY); 243 if (!view) { 244 return TRUE; 245 } 246 sWriteCopyView = view.as<Layout>(); 247 view.release(); 248 } 249 return sWriteCopyView->Resolve() ? TRUE : FALSE; 250 } 251 252 SharedSection::Layout* SharedSection::EnsureWriteCopyView( 253 bool requireKernel32Exports /*= false */) { 254 ::RtlRunOnceExecuteOnce(&sEnsureOnce, &EnsureWriteCopyViewOnce, nullptr, 255 nullptr); 256 if (!sWriteCopyView) { 257 return nullptr; 258 } 259 auto requiredState = requireKernel32Exports 260 ? Layout::State::kResolved 261 : Layout::State::kLoadedDynamicBlocklistEntries; 262 return sWriteCopyView->mState >= requiredState ? sWriteCopyView : nullptr; 263 } 264 265 bool SharedSection::Layout::Resolve() { 266 if (mState == State::kResolved) { 267 return true; 268 } 269 if (mState == State::kUninitialized) { 270 return false; 271 } 272 if (mState == State::kInitialized) { 273 if (!mNumBlockEntries) { 274 uintptr_t arrayBase = reinterpret_cast<uintptr_t>(mFirstBlockEntry); 275 uint32_t numEntries = 0; 276 for (DllBlockInfo* entry = mFirstBlockEntry; 277 entry->mName.Length && numEntries < GetMaxNumBlockEntries(); 278 ++entry) { 279 entry->mName.Buffer = reinterpret_cast<wchar_t*>( 280 arrayBase + reinterpret_cast<uintptr_t>(entry->mName.Buffer)); 281 ++numEntries; 282 } 283 mNumBlockEntries = numEntries; 284 // Sort by name so that we can binary-search 285 std::sort(mFirstBlockEntry, mFirstBlockEntry + mNumBlockEntries, 286 [](const DllBlockInfo& a, const DllBlockInfo& b) { 287 return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) < 288 0; 289 }); 290 } 291 mState = State::kLoadedDynamicBlocklistEntries; 292 } 293 294 if (!mK32Exports.Resolve()) { 295 return false; 296 } 297 298 mState = State::kResolved; 299 return true; 300 } 301 302 Span<wchar_t> SharedSection::Layout::GetDependentModules() { 303 if (!mDependentModulePathArrayStart) { 304 return nullptr; 305 } 306 return Span<wchar_t>( 307 reinterpret_cast<wchar_t*>(reinterpret_cast<uintptr_t>(this) + 308 mDependentModulePathArrayStart), 309 (kSharedViewSize - mDependentModulePathArrayStart) / sizeof(wchar_t)); 310 } 311 312 bool SharedSection::Layout::IsDisabled() const { 313 return !!mBlocklistIsDisabled; 314 } 315 316 const DllBlockInfo* SharedSection::Layout::SearchBlocklist( 317 const UNICODE_STRING& aLeafName) const { 318 MOZ_ASSERT(mState >= State::kLoadedDynamicBlocklistEntries); 319 DllBlockInfoComparator comp(aLeafName); 320 size_t match; 321 if (!BinarySearchIf(mFirstBlockEntry, 0, mNumBlockEntries, comp, &match)) { 322 return nullptr; 323 } 324 return &mFirstBlockEntry[match]; 325 } 326 327 Kernel32ExportsSolver* SharedSection::GetKernel32Exports() { 328 Layout* writeCopyView = EnsureWriteCopyView(true); 329 return writeCopyView ? &writeCopyView->mK32Exports : nullptr; 330 } 331 332 Maybe<Vector<const wchar_t*>> SharedSection::GetDependentModules() { 333 Layout* writeCopyView = EnsureWriteCopyView(); 334 if (!writeCopyView) { 335 return Nothing(); 336 } 337 338 mozilla::Span<wchar_t> dependentModules = 339 writeCopyView->GetDependentModules(); 340 // Convert a null-delimited string set to a string vector. 341 Vector<const wchar_t*> paths; 342 for (const wchar_t* p = dependentModules.data(); 343 (p - dependentModules.data() < 344 static_cast<long long>(dependentModules.size()) && 345 *p);) { 346 if (MOZ_UNLIKELY(!paths.append(p))) { 347 return Nothing(); 348 } 349 while (*p) { 350 ++p; 351 } 352 ++p; 353 } 354 return Some(std::move(paths)); 355 } 356 357 Span<const DllBlockInfo> SharedSection::GetDynamicBlocklist() { 358 Layout* writeCopyView = EnsureWriteCopyView(); 359 return writeCopyView ? writeCopyView->GetModulePathArray() : nullptr; 360 } 361 362 const DllBlockInfo* SharedSection::SearchBlocklist( 363 const UNICODE_STRING& aLeafName) { 364 Layout* writeCopyView = EnsureWriteCopyView(); 365 return writeCopyView ? writeCopyView->SearchBlocklist(aLeafName) : nullptr; 366 } 367 368 bool SharedSection::IsDisabled() { 369 Layout* writeCopyView = EnsureWriteCopyView(); 370 return writeCopyView ? writeCopyView->IsDisabled() : false; 371 } 372 373 LauncherVoidResult SharedSection::TransferHandle( 374 nt::CrossExecTransferManager& aTransferMgr, DWORD aDesiredAccess, 375 HANDLE* aDestinationAddress) { 376 HANDLE remoteHandle; 377 if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle, 378 aTransferMgr.RemoteProcess(), &remoteHandle, 379 aDesiredAccess, FALSE, 0)) { 380 return LAUNCHER_ERROR_FROM_LAST(); 381 } 382 383 return aTransferMgr.Transfer(aDestinationAddress, &remoteHandle, 384 sizeof(remoteHandle)); 385 } 386 387 } // namespace freestanding 388 } // namespace mozilla