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
This commit is contained in:
Kris Maglione 2018-06-22 20:30:23 -07:00
parent 5b6cf04d31
commit fd093e7cca
5 changed files with 301 additions and 18 deletions

129
dom/ipc/MemMapSnapshot.cpp Normal file
View File

@ -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.initWithHandle(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.initWithHandle(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

64
dom/ipc/MemMapSnapshot.h Normal file
View File

@ -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

View File

@ -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',

View File

@ -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<Ok, nsresult>
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<Ok, nsresult>
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<Ok, nsresult>
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<Ok, nsresult>
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

View File

@ -34,14 +34,26 @@ class AutoMemMap
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);
// 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<Ok, nsresult>
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<typename T = void>
const RangedPtr<T> get()
RangedPtr<T> get()
{
MOZ_ASSERT(addr);
return { static_cast<T*>(addr), size_ };
@ -56,14 +68,23 @@ class AutoMemMap
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;