From fd093e7ccaa1dae4a3ec025fb307f4ace0e61b1b Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 22 Jun 2018 20:30:23 -0700 Subject: [PATCH] Bug 1463587: Part 1 - Add helper class for creating snapshots of shared memory regions. r=jld,erahm This class allows us to map a read-write shared memory region, and then safely remap it read-only, so that it can be shared with sandboxed content processes. MozReview-Commit-ID: 2PJMQgOwA4V --HG-- extra : rebase_source : c556cabfa7d379a91dc9ef7171ac0a7d7d8fb32e extra : absorb_source : e78e304ed95891c694050f79a0bb5d40d11ee884 --- dom/ipc/MemMapSnapshot.cpp | 129 +++++++++++++++++++++++++++++ dom/ipc/MemMapSnapshot.h | 64 ++++++++++++++ dom/ipc/moz.build | 2 + js/xpconnect/loader/AutoMemMap.cpp | 93 ++++++++++++++++++--- js/xpconnect/loader/AutoMemMap.h | 31 +++++-- 5 files changed, 301 insertions(+), 18 deletions(-) create mode 100644 dom/ipc/MemMapSnapshot.cpp create mode 100644 dom/ipc/MemMapSnapshot.h diff --git a/dom/ipc/MemMapSnapshot.cpp b/dom/ipc/MemMapSnapshot.cpp new file mode 100644 index 000000000000..5552f6161a38 --- /dev/null +++ b/dom/ipc/MemMapSnapshot.cpp @@ -0,0 +1,129 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MemMapSnapshot.h" + +#include "base/eintr_wrapper.h" +#include "base/file_util.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "nsIFile.h" + +#ifdef XP_UNIX +# include +#endif + +namespace mozilla { + +using loader::AutoMemMap; + +namespace ipc { + +Result +MemMapSnapshot::Init(size_t aSize) +{ + MOZ_ASSERT(!mInitialized); + + MOZ_TRY(Create(aSize)); + + mInitialized = true; + return Ok(); +} + +Result +MemMapSnapshot::Finalize(AutoMemMap& aMem) +{ + MOZ_ASSERT(mInitialized); + + MOZ_TRY(Freeze(aMem)); + + mInitialized = false; + return Ok(); +} + +#if defined(XP_WIN) + +Result +MemMapSnapshot::Create(size_t aSize) +{ + HANDLE handle = CreateFileMapping( + INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, + 0, DWORD(aSize), nullptr); + + if (!handle) { + return Err(NS_ERROR_FAILURE); + } + + mFile.emplace(handle); + return mMem.initWithHandle(mFile.ref(), aSize, PR_PROT_READWRITE); +} + +Result +MemMapSnapshot::Freeze(AutoMemMap& aMem) +{ + auto orig = mFile.ref().ClonePlatformHandle(); + mFile.reset(); + + HANDLE handle; + if (!::DuplicateHandle(GetCurrentProcess(), orig.release(), GetCurrentProcess(), + &handle, GENERIC_READ | FILE_MAP_READ, + false, DUPLICATE_CLOSE_SOURCE)) { + return Err(NS_ERROR_FAILURE); + } + + return aMem.initWithHandle(FileDescriptor(handle), mMem.size()); +} + +#elif defined(XP_UNIX) + +Result +MemMapSnapshot::Create(size_t aSize) +{ + FilePath path; + ScopedCloseFile fd(file_util::CreateAndOpenTemporaryShmemFile(&path)); + if (!fd) { + return Err(NS_ERROR_FAILURE); + } + + if (HANDLE_EINTR(ftruncate(fileno(fd), aSize)) != 0) { + return Err(NS_ERROR_FAILURE); + } + + MOZ_TRY(mMem.init(FILEToFileDescriptor(fd), PR_PROT_READWRITE)); + + mPath.Assign(path.value().data(), path.value().length()); + return Ok(); +} + +Result +MemMapSnapshot::Freeze(AutoMemMap& aMem) +{ + // Delete the shm file after we're done here, whether we succeed or not. The + // open file descriptor will keep it alive until all remaining references + // are closed, at which point it will be automatically freed. + auto cleanup = MakeScopeExit([&]() { + PR_Delete(mPath.get()); + }); + + // Make the shm file readonly. This doesn't make a difference in practice, + // since we open and share a read-only file descriptor, and then delete the + // file. But it doesn't hurt, either. + chmod(mPath.get(), 0400); + + nsCOMPtr file; + MOZ_TRY(NS_NewNativeLocalFile(mPath, /* followLinks = */ false, + getter_AddRefs(file))); + + return aMem.init(file); +} + +#else +# error "Unsupported build configuration" +#endif + +} // namespace ipc +} // namespace mozilla diff --git a/dom/ipc/MemMapSnapshot.h b/dom/ipc/MemMapSnapshot.h new file mode 100644 index 000000000000..d29e9748fafc --- /dev/null +++ b/dom/ipc/MemMapSnapshot.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_ipc_MemMapSnapshot_h +#define dom_ipc_MemMapSnapshot_h + +#include "AutoMemMap.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/Result.h" +#ifdef XP_WIN +# include "mozilla/ipc/FileDescriptor.h" +#endif + +namespace mozilla { +namespace ipc { + +/** + * A helper class for creating a read-only snapshot of memory-mapped data. + * + * The Init() method initializes a read-write memory mapped region of the given + * size, which can be initialized with arbitrary data. The Finalize() method + * remaps that region as read-only (and backs it with a read-only file + * descriptor), and initializes an AutoMemMap with the new contents. + * + * The file descriptor for the resulting AutoMemMap can be shared among + * processes, to safely access a shared, read-only copy of the data snapshot. + */ +class MOZ_RAII MemMapSnapshot +{ +public: + Result Init(size_t aSize); + Result Finalize(loader::AutoMemMap& aMap); + + template + RangedPtr Get() + { + MOZ_ASSERT(mInitialized); + return mMem.get(); + } + +private: + Result Create(size_t aSize); + Result Freeze(loader::AutoMemMap& aMem); + + loader::AutoMemMap mMem; + + bool mInitialized = false; + +#ifdef XP_WIN + Maybe mFile; +#else + nsCString mPath; +#endif +}; + +} // ipc +} // mozilla + +#endif // dom_ipc_MemMapSnapshot_h diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index 13289b363643..81f94353ce51 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -61,6 +61,7 @@ UNIFIED_SOURCES += [ 'ContentProcessHost.cpp', 'ContentProcessManager.cpp', 'FilePickerParent.cpp', + 'MemMapSnapshot.cpp', 'MemoryReportRequest.cpp', 'nsIContentChild.cpp', 'nsIContentParent.cpp', @@ -132,6 +133,7 @@ LOCAL_INCLUDES += [ '/extensions/spellcheck/src', '/gfx/2d', '/hal/sandbox', + '/js/xpconnect/loader', '/layout/base', '/media/webrtc', '/netwerk/base', diff --git a/js/xpconnect/loader/AutoMemMap.cpp b/js/xpconnect/loader/AutoMemMap.cpp index 466e2db64e29..e9a101a6ed14 100644 --- a/js/xpconnect/loader/AutoMemMap.cpp +++ b/js/xpconnect/loader/AutoMemMap.cpp @@ -19,19 +19,11 @@ using namespace mozilla::ipc; AutoMemMap::~AutoMemMap() { - if (fileMap) { - if (addr) { - Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS); - addr = nullptr; - } - - Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS); - fileMap = nullptr; - } + reset(); } FileDescriptor -AutoMemMap::cloneFileDescriptor() +AutoMemMap::cloneFileDescriptor() const { if (fd.get()) { auto handle = FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd.get())); @@ -51,7 +43,8 @@ AutoMemMap::init(nsIFile* file, int flags, int mode, PRFileMapProtect prot) } Result -AutoMemMap::init(const FileDescriptor& file) +AutoMemMap::init(const FileDescriptor& file, PRFileMapProtect prot, + size_t expectedSize) { MOZ_ASSERT(!fd); if (!file.IsValid()) { @@ -66,11 +59,11 @@ AutoMemMap::init(const FileDescriptor& file) } Unused << handle.release(); - return initInternal(); + return initInternal(prot, expectedSize); } Result -AutoMemMap::initInternal(PRFileMapProtect prot) +AutoMemMap::initInternal(PRFileMapProtect prot, size_t expectedSize) { MOZ_ASSERT(!fileMap); MOZ_ASSERT(!addr); @@ -86,6 +79,12 @@ AutoMemMap::initInternal(PRFileMapProtect prot) return Err(NS_ERROR_FAILURE); size_ = fileInfo.size; + // The memory region size passed in certain IPC messages isn't necessary on + // Unix-like systems, since we can always stat the file descriptor to + // determine it accurately. But since we have it, anyway, sanity check that + // it matches the size returned by the stat. + MOZ_ASSERT_IF(expectedSize > 0, size_ == expectedSize); + addr = PR_MemMap(fileMap, 0, size_); if (!addr) return Err(NS_ERROR_FAILURE); @@ -93,5 +92,73 @@ AutoMemMap::initInternal(PRFileMapProtect prot) return Ok(); } +#ifdef XP_WIN + +Result +AutoMemMap::initWithHandle(const FileDescriptor& file, size_t size, PRFileMapProtect prot) +{ + MOZ_ASSERT(!fd); + MOZ_ASSERT(!handle_); + if (!file.IsValid()) { + return Err(NS_ERROR_INVALID_ARG); + } + + handle_ = file.ClonePlatformHandle().release(); + + MOZ_ASSERT(!addr); + + size_ = size; + + addr = MapViewOfFile( + handle_, + prot == PR_PROT_READONLY ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, + 0, 0, size); + + return Ok(); +} + +FileDescriptor +AutoMemMap::cloneHandle() const +{ + return FileDescriptor(handle_); +} + +#else + +Result +AutoMemMap::initWithHandle(const FileDescriptor& file, size_t size, PRFileMapProtect prot) +{ + return init(file, prot); +} + +FileDescriptor +AutoMemMap::cloneHandle() const +{ + return cloneFileDescriptor(); +} + +#endif + +void +AutoMemMap::reset() +{ + if (fileMap) { + if (addr) { + Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS); + addr = nullptr; + } + + Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS); + fileMap = nullptr; + } +#ifdef XP_WIN + if (handle_) { + CloseHandle(handle_); + handle_ = nullptr; + } +#endif + fd.dispose(); +} + } // namespace loader } // namespace mozilla diff --git a/js/xpconnect/loader/AutoMemMap.h b/js/xpconnect/loader/AutoMemMap.h index 5257cfd7f948..11e07800890c 100644 --- a/js/xpconnect/loader/AutoMemMap.h +++ b/js/xpconnect/loader/AutoMemMap.h @@ -34,14 +34,26 @@ class AutoMemMap PRFileMapProtect prot = PR_PROT_READONLY); Result - init(const ipc::FileDescriptor& file); + init(const ipc::FileDescriptor& file, + PRFileMapProtect prot = PR_PROT_READONLY, + size_t expectedSize = 0); + + // Initializes the mapped memory with a shared memory handle. On + // Unix-like systems, this is identical to the above init() method. On + // Windows, the FileDescriptor must be a handle for a file mapping, + // rather than a file descriptor. + Result + initWithHandle(const ipc::FileDescriptor& file, size_t size, + PRFileMapProtect prot = PR_PROT_READONLY); + + void reset(); bool initialized() { return addr; } - uint32_t size() const { MOZ_ASSERT(fd); return size_; } + uint32_t size() const { return size_; } template - const RangedPtr get() + RangedPtr get() { MOZ_ASSERT(addr); return { static_cast(addr), size_ }; @@ -56,14 +68,23 @@ class AutoMemMap size_t nonHeapSizeOfExcludingThis() { return size_; } - FileDescriptor cloneFileDescriptor(); + FileDescriptor cloneFileDescriptor() const; + FileDescriptor cloneHandle() const; private: - Result initInternal(PRFileMapProtect prot = PR_PROT_READONLY); + Result initInternal(PRFileMapProtect prot = PR_PROT_READONLY, + size_t expectedSize = 0); AutoFDClose fd; PRFileMap* fileMap = nullptr; +#ifdef XP_WIN + // We can't include windows.h in this header, since it gets included + // by some binding headers (which are explicitly incompatible with + // windows.h). So we can't use the HANDLE type here. + void* handle_ = nullptr; +#endif + uint32_t size_ = 0; void* addr = nullptr;