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
new file mode 100644
--- /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 <sys/stat.h>
+#endif
+
+namespace mozilla {
+
+using loader::AutoMemMap;
+
+namespace ipc {
+
+Result<Ok, nsresult>
+MemMapSnapshot::Init(size_t aSize)
+{
+ MOZ_ASSERT(!mInitialized);
+
+ MOZ_TRY(Create(aSize));
+
+ mInitialized = true;
+ return Ok();
+}
+
+Result<Ok, nsresult>
+MemMapSnapshot::Finalize(AutoMemMap& aMem)
+{
+ MOZ_ASSERT(mInitialized);
+
+ MOZ_TRY(Freeze(aMem));
+
+ mInitialized = false;
+ return Ok();
+}
+
+#if defined(XP_WIN)
+
+Result<Ok, nsresult>
+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.init(mFile.ref(), aSize, PR_PROT_READWRITE);
+}
+
+Result<Ok, nsresult>
+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.init(FileDescriptor(handle), mMem.size());
+}
+
+#elif defined(XP_UNIX)
+
+Result<Ok, nsresult>
+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<Ok, nsresult>
+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<nsIFile> 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
new file mode 100644
--- /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<Ok, nsresult> Init(size_t aSize);
+ Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap);
+
+ template<typename T = void>
+ RangedPtr<T> Get()
+ {
+ MOZ_ASSERT(mInitialized);
+ return mMem.get<T>();
+ }
+
+private:
+ Result<Ok, nsresult> Create(size_t aSize);
+ Result<Ok, nsresult> Freeze(loader::AutoMemMap& aMem);
+
+ loader::AutoMemMap mMem;
+
+ bool mInitialized = false;
+
+#ifdef XP_WIN
+ Maybe<FileDescriptor> mFile;
+#else
+ nsCString mPath;
+#endif
+};
+
+} // ipc
+} // mozilla
+
+#endif // dom_ipc_MemMapSnapshot_h
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -56,16 +56,17 @@ UNIFIED_SOURCES += [
'ColorPickerParent.cpp',
'ContentBridgeChild.cpp',
'ContentBridgeParent.cpp',
'ContentParent.cpp',
'ContentProcess.cpp',
'ContentProcessHost.cpp',
'ContentProcessManager.cpp',
'FilePickerParent.cpp',
+ 'MemMapSnapshot.cpp',
'MemoryReportRequest.cpp',
'nsIContentChild.cpp',
'nsIContentParent.cpp',
'PermissionMessageUtils.cpp',
'PreallocatedProcessManager.cpp',
'ProcessPriorityManager.cpp',
'StructuredCloneData.cpp',
'TabChild.cpp',
@@ -127,16 +128,17 @@ LOCAL_INCLUDES += [
'/dom/filesystem',
'/dom/geolocation',
'/dom/media/webspeech/synth/ipc',
'/dom/security',
'/extensions/cookie',
'/extensions/spellcheck/src',
'/gfx/2d',
'/hal/sandbox',
+ '/js/xpconnect/loader',
'/layout/base',
'/media/webrtc',
'/netwerk/base',
'/toolkit/components/printingui/ipc',
'/toolkit/crashreporter',
'/toolkit/xre',
'/uriloader/exthandler',
'/widget',
--- a/js/xpconnect/loader/AutoMemMap.cpp
+++ b/js/xpconnect/loader/AutoMemMap.cpp
@@ -14,29 +14,21 @@
namespace mozilla {
namespace loader {
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()));
return FileDescriptor(handle);
}
return FileDescriptor();
}
@@ -46,52 +38,127 @@ AutoMemMap::init(nsIFile* file, int flag
MOZ_ASSERT(!fd);
MOZ_TRY(file->OpenNSPRFileDesc(flags, mode, &fd.rwget()));
return initInternal(prot);
}
Result<Ok, nsresult>
-AutoMemMap::init(const FileDescriptor& file)
+AutoMemMap::init(const FileDescriptor& file, PRFileMapProtect prot,
+ size_t expectedSize)
{
MOZ_ASSERT(!fd);
if (!file.IsValid()) {
return Err(NS_ERROR_INVALID_ARG);
}
auto handle = file.ClonePlatformHandle();
fd = PR_ImportFile(PROsfd(handle.get()));
if (!fd) {
return Err(NS_ERROR_FAILURE);
}
Unused << handle.release();
- return initInternal();
+ return initInternal(prot, expectedSize);
}
Result<Ok, nsresult>
-AutoMemMap::initInternal(PRFileMapProtect prot)
+AutoMemMap::initInternal(PRFileMapProtect prot, size_t expectedSize)
{
MOZ_ASSERT(!fileMap);
MOZ_ASSERT(!addr);
PRFileInfo64 fileInfo;
MOZ_TRY(PR_GetOpenFileInfo64(fd.get(), &fileInfo));
if (fileInfo.size > UINT32_MAX)
return Err(NS_ERROR_INVALID_ARG);
fileMap = PR_CreateFileMap(fd, 0, prot);
if (!fileMap)
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);
return Ok();
}
+#ifdef XP_WIN
+
+Result<Ok, nsresult>
+AutoMemMap::init(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<Ok, nsresult>
+AutoMemMap::init(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
--- a/js/xpconnect/loader/AutoMemMap.h
+++ b/js/xpconnect/loader/AutoMemMap.h
@@ -29,46 +29,63 @@ class AutoMemMap
~AutoMemMap();
Result<Ok, nsresult>
init(nsIFile* file, int flags = PR_RDONLY, int mode = 0,
PRFileMapProtect prot = PR_PROT_READONLY);
Result<Ok, nsresult>
- init(const ipc::FileDescriptor& file);
+ init(const ipc::FileDescriptor& file,
+ PRFileMapProtect prot = PR_PROT_READONLY,
+ size_t expectedSize = 0);
+
+ Result<Ok, nsresult>
+ init(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_; }
template<typename T = void>
- const RangedPtr<T> get()
+ RangedPtr<T> get()
{
MOZ_ASSERT(addr);
return { static_cast<T*>(addr), size_ };
}
template<typename T = void>
const RangedPtr<T> get() const
{
MOZ_ASSERT(addr);
return { static_cast<T*>(addr), size_ };
}
size_t nonHeapSizeOfExcludingThis() { return size_; }
- FileDescriptor cloneFileDescriptor();
+ FileDescriptor cloneFileDescriptor() const;
+ FileDescriptor cloneHandle() const;
private:
- Result<Ok, nsresult> initInternal(PRFileMapProtect prot = PR_PROT_READONLY);
+ Result<Ok, nsresult> 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;
AutoMemMap(const AutoMemMap&) = delete;
void operator=(const AutoMemMap&) = delete;
};
} // namespace loader