From 187d19452d2cb3889556d8c3b8f82cd32f7c024b Mon Sep 17 00:00:00 2001 From: Toshihito Kikuchi Date: Tue, 10 Nov 2020 20:51:00 +0000 Subject: [PATCH] Bug 1659438 - Part2: Transfer Kernel32ExportsSolver as a shared memory. r=mhowell We transfer several ntdll's function addresses to a child process directly via `WriteProcessMemory`. This patch changes the way to transfer data to using a section object as Chromium sandbox does, so that we can transfer more data with the same cost as transferring a single handle value. Depends on D96282 Differential Revision: https://phabricator.services.mozilla.com/D96283 --- browser/app/winlauncher/DllBlocklistInit.cpp | 62 ++++++---- .../winlauncher/freestanding/DllBlocklist.cpp | 11 +- .../freestanding/SharedSection.cpp | 78 +++++++++--- .../winlauncher/freestanding/SharedSection.h | 39 ++++-- .../winlauncher/test/TestCrossProcessWin.cpp | 114 +++++++++++------- mozglue/misc/NativeNt.h | 81 ++++++++++++- 6 files changed, 287 insertions(+), 98 deletions(-) diff --git a/browser/app/winlauncher/DllBlocklistInit.cpp b/browser/app/winlauncher/DllBlocklistInit.cpp index 3f770459010d..11e79f5ac90c 100644 --- a/browser/app/winlauncher/DllBlocklistInit.cpp +++ b/browser/app/winlauncher/DllBlocklistInit.cpp @@ -41,30 +41,26 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( #else static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal( - const wchar_t* aFullImagePath, HANDLE aChildProcess, + const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr, const IMAGE_THUNK_DATA* aCachedNtdllThunk) { - nt::CrossExecTransferManager transferMgr(aChildProcess); - if (!transferMgr) { - return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + LauncherVoidResult transferResult = + freestanding::gSharedSection.TransferHandle(aTransferMgr); + if (transferResult.isErr()) { + return transferResult.propagateErr(); } - freestanding::gK32.Init(); - if (freestanding::gK32.IsInitialized()) { - Unused << freestanding::gK32.Transfer(transferMgr, &freestanding::gK32); - } - - CrossProcessDllInterceptor intcpt(aChildProcess); + CrossProcessDllInterceptor intcpt(aTransferMgr.RemoteProcess()); intcpt.Init(L"ntdll.dll"); bool ok = freestanding::stub_NtMapViewOfSection.SetDetour( - transferMgr, intcpt, "NtMapViewOfSection", + aTransferMgr, intcpt, "NtMapViewOfSection", &freestanding::patched_NtMapViewOfSection); if (!ok) { return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError()); } ok = freestanding::stub_LdrLoadDll.SetDetour( - transferMgr, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll); + aTransferMgr, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll); if (!ok) { return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError()); } @@ -80,12 +76,12 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal( // it onto the child process's IAT, thus enabling the child process's hook to // safely make its ntdll calls. - const nt::PEHeaders& ourExeImage = transferMgr.LocalPEHeaders(); + const nt::PEHeaders& ourExeImage = aTransferMgr.LocalPEHeaders(); // As part of our mitigation of binary tampering, copy our import directory // from the original in our executable file. LauncherVoidResult importDirRestored = - RestoreImportDirectory(aFullImagePath, transferMgr); + RestoreImportDirectory(aFullImagePath, aTransferMgr); if (importDirRestored.isErr()) { return importDirRestored; } @@ -126,13 +122,13 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal( SIZE_T iatLength = ntdllThunks.value().LengthBytes(); AutoVirtualProtect prot = - transferMgr.Protect(firstIatThunkDst, iatLength, PAGE_READWRITE); + aTransferMgr.Protect(firstIatThunkDst, iatLength, PAGE_READWRITE); if (!prot) { return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError()); } LauncherVoidResult writeResult = - transferMgr.Transfer(firstIatThunkDst, firstIatThunkSrc, iatLength); + aTransferMgr.Transfer(firstIatThunkDst, firstIatThunkSrc, iatLength); if (writeResult.isErr()) { return writeResult.propagateErr(); } @@ -148,7 +144,7 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal( } LauncherVoidResult writeResult = - transferMgr.Transfer(&gBlocklistInitFlags, &newFlags, sizeof(newFlags)); + aTransferMgr.Transfer(&gBlocklistInitFlags, &newFlags, sizeof(newFlags)); if (writeResult.isErr()) { return writeResult.propagateErr(); } @@ -159,26 +155,44 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal( LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP( const wchar_t* aFullImagePath, HANDLE aChildProcess, const IMAGE_THUNK_DATA* aCachedNtdllThunk) { + nt::CrossExecTransferManager transferMgr(aChildProcess); + if (!transferMgr) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + } + // We come here when the browser process launches a sandbox process. // If the launcher process already failed to bootstrap the browser process, // we should not attempt to bootstrap a child process because it's likely // to fail again. Instead, we only restore the import directory entry. if (!(gBlocklistInitFlags & eDllBlocklistInitFlagWasBootstrapped)) { - nt::CrossExecTransferManager transferMgr(aChildProcess); - if (!transferMgr) { - return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); - } - return RestoreImportDirectory(aFullImagePath, transferMgr); } - return InitializeDllBlocklistOOPInternal(aFullImagePath, aChildProcess, + return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr, aCachedNtdllThunk); } LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher( const wchar_t* aFullImagePath, HANDLE aChildProcess) { - return InitializeDllBlocklistOOPInternal(aFullImagePath, aChildProcess, + nt::CrossExecTransferManager transferMgr(aChildProcess); + if (!transferMgr) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + } + + // The launcher process initializes a section object, whose handle is + // transferred to the browser process, and that transferred handle in + // the browser process is transferred to the sandbox processes. + LauncherVoidResultWithLineInfo result = + freestanding::gSharedSection.Init(transferMgr.LocalPEHeaders()); + if (result.isErr()) { + return result.propagateErr(); + } + + auto clearInstance = MakeScopeExit([]() { + // After transfer, the launcher process does not need the object anymore. + freestanding::gSharedSection.Reset(nullptr); + }); + return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr, nullptr); } diff --git a/browser/app/winlauncher/freestanding/DllBlocklist.cpp b/browser/app/winlauncher/freestanding/DllBlocklist.cpp index bbbe10f0cd58..07436a2a8080 100644 --- a/browser/app/winlauncher/freestanding/DllBlocklist.cpp +++ b/browser/app/winlauncher/freestanding/DllBlocklist.cpp @@ -279,14 +279,19 @@ static BlockAction DetermineBlockAction(const UNICODE_STRING& aLeafName, // If we fail to detour a module's entrypoint, we reluctantly allow the // module for free. + auto resultView = mozilla::freestanding::gSharedSection.GetView(); + if (resultView.isErr()) { + return BlockAction::Allow; + } + static RTL_RUN_ONCE sRunOnce = RTL_RUN_ONCE_INIT; - mozilla::freestanding::gK32.Resolve(sRunOnce); - if (!mozilla::freestanding::gK32.IsResolved()) { + resultView.inspect()->mK32Exports.Resolve(sRunOnce); + if (!resultView.inspect()->mK32Exports.IsResolved()) { return BlockAction::Allow; } mozilla::interceptor::WindowsDllEntryPointInterceptor interceptor( - mozilla::freestanding::gK32); + resultView.inspect()->mK32Exports); if (!interceptor.Set(headers, NoOp_DllMain)) { return BlockAction::Allow; } diff --git a/browser/app/winlauncher/freestanding/SharedSection.cpp b/browser/app/winlauncher/freestanding/SharedSection.cpp index 8eb664f5e6f3..0a9f950f6c2c 100644 --- a/browser/app/winlauncher/freestanding/SharedSection.cpp +++ b/browser/app/winlauncher/freestanding/SharedSection.cpp @@ -9,7 +9,7 @@ namespace mozilla { namespace freestanding { -Kernel32ExportsSolver gK32; +SharedSection gSharedSection; bool Kernel32ExportsSolver::IsInitialized() const { return mState == State::Initialized || IsResolved(); @@ -33,11 +33,12 @@ bool Kernel32ExportsSolver::IsResolved() const { if (!rvaToFunction) { \ return; \ } \ - mOffsets.m##name = *rvaToFunction; \ + m##name = reinterpret_cast(*rvaToFunction); \ } while (0) -#define RESOLVE_FUNCTION(base, name) \ - m##name = reinterpret_cast(base + mOffsets.m##name) +#define RESOLVE_FUNCTION(base, name) \ + m##name = reinterpret_cast( \ + base + reinterpret_cast(m##name)) void Kernel32ExportsSolver::Init() { if (mState == State::Initialized || mState == State::Resolved) { @@ -95,18 +96,67 @@ void Kernel32ExportsSolver::Resolve(RTL_RUN_ONCE& aRunOnce) { ::RtlRunOnceExecuteOnce(&aRunOnce, &ResolveOnce, this, nullptr); } -LauncherVoidResult Kernel32ExportsSolver::Transfer( - nt::CrossExecTransferManager& aTransferMgr, - Kernel32ExportsSolver* aTargetAddress) const { - LauncherVoidResult writeResult = aTransferMgr.Transfer( - &aTargetAddress->mOffsets, &mOffsets, sizeof(mOffsets)); - if (writeResult.isErr()) { - return writeResult.propagateErr(); +HANDLE SharedSection::sSectionHandle = nullptr; +void* SharedSection::sWriteCopyView = nullptr; + +void SharedSection::Reset(HANDLE aNewSecionObject) { + if (sWriteCopyView) { + nt::AutoMappedView view(sWriteCopyView); + sWriteCopyView = nullptr; } - State stateInChild = State::Initialized; - return aTransferMgr.Transfer(&aTargetAddress->mState, &stateInChild, - sizeof(stateInChild)); + if (sSectionHandle) { + ::CloseHandle(sSectionHandle); + } + sSectionHandle = aNewSecionObject; +} + +LauncherVoidResult SharedSection::Init(const nt::PEHeaders& aPEHeaders) { + HANDLE section = + ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, + sizeof(Kernel32ExportsSolver), nullptr); + if (!section) { + return LAUNCHER_ERROR_FROM_LAST(); + } + Reset(section); + + // The initial contents of the pages in a file mapping object backed by + // the operating system paging file are 0 (zero). No need to zero it out + // ourselves. + // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw + nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE); + if (!writableView) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + SharedSection::Layout* view = writableView.as(); + view->mK32Exports.Init(); + + return Ok(); +} + +LauncherResult SharedSection::GetView() { + if (!sWriteCopyView) { + nt::AutoMappedView view(sSectionHandle, PAGE_WRITECOPY); + if (!view) { + return LAUNCHER_ERROR_FROM_LAST(); + } + sWriteCopyView = view.release(); + } + return reinterpret_cast(sWriteCopyView); +} + +LauncherVoidResult SharedSection::TransferHandle( + nt::CrossExecTransferManager& aTransferMgr, HANDLE* aDestinationAddress) { + HANDLE remoteHandle; + if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle, + aTransferMgr.RemoteProcess(), &remoteHandle, + GENERIC_READ, FALSE, 0)) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + return aTransferMgr.Transfer(aDestinationAddress, &remoteHandle, + sizeof(remoteHandle)); } } // namespace freestanding diff --git a/browser/app/winlauncher/freestanding/SharedSection.h b/browser/app/winlauncher/freestanding/SharedSection.h index d6e1522c7b20..5468fe4f94d3 100644 --- a/browser/app/winlauncher/freestanding/SharedSection.h +++ b/browser/app/winlauncher/freestanding/SharedSection.h @@ -25,12 +25,6 @@ class MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final Resolved, } mState; - struct FunctionOffsets { - uint32_t mFlushInstructionCache; - uint32_t mGetSystemInfo; - uint32_t mVirtualProtect; - } mOffsets; - static ULONG NTAPI ResolveOnce(PRTL_RUN_ONCE aRunOnce, PVOID aParameter, PVOID*); void ResolveInternal(); @@ -48,11 +42,38 @@ class MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final void Init(); void Resolve(RTL_RUN_ONCE& aRunOnce); - LauncherVoidResult Transfer(nt::CrossExecTransferManager& aTransferMgr, - Kernel32ExportsSolver* aTargetAddress) const; }; -extern Kernel32ExportsSolver gK32; +// This class manages a section which is created in the launcher process and +// mapped in the browser process and the sandboxed processes as a copy-on-write +// region. The section's layout is represented as SharedSection::Layout. +class MOZ_TRIVIAL_CTOR_DTOR SharedSection final { + // As we define a global variable of this class and use it in our blocklist + // which is excuted in a process's early stage. If we have a complex dtor, + // the static initializer tries to register that dtor with onexit() of + // ucrtbase.dll which is not loaded yet, resulting in crash. Thus, we have + // a raw handle and a pointer as a static variable and manually release them + // by calling Reset() where possible. + static HANDLE sSectionHandle; + static void* sWriteCopyView; + + public: + struct Layout final { + Kernel32ExportsSolver mK32Exports; + + Layout() = delete; // disallow instantiation + }; + + static void Reset(HANDLE aNewSecionObject); + static LauncherVoidResult Init(const nt::PEHeaders& aPEHeaders); + + static LauncherResult GetView(); + static LauncherVoidResult TransferHandle( + nt::CrossExecTransferManager& aTransferMgr, + HANDLE* aDestinationAddress = &sSectionHandle); +}; + +extern SharedSection gSharedSection; } // namespace freestanding } // namespace mozilla diff --git a/browser/app/winlauncher/test/TestCrossProcessWin.cpp b/browser/app/winlauncher/test/TestCrossProcessWin.cpp index 2fa4a299c4a0..5a97cece1386 100644 --- a/browser/app/winlauncher/test/TestCrossProcessWin.cpp +++ b/browser/app/winlauncher/test/TestCrossProcessWin.cpp @@ -13,6 +13,7 @@ const wchar_t kChildArg[] = L"--child"; using namespace mozilla; +using namespace mozilla::freestanding; template void PrintLauncherError(const LauncherResult& aResult, @@ -22,17 +23,38 @@ void PrintLauncherError(const LauncherResult& aResult, err.mError.AsHResult(), err.mFile, err.mLine); } -#define VERIFY_FUNCTION_RESOLVED(mod, name) \ - do { \ - if (reinterpret_cast(freestanding::gK32.m##name) != \ - ::GetProcAddress(mod, #name)) { \ - printf( \ - "TEST-FAILED | TestCrossProcessWin | " \ - "Kernel32ExportsSolver::" #name " did not match.\n"); \ - return 1; \ - } \ +#define VERIFY_FUNCTION_RESOLVED(mod, exports, name) \ + do { \ + if (reinterpret_cast(exports.m##name) != \ + ::GetProcAddress(mod, #name)) { \ + printf( \ + "TEST-FAILED | TestCrossProcessWin | " \ + "Kernel32ExportsSolver::" #name " did not match.\n"); \ + return false; \ + } \ } while (0) +static bool VerifySharedSection(SharedSection& aSharedSection) { + LauncherResult resultView = aSharedSection.GetView(); + if (resultView.isErr()) { + PrintLauncherError(resultView, "Failed to map a shared section"); + return false; + } + + SharedSection::Layout* view = resultView.unwrap(); + + // Use a local variable of RTL_RUN_ONCE to resolve Kernel32Exports every time + RTL_RUN_ONCE sRunEveryTime = RTL_RUN_ONCE_INIT; + view->mK32Exports.Resolve(sRunEveryTime); + + HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll"); + VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, FlushInstructionCache); + VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, GetSystemInfo); + VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, VirtualProtect); + + return true; +} + class ChildProcess final { nsAutoHandle mChildProcess; nsAutoHandle mChildMainThread; @@ -69,18 +91,24 @@ class ChildProcess final { return 1; } - static RTL_RUN_ONCE sRunOnce = RTL_RUN_ONCE_INIT; - freestanding::gK32.Resolve(sRunOnce); - if (!freestanding::gK32.IsResolved()) { - printf( - "TEST-FAILED | TestCrossProcessWin | " - "Kernel32ExportsSolver::Resolve failed.\n"); + if (!VerifySharedSection(gSharedSection)) { + return 1; } - HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll"); - VERIFY_FUNCTION_RESOLVED(k32mod, FlushInstructionCache); - VERIFY_FUNCTION_RESOLVED(k32mod, GetSystemInfo); - VERIFY_FUNCTION_RESOLVED(k32mod, VirtualProtect); + // Test a scenario to transfer a transferred section + static HANDLE copiedHandle = nullptr; + nt::CrossExecTransferManager tansferToSelf(::GetCurrentProcess()); + LauncherVoidResult result = + gSharedSection.TransferHandle(tansferToSelf, &copiedHandle); + if (result.isErr()) { + PrintLauncherError(result, "SharedSection::TransferHandle(self) failed"); + return 1; + } + + gSharedSection.Reset(copiedHandle); + if (!VerifySharedSection(gSharedSection)) { + return 1; + } return 0; } @@ -175,35 +203,19 @@ int wmain(int argc, wchar_t* argv[]) { return 1; } - freestanding::gK32.Init(); - if (!freestanding::gK32.IsInitialized()) { - printf( - "TEST-FAILED | TestCrossProcessWin | " - "Kernel32ExportsSolver initialization failed.\n"); - return 1; - } - - LauncherVoidResult writeResult = - freestanding::gK32.Transfer(transferMgr, &freestanding::gK32); - if (writeResult.isErr()) { - PrintLauncherError(writeResult, "Kernel32ExportsSolver::Transfer failed"); - return 1; - } - - writeResult = + LauncherVoidResult result = transferMgr.Transfer(&ChildProcess::sExecutableImageBase, &remoteImageBase.inspect(), sizeof(HMODULE)); - if (writeResult.isErr()) { - PrintLauncherError(writeResult, - "ChildProcess::WriteData(Imagebase) failed"); + if (result.isErr()) { + PrintLauncherError(result, "ChildProcess::WriteData(Imagebase) failed"); return 1; } DWORD childPid = childProcess.GetProcessId(); DWORD* readOnlyData = const_cast(&ChildProcess::sReadOnlyProcessId); - writeResult = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD)); - if (writeResult.isOk()) { + result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD)); + if (result.isOk()) { printf( "TEST-UNEXPECTED | TestCrossProcessWin | " "A constant was located in a writable section."); @@ -220,12 +232,28 @@ int wmain(int argc, wchar_t* argv[]) { return 1; } - writeResult = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD)); - if (writeResult.isErr()) { - PrintLauncherError(writeResult, "ChildProcess::WriteData(PID) failed"); + result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD)); + if (result.isErr()) { + PrintLauncherError(result, "ChildProcess::WriteData(PID) failed"); return 1; } + { + // Define a scope for |sharedSection| to resume the child process after + // the section is deleted in the parent process. + result = gSharedSection.Init(transferMgr.LocalPEHeaders()); + if (result.isErr()) { + PrintLauncherError(result, "SharedSection::Init failed"); + return 1; + } + + result = gSharedSection.TransferHandle(transferMgr); + if (result.isErr()) { + PrintLauncherError(result, "SharedSection::TransferHandle failed"); + return 1; + } + } + if (!childProcess.ResumeAndWaitUntilExit()) { return 1; } diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h index ccaab3ada16f..43d7e2eaae76 100644 --- a/mozglue/misc/NativeNt.h +++ b/mozglue/misc/NativeNt.h @@ -655,10 +655,8 @@ class MOZ_RAII PEHeaders final { return nullptr; } -#if defined(MOZILLA_INTERNAL_API) - nsTHashtable GenerateDependentModuleSet() { - nsTHashtable dependentModuleSet; - + template + void EnumImportChunks(const CallbackT& aCallback) const { for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory(); IsValid(curImpDesc); ++curImpDesc) { auto curName = mIsImportDirectoryTampered @@ -668,9 +666,17 @@ class MOZ_RAII PEHeaders final { continue; } - dependentModuleSet.PutEntry(GetLeafName(NS_ConvertASCIItoUTF16(curName))); + aCallback(curName); } + } +#if defined(MOZILLA_INTERNAL_API) + nsTHashtable GenerateDependentModuleSet() + const { + nsTHashtable dependentModuleSet; + EnumImportChunks([&dependentModuleSet](const char* aModule) { + dependentModuleSet.PutEntry(GetLeafName(NS_ConvertASCIItoUTF16(aModule))); + }); return dependentModuleSet; } #endif // defined(MOZILLA_INTERNAL_API) @@ -1530,6 +1536,71 @@ class RtlAllocPolicy { [[nodiscard]] bool checkSimulatedOOM() const { return true; } }; +class AutoMappedView final { + void* mView; + + void Unmap() { + if (!mView) { + return; + } + +#if defined(MOZILLA_INTERNAL_API) + ::UnmapViewOfFile(mView); +#else + NTSTATUS status = ::NtUnmapViewOfSection(nt::kCurrentProcess, mView); + if (!NT_SUCCESS(status)) { + ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status)); + } +#endif + mView = nullptr; + } + + public: + explicit AutoMappedView(void* aView) : mView(aView) {} + + AutoMappedView(HANDLE aSection, ULONG aProtectionFlags) : mView(nullptr) { +#if defined(MOZILLA_INTERNAL_API) + mView = ::MapViewOfFile(aSection, aProtectionFlags, 0, 0, 0); +#else + SIZE_T viewSize = 0; + NTSTATUS status = ::NtMapViewOfSection(aSection, nt::kCurrentProcess, + &mView, 0, 0, nullptr, &viewSize, + ViewUnmap, 0, aProtectionFlags); + if (!NT_SUCCESS(status)) { + ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status)); + } +#endif + } + ~AutoMappedView() { Unmap(); } + + // Allow move & Disallow copy + AutoMappedView(AutoMappedView&& aOther) : mView(aOther.mView) { + aOther.mView = nullptr; + } + AutoMappedView& operator=(AutoMappedView&& aOther) { + if (this != &aOther) { + Unmap(); + mView = aOther.mView; + aOther.mView = nullptr; + } + return *this; + } + AutoMappedView(const AutoMappedView&) = delete; + AutoMappedView& operator=(const AutoMappedView&) = delete; + + explicit operator bool() const { return !!mView; } + template + T* as() { + return reinterpret_cast(mView); + } + + void* release() { + void* p = mView; + mView = nullptr; + return p; + } +}; + } // namespace nt } // namespace mozilla