Bug 1463587: Part 1 - Add helper class for creating snapshots of shared memory regions. r=jld,erahm draft
authorKris Maglione <maglione.k@gmail.com>
Fri, 22 Jun 2018 20:30:23 -0700
changeset 816385 ad6f23c34836b2f61797243ef0198805706e4719
parent 816384 656e440c1fe15499057de706c3d572c14d18fae7
child 816386 dc80a950cf8df699ce688002bc0c45e3988df039
push id115826
push usermaglione.k@gmail.com
push dateWed, 11 Jul 2018 05:19:48 +0000
reviewersjld, erahm
bugs1463587
milestone63.0a1
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
dom/ipc/MemMapSnapshot.cpp
dom/ipc/MemMapSnapshot.h
dom/ipc/moz.build
js/xpconnect/loader/AutoMemMap.cpp
js/xpconnect/loader/AutoMemMap.h
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