Bug 1454816 - Remove base::SharedMemory r=ipc-reviewers,nika
Differential Revision: https://phabricator.services.mozilla.com/D224154
This commit is contained in:
@@ -47,7 +47,6 @@ if CONFIG["TARGET_KERNEL"] == "WINNT":
|
||||
"src/base/object_watcher.cc",
|
||||
"src/base/platform_thread_win.cc",
|
||||
"src/base/process_util_win.cc",
|
||||
"src/base/shared_memory_win.cc",
|
||||
"src/base/sys_string_conversions_win.cc",
|
||||
"src/base/thread_local_win.cc",
|
||||
"src/base/time_win.cc",
|
||||
@@ -67,7 +66,6 @@ if CONFIG["TARGET_KERNEL"] != "WINNT":
|
||||
"src/base/message_pump_libevent.cc",
|
||||
"src/base/platform_thread_posix.cc",
|
||||
"src/base/process_util_posix.cc",
|
||||
"src/base/shared_memory_posix.cc",
|
||||
"src/base/string16.cc",
|
||||
"src/base/thread_local_posix.cc",
|
||||
"src/base/waitable_event_posix.cc",
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef BASE_SHARED_MEMORY_H_
|
||||
#define BASE_SHARED_MEMORY_H_
|
||||
|
||||
#if defined(XP_UNIX)
|
||||
# include <sys/types.h>
|
||||
# include <semaphore.h>
|
||||
#endif
|
||||
#include <string>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/process.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
// SharedMemoryHandle is a platform specific type which represents
|
||||
// the underlying OS handle to a shared memory segment.
|
||||
typedef mozilla::UniqueFileHandle SharedMemoryHandle;
|
||||
|
||||
// Platform abstraction for shared memory. Provides a C++ wrapper
|
||||
// around the OS primitive for a memory mapped file.
|
||||
class SharedMemory {
|
||||
public:
|
||||
// Create a new SharedMemory object.
|
||||
SharedMemory() = default;
|
||||
|
||||
// Create a new SharedMemory object from an existing, open
|
||||
// shared memory file.
|
||||
SharedMemory(SharedMemoryHandle init_handle, bool read_only)
|
||||
: SharedMemory() {
|
||||
SetHandle(std::move(init_handle), read_only);
|
||||
}
|
||||
|
||||
// Move constructor; transfers ownership.
|
||||
SharedMemory(SharedMemory&& other) = default;
|
||||
|
||||
// Destructor. Will close any open files.
|
||||
~SharedMemory();
|
||||
|
||||
// Initialize a new SharedMemory object from an existing, open
|
||||
// shared memory file.
|
||||
bool SetHandle(SharedMemoryHandle handle, bool read_only);
|
||||
|
||||
// Return true iff the given handle is valid (i.e. not the distingished
|
||||
// invalid value; NULL for a HANDLE and -1 for a file descriptor)
|
||||
static bool IsHandleValid(const SharedMemoryHandle& handle);
|
||||
|
||||
// IsHandleValid applied to this object's handle.
|
||||
bool IsValid() const { return static_cast<bool>(mapped_file_); }
|
||||
|
||||
// Return invalid handle (see comment above for exact definition).
|
||||
static SharedMemoryHandle NULLHandle();
|
||||
|
||||
// Creates a shared memory segment.
|
||||
// Returns true on success, false on failure.
|
||||
bool Create(size_t size) { return CreateInternal(size, false); }
|
||||
|
||||
// Creates a shared memory segment that supports the Freeze()
|
||||
// method; see below. (Warning: creating freezeable shared memory
|
||||
// within a sandboxed process isn't possible on some platforms.)
|
||||
// Returns true on success, false on failure.
|
||||
bool CreateFreezeable(size_t size) { return CreateInternal(size, true); }
|
||||
|
||||
// Maps the shared memory into the caller's address space.
|
||||
// Returns true on success, false otherwise. The memory address
|
||||
// is accessed via the memory() accessor.
|
||||
//
|
||||
// If the specified fixed address is not null, it is the address that the
|
||||
// shared memory must be mapped at. Returns false if the shared memory
|
||||
// could not be mapped at that address.
|
||||
bool Map(size_t bytes, void* fixed_address = nullptr);
|
||||
|
||||
// Unmaps the shared memory from the caller's address space.
|
||||
void Unmap() { memory_ = nullptr; }
|
||||
|
||||
// Get the size of the opened shared memory backing file.
|
||||
// Note: This size is only available to the creator of the
|
||||
// shared memory, and not to those that opened shared memory
|
||||
// created externally.
|
||||
// Returns 0 if not opened or unknown.
|
||||
size_t max_size() const { return max_size_; }
|
||||
|
||||
// Gets a pointer to the opened memory space if it has been
|
||||
// Mapped via Map(). Returns NULL if it is not mapped.
|
||||
void* memory() const { return memory_.get(); }
|
||||
|
||||
// Extracts the underlying file handle, returning a RAII type.
|
||||
// If `unmap_view` is true, this unmaps the memory as a side-effect (and
|
||||
// cleans up any OS-specific resources).
|
||||
mozilla::UniqueFileHandle TakeHandle(bool unmap_view = true) {
|
||||
mozilla::UniqueFileHandle handle = std::move(mapped_file_);
|
||||
Close(unmap_view);
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Creates a copy of the underlying file handle, returning a RAII type.
|
||||
// This operation may fail, in which case the returned file handle will be
|
||||
// invalid.
|
||||
mozilla::UniqueFileHandle CloneHandle();
|
||||
|
||||
// Make the shared memory object read-only, such that it cannot be
|
||||
// written even if it's sent to an untrusted process. If it was
|
||||
// mapped in this process, it will be unmapped. The object must
|
||||
// have been created with CreateFreezeable(), and must not have
|
||||
// already been shared to another process.
|
||||
//
|
||||
// (See bug 1479960 comment #0 for OS-specific implementation
|
||||
// details.)
|
||||
[[nodiscard]] bool Freeze() {
|
||||
Unmap();
|
||||
return ReadOnlyCopy(this);
|
||||
}
|
||||
|
||||
// Similar to Freeze(), but assigns the read-only capability to
|
||||
// another SharedMemory object and leaves this object's mapping in
|
||||
// place and writeable. This can be used to broadcast data to
|
||||
// several untrusted readers without copying.
|
||||
//
|
||||
// The other constraints of Freeze() still apply: this object closes
|
||||
// its handle (as if by `Close(false)`), it cannot have been
|
||||
// previously shared, and the read-only handle cannot be used to
|
||||
// write the memory even by a malicious process.
|
||||
//
|
||||
// (The out parameter can currently be the same as `this` if and
|
||||
// only if the memory was unmapped, but this is an implementation
|
||||
// detail and shouldn't be relied on; for that use case Freeze()
|
||||
// should be used instead.)
|
||||
[[nodiscard]] bool ReadOnlyCopy(SharedMemory* ro_out);
|
||||
|
||||
// Closes the open shared memory segment.
|
||||
// It is safe to call Close repeatedly.
|
||||
void Close(bool unmap_view = true);
|
||||
|
||||
// Returns a page-aligned address at which the given number of bytes could
|
||||
// probably be mapped. Returns NULL on error or if there is insufficient
|
||||
// contiguous address space to map the required number of pages.
|
||||
//
|
||||
// Note that there is no guarantee that the given address space will actually
|
||||
// be free by the time this function returns, since another thread might map
|
||||
// something there in the meantime.
|
||||
static void* FindFreeAddressSpace(size_t size);
|
||||
|
||||
#ifdef XP_UNIX
|
||||
// If named POSIX shm is being used, append the prefix (including
|
||||
// the leading '/') that would be used by a process with the given
|
||||
// pid to the given string and return true. If not, return false.
|
||||
// (This is public so that the Linux sandboxing code can use it.)
|
||||
static bool AppendPosixShmPrefix(std::string* str, pid_t pid);
|
||||
// Similar, but simply returns whether POSIX shm is in use.
|
||||
static bool UsingPosixShm();
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool CreateInternal(size_t size, bool freezeable);
|
||||
|
||||
// Unmapping shared memory requires the mapped size on Unix but not
|
||||
// Windows; this encapsulates that difference.
|
||||
struct MappingDeleter {
|
||||
#ifdef XP_UNIX
|
||||
// A default-constructed deleter must be used only with nullptr
|
||||
// (to allow default-constructing UniqueMapping). A deleter with
|
||||
// a size must be used at most once.
|
||||
size_t mapped_size_ = 0;
|
||||
explicit MappingDeleter(size_t size) : mapped_size_(size) {}
|
||||
#endif
|
||||
MappingDeleter() = default;
|
||||
void operator()(void* ptr);
|
||||
};
|
||||
using UniqueMapping = mozilla::UniquePtr<void, MappingDeleter>;
|
||||
|
||||
UniqueMapping memory_;
|
||||
size_t max_size_ = 0;
|
||||
mozilla::UniqueFileHandle mapped_file_;
|
||||
#if defined(XP_WIN)
|
||||
// If true indicates this came from an external source so needs extra checks
|
||||
// before being mapped.
|
||||
bool external_section_ = false;
|
||||
#elif !defined(ANDROID)
|
||||
mozilla::UniqueFileHandle frozen_file_;
|
||||
bool is_memfd_ = false;
|
||||
#endif
|
||||
bool read_only_ = false;
|
||||
bool freezeable_ = false;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(SharedMemory);
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_SHARED_MEMORY_H_
|
||||
@@ -1,563 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/shared_memory.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef ANDROID
|
||||
# include "mozilla/Ashmem.h"
|
||||
#endif
|
||||
|
||||
#ifdef XP_LINUX
|
||||
# include "linux_memfd_defs.h"
|
||||
#endif
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
# include "mozilla/WidgetUtilsGtk.h"
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/capsicum.h>
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_VALGRIND
|
||||
# include <valgrind/valgrind.h>
|
||||
#endif
|
||||
|
||||
#include "base/eintr_wrapper.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/string_util.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/ProfilerThreadSleep.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "prenv.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
void SharedMemory::MappingDeleter::operator()(void* ptr) {
|
||||
// Check that this isn't a default-constructed deleter. (`munmap`
|
||||
// is specified to fail with `EINVAL` if the length is 0, so this
|
||||
// assertion isn't load-bearing.)
|
||||
DCHECK(mapped_size_ != 0);
|
||||
munmap(ptr, mapped_size_);
|
||||
// Guard against multiple calls of the same deleter, which shouldn't
|
||||
// happen (but could, if `UniquePtr::reset` were used). Calling
|
||||
// `munmap` with an incorrect non-zero length would be bad.
|
||||
mapped_size_ = 0;
|
||||
}
|
||||
|
||||
SharedMemory::~SharedMemory() {
|
||||
// This is almost equal to the default destructor, except for the
|
||||
// warning message about unfrozen freezable memory.
|
||||
Close();
|
||||
}
|
||||
|
||||
bool SharedMemory::SetHandle(SharedMemoryHandle handle, bool read_only) {
|
||||
DCHECK(!mapped_file_);
|
||||
#ifndef ANDROID
|
||||
DCHECK(!frozen_file_);
|
||||
#endif
|
||||
|
||||
freezeable_ = false;
|
||||
mapped_file_ = std::move(handle);
|
||||
read_only_ = read_only;
|
||||
// is_memfd_ only matters for freezing, which isn't possible
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) {
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
SharedMemoryHandle SharedMemory::NULLHandle() { return nullptr; }
|
||||
|
||||
// static
|
||||
bool SharedMemory::UsingPosixShm() {
|
||||
// Undocumented feature of AppendPosixShmPrefix to reduce code
|
||||
// duplication: if the string pointer is null, it's ignored.
|
||||
return AppendPosixShmPrefix(nullptr, 0);
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
// Android has its own shared memory API, ashmem. It doesn't support
|
||||
// POSIX shm_open, and the memfd support (see below) also doesn't work
|
||||
// because its SELinux policy prevents the procfs operations we'd use
|
||||
// (see bug 1670277 for more details).
|
||||
|
||||
// static
|
||||
bool SharedMemory::AppendPosixShmPrefix(std::string* str, pid_t pid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SharedMemory::CreateInternal(size_t size, bool freezeable) {
|
||||
read_only_ = false;
|
||||
|
||||
DCHECK(size > 0);
|
||||
DCHECK(!mapped_file_);
|
||||
|
||||
int fd = mozilla::android::ashmem_create(nullptr, size);
|
||||
if (fd < 0) {
|
||||
CHROMIUM_LOG(WARNING) << "failed to open shm: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mapped_file_.reset(fd);
|
||||
max_size_ = size;
|
||||
freezeable_ = freezeable;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedMemory::ReadOnlyCopy(SharedMemory* ro_out) {
|
||||
DCHECK(mapped_file_);
|
||||
DCHECK(!read_only_);
|
||||
CHECK(freezeable_);
|
||||
|
||||
if (ro_out == this) {
|
||||
DCHECK(!memory_);
|
||||
}
|
||||
|
||||
if (mozilla::android::ashmem_setProt(mapped_file_.get(), PROT_READ) != 0) {
|
||||
CHROMIUM_LOG(WARNING) << "failed to set ashmem read-only: "
|
||||
<< strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::UniqueFileHandle ro_file = std::move(mapped_file_);
|
||||
|
||||
freezeable_ = false;
|
||||
ro_out->Close();
|
||||
ro_out->mapped_file_ = std::move(ro_file);
|
||||
ro_out->max_size_ = max_size_;
|
||||
ro_out->read_only_ = true;
|
||||
ro_out->freezeable_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#else // not Android
|
||||
|
||||
// memfd_create is a nonstandard interface for creating anonymous
|
||||
// shared memory accessible as a file descriptor but not tied to any
|
||||
// filesystem. It first appeared in Linux 3.17, and was adopted by
|
||||
// FreeBSD in version 13.
|
||||
|
||||
# if !defined(HAVE_MEMFD_CREATE) && defined(XP_LINUX) && \
|
||||
defined(SYS_memfd_create)
|
||||
|
||||
// Older libc versions (e.g., glibc before 2.27) don't have the
|
||||
// wrapper, but we can supply our own; see `linux_memfd_defs.h`.
|
||||
|
||||
static int memfd_create(const char* name, unsigned int flags) {
|
||||
return syscall(SYS_memfd_create, name, flags);
|
||||
}
|
||||
|
||||
# define HAVE_MEMFD_CREATE 1
|
||||
# endif
|
||||
|
||||
// memfd supports having "seals" applied to the file, to prevent
|
||||
// various types of changes (which apply to all fds referencing the
|
||||
// file). Unfortunately, we can't rely on F_SEAL_WRITE to implement
|
||||
// Freeze(); see the comments in ReadOnlyCopy() below.
|
||||
//
|
||||
// Instead, to prevent a child process from regaining write access to
|
||||
// a read-only copy, the OS must also provide a way to remove write
|
||||
// permissions at the file descriptor level. This next section
|
||||
// attempts to accomplish that.
|
||||
|
||||
# ifdef HAVE_MEMFD_CREATE
|
||||
# ifdef XP_LINUX
|
||||
# define USE_MEMFD_CREATE 1
|
||||
|
||||
// To create a read-only duplicate of an fd, we can use procfs; the
|
||||
// same operation could restore write access, but sandboxing prevents
|
||||
// child processes from accessing /proc.
|
||||
//
|
||||
// (Note: if this ever changes to not use /proc, also reconsider how
|
||||
// and if HaveMemfd should check whether this works.)
|
||||
|
||||
static int DupReadOnly(int fd) {
|
||||
std::string path = StringPrintf("/proc/self/fd/%d", fd);
|
||||
// procfs opens probably won't EINTR, but checking for it can't hurt
|
||||
return HANDLE_EINTR(open(path.c_str(), O_RDONLY | O_CLOEXEC));
|
||||
}
|
||||
|
||||
# elif defined(__FreeBSD__)
|
||||
# define USE_MEMFD_CREATE 1
|
||||
|
||||
// FreeBSD's Capsicum framework allows irrevocably restricting the
|
||||
// operations permitted on a file descriptor.
|
||||
|
||||
static int DupReadOnly(int fd) {
|
||||
int rofd = dup(fd);
|
||||
if (rofd < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cap_rights_t rights;
|
||||
cap_rights_init(&rights, CAP_FSTAT, CAP_MMAP_R);
|
||||
if (cap_rights_limit(rofd, &rights) < 0) {
|
||||
int err = errno;
|
||||
close(rofd);
|
||||
errno = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return rofd;
|
||||
}
|
||||
|
||||
# else // unhandled OS
|
||||
# warning "OS has memfd_create but no DupReadOnly implementation"
|
||||
# endif // OS selection
|
||||
# endif // HAVE_MEMFD_CREATE
|
||||
|
||||
// Runtime detection for memfd support.
|
||||
static bool HaveMemfd() {
|
||||
# ifdef USE_MEMFD_CREATE
|
||||
static const bool kHave = [] {
|
||||
mozilla::UniqueFileHandle fd(
|
||||
memfd_create("mozilla-ipc-test", MFD_CLOEXEC | MFD_ALLOW_SEALING));
|
||||
if (!fd) {
|
||||
DCHECK_EQ(errno, ENOSYS);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that DupReadOnly works; on Linux it's known to fail if:
|
||||
//
|
||||
// * SELinux assigns the memfd a type for which this process's
|
||||
// domain doesn't have "open" permission; this is always the
|
||||
// case on Android but could occur on desktop as well
|
||||
//
|
||||
// * /proc (used by the DupReadOnly implementation) isn't mounted,
|
||||
// which is a configuration that the Tor Browser project is
|
||||
// interested in as a way to reduce fingerprinting risk
|
||||
//
|
||||
// Sandboxed processes on Linux also can't use it if sandboxing
|
||||
// has already been started, but that's expected. It should be
|
||||
// safe for sandboxed child processes to use memfd even if an
|
||||
// unsandboxed process couldn't freeze them, because freezing
|
||||
// isn't allowed (or meaningful) for memory created by another
|
||||
// process.
|
||||
|
||||
if (!PR_GetEnv("MOZ_SANDBOXED")) {
|
||||
mozilla::UniqueFileHandle rofd(DupReadOnly(fd.get()));
|
||||
if (!rofd) {
|
||||
CHROMIUM_LOG(WARNING) << "read-only dup failed (" << strerror(errno)
|
||||
<< "); not using memfd";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}();
|
||||
return kHave;
|
||||
# else
|
||||
return false;
|
||||
# endif // USE_MEMFD_CREATE
|
||||
}
|
||||
|
||||
// static
|
||||
bool SharedMemory::AppendPosixShmPrefix(std::string* str, pid_t pid) {
|
||||
if (HaveMemfd()) {
|
||||
return false;
|
||||
}
|
||||
// See also UsingPosixShm().
|
||||
if (!str) {
|
||||
return true;
|
||||
}
|
||||
*str += '/';
|
||||
# ifdef MOZ_WIDGET_GTK
|
||||
// The Snap package environment doesn't provide a private /dev/shm
|
||||
// (it's used for communication with services like PulseAudio);
|
||||
// instead AppArmor is used to restrict access to it. Anything with
|
||||
// this prefix is allowed:
|
||||
if (const char* snap = mozilla::widget::GetSnapInstanceName()) {
|
||||
StringAppendF(str, "snap.%s.", snap);
|
||||
}
|
||||
# endif // XP_LINUX
|
||||
// Hopefully the "implementation defined" name length limit is long
|
||||
// enough for this.
|
||||
StringAppendF(str, "org.mozilla.ipc.%d.", static_cast<int>(pid));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedMemory::CreateInternal(size_t size, bool freezeable) {
|
||||
read_only_ = false;
|
||||
|
||||
DCHECK(size > 0);
|
||||
DCHECK(!mapped_file_);
|
||||
DCHECK(!frozen_file_);
|
||||
|
||||
mozilla::UniqueFileHandle fd;
|
||||
mozilla::UniqueFileHandle frozen_fd;
|
||||
bool is_memfd = false;
|
||||
|
||||
# ifdef USE_MEMFD_CREATE
|
||||
if (HaveMemfd()) {
|
||||
const unsigned flags = MFD_CLOEXEC | (freezeable ? MFD_ALLOW_SEALING : 0);
|
||||
fd.reset(memfd_create("mozilla-ipc", flags));
|
||||
if (!fd) {
|
||||
// In general it's too late to fall back here -- in a sandboxed
|
||||
// child process, shm_open is already blocked. And it shouldn't
|
||||
// be necessary.
|
||||
CHROMIUM_LOG(WARNING) << "failed to create memfd: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
is_memfd = true;
|
||||
if (freezeable) {
|
||||
frozen_fd.reset(DupReadOnly(fd.get()));
|
||||
if (!frozen_fd) {
|
||||
CHROMIUM_LOG(WARNING)
|
||||
<< "failed to create read-only memfd: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
if (!fd) {
|
||||
// Generic Unix: shm_open + shm_unlink
|
||||
do {
|
||||
// The names don't need to be unique, but it saves time if they
|
||||
// usually are.
|
||||
static mozilla::Atomic<size_t> sNameCounter;
|
||||
std::string name;
|
||||
CHECK(AppendPosixShmPrefix(&name, getpid()));
|
||||
StringAppendF(&name, "%zu", sNameCounter++);
|
||||
// O_EXCL means the names being predictable shouldn't be a problem.
|
||||
fd.reset(HANDLE_EINTR(
|
||||
shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)));
|
||||
if (fd) {
|
||||
if (freezeable) {
|
||||
frozen_fd.reset(HANDLE_EINTR(shm_open(name.c_str(), O_RDONLY, 0400)));
|
||||
if (!frozen_fd) {
|
||||
int open_err = errno;
|
||||
shm_unlink(name.c_str());
|
||||
DLOG(FATAL) << "failed to re-open freezeable shm: "
|
||||
<< strerror(open_err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (shm_unlink(name.c_str()) != 0) {
|
||||
// This shouldn't happen, but if it does: assume the file is
|
||||
// in fact leaked, and bail out now while it's still 0-length.
|
||||
DLOG(FATAL) << "failed to unlink shm: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (!fd && errno == EEXIST);
|
||||
}
|
||||
|
||||
if (!fd) {
|
||||
CHROMIUM_LOG(WARNING) << "failed to open shm: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::Maybe<int> fallocateError;
|
||||
# if defined(HAVE_POSIX_FALLOCATE)
|
||||
// Using posix_fallocate will ensure that there's actually space for this
|
||||
// file. Otherwise we end up with a sparse file that can give SIGBUS if we
|
||||
// run out of space while writing to it. (This doesn't apply to memfd.)
|
||||
if (!is_memfd) {
|
||||
int rv;
|
||||
// Avoid repeated interruptions of posix_fallocate by the profiler's
|
||||
// SIGPROF sampling signal. Indicating "thread sleep" here means we'll
|
||||
// get up to one interruption but not more. See bug 1658847 for more.
|
||||
// This has to be scoped outside the HANDLE_RV_EINTR retry loop.
|
||||
{
|
||||
AUTO_PROFILER_THREAD_SLEEP;
|
||||
|
||||
rv = HANDLE_RV_EINTR(
|
||||
posix_fallocate(fd.get(), 0, static_cast<off_t>(size)));
|
||||
}
|
||||
|
||||
// Some filesystems have trouble with posix_fallocate. For now, we must
|
||||
// fallback ftruncate and accept the allocation failures like we do
|
||||
// without posix_fallocate.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1618914
|
||||
if (rv != 0 && rv != EOPNOTSUPP && rv != EINVAL && rv != ENODEV) {
|
||||
CHROMIUM_LOG(WARNING)
|
||||
<< "fallocate failed to set shm size: " << strerror(rv);
|
||||
return false;
|
||||
}
|
||||
fallocateError = mozilla::Some(rv);
|
||||
}
|
||||
# endif
|
||||
|
||||
// If posix_fallocate isn't supported / relevant for this type of
|
||||
// file (either failed with an expected error, or wasn't attempted),
|
||||
// then set the size with ftruncate:
|
||||
if (fallocateError != mozilla::Some(0)) {
|
||||
int rv = HANDLE_EINTR(ftruncate(fd.get(), static_cast<off_t>(size)));
|
||||
if (rv != 0) {
|
||||
int ftruncate_errno = errno;
|
||||
if (fallocateError) {
|
||||
CHROMIUM_LOG(WARNING) << "fallocate failed to set shm size: "
|
||||
<< strerror(*fallocateError);
|
||||
}
|
||||
CHROMIUM_LOG(WARNING)
|
||||
<< "ftruncate failed to set shm size: " << strerror(ftruncate_errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mapped_file_ = std::move(fd);
|
||||
frozen_file_ = std::move(frozen_fd);
|
||||
max_size_ = size;
|
||||
freezeable_ = freezeable;
|
||||
is_memfd_ = is_memfd;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedMemory::ReadOnlyCopy(SharedMemory* ro_out) {
|
||||
DCHECK(mapped_file_);
|
||||
DCHECK(!read_only_);
|
||||
CHECK(freezeable_);
|
||||
|
||||
if (ro_out == this) {
|
||||
DCHECK(!memory_);
|
||||
}
|
||||
|
||||
# ifdef USE_MEMFD_CREATE
|
||||
# ifdef MOZ_VALGRIND
|
||||
// Valgrind allows memfd_create but doesn't understand F_ADD_SEALS.
|
||||
static const bool haveSeals = RUNNING_ON_VALGRIND == 0;
|
||||
# else
|
||||
static const bool haveSeals = true;
|
||||
# endif
|
||||
static const bool useSeals = !PR_GetEnv("MOZ_SHM_NO_SEALS");
|
||||
if (is_memfd_ && haveSeals && useSeals) {
|
||||
// Seals are added to the file as defense-in-depth. The primary
|
||||
// method of access control is creating a read-only fd (using
|
||||
// procfs in this case) and requiring that sandboxes processes not
|
||||
// have access to /proc/self/fd to regain write permission; this
|
||||
// is the same as with shm_open.
|
||||
//
|
||||
// Unfortunately, F_SEAL_WRITE is unreliable: if the process
|
||||
// forked while there was a writeable mapping, it will inherit a
|
||||
// copy of the mapping, which causes the seal to fail.
|
||||
//
|
||||
// (Also, in the future we may want to split this into separate
|
||||
// classes for mappings and shared memory handles, which would
|
||||
// complicate identifying the case where `F_SEAL_WRITE` would be
|
||||
// possible even in the absence of races with fork.)
|
||||
//
|
||||
// However, Linux 5.1 added F_SEAL_FUTURE_WRITE, which prevents
|
||||
// write operations afterwards, but existing writeable mappings
|
||||
// are unaffected (similar to ashmem protection semantics).
|
||||
|
||||
const int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
|
||||
int sealError = EINVAL;
|
||||
|
||||
# ifdef F_SEAL_FUTURE_WRITE
|
||||
sealError =
|
||||
fcntl(mapped_file_.get(), F_ADD_SEALS, seals | F_SEAL_FUTURE_WRITE) == 0
|
||||
? 0
|
||||
: errno;
|
||||
# endif // F_SEAL_FUTURE_WRITE
|
||||
if (sealError == EINVAL) {
|
||||
sealError =
|
||||
fcntl(mapped_file_.get(), F_ADD_SEALS, seals) == 0 ? 0 : errno;
|
||||
}
|
||||
if (sealError != 0) {
|
||||
CHROMIUM_LOG(WARNING) << "failed to seal memfd: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
# else // !USE_MEMFD_CREATE
|
||||
DCHECK(!is_memfd_);
|
||||
# endif
|
||||
|
||||
DCHECK(frozen_file_);
|
||||
DCHECK(mapped_file_);
|
||||
mapped_file_ = nullptr;
|
||||
mozilla::UniqueFileHandle ro_file = std::move(frozen_file_);
|
||||
|
||||
DCHECK(ro_file);
|
||||
freezeable_ = false;
|
||||
ro_out->Close();
|
||||
ro_out->mapped_file_ = std::move(ro_file);
|
||||
ro_out->max_size_ = max_size_;
|
||||
ro_out->read_only_ = true;
|
||||
ro_out->freezeable_ = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // not Android
|
||||
|
||||
#ifndef MAP_NORESERVE
|
||||
# define MAP_NORESERVE 0
|
||||
#endif
|
||||
|
||||
bool SharedMemory::Map(size_t bytes, void* fixed_address) {
|
||||
if (!mapped_file_) {
|
||||
return false;
|
||||
}
|
||||
DCHECK(!memory_);
|
||||
|
||||
// Don't use MAP_FIXED when a fixed_address was specified, since that can
|
||||
// replace pages that are alread mapped at that address.
|
||||
void* mem =
|
||||
mmap(fixed_address, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
|
||||
MAP_SHARED, mapped_file_.get(), 0);
|
||||
|
||||
if (mem == MAP_FAILED) {
|
||||
CHROMIUM_LOG(WARNING) << "Call to mmap failed: " << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fixed_address && mem != fixed_address) {
|
||||
bool munmap_succeeded = munmap(mem, bytes) == 0;
|
||||
DCHECK(munmap_succeeded) << "Call to munmap failed, errno=" << errno;
|
||||
return false;
|
||||
}
|
||||
|
||||
memory_ = UniqueMapping(mem, MappingDeleter(bytes));
|
||||
return true;
|
||||
}
|
||||
|
||||
void* SharedMemory::FindFreeAddressSpace(size_t size) {
|
||||
void* memory = mmap(nullptr, size, PROT_NONE,
|
||||
MAP_ANONYMOUS | MAP_NORESERVE | MAP_PRIVATE, -1, 0);
|
||||
if (memory == MAP_FAILED) {
|
||||
return nullptr;
|
||||
}
|
||||
munmap(memory, size);
|
||||
return memory;
|
||||
}
|
||||
|
||||
SharedMemoryHandle SharedMemory::CloneHandle() {
|
||||
freezeable_ = false;
|
||||
const int new_fd = dup(mapped_file_.get());
|
||||
if (new_fd < 0) {
|
||||
CHROMIUM_LOG(WARNING) << "failed to duplicate file descriptor: "
|
||||
<< strerror(errno);
|
||||
return nullptr;
|
||||
}
|
||||
return mozilla::UniqueFileHandle(new_fd);
|
||||
}
|
||||
void SharedMemory::Close(bool unmap_view) {
|
||||
if (unmap_view) {
|
||||
Unmap();
|
||||
}
|
||||
|
||||
mapped_file_ = nullptr;
|
||||
#ifndef ANDROID
|
||||
if (frozen_file_) {
|
||||
CHROMIUM_LOG(WARNING) << "freezeable shared memory was never frozen";
|
||||
frozen_file_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
@@ -1,266 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/shared_memory.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/win_util.h"
|
||||
#include "base/string_util.h"
|
||||
#include "mozilla/ipc/ProtocolUtils.h"
|
||||
#include "mozilla/RandomNum.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsString.h"
|
||||
#ifdef MOZ_MEMORY
|
||||
# include "mozmemory_utils.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
// NtQuerySection is an internal (but believed to be stable) API and the
|
||||
// structures it uses are defined in nt_internals.h.
|
||||
// So we have to define them ourselves.
|
||||
typedef enum _SECTION_INFORMATION_CLASS {
|
||||
SectionBasicInformation,
|
||||
} SECTION_INFORMATION_CLASS;
|
||||
|
||||
typedef struct _SECTION_BASIC_INFORMATION {
|
||||
PVOID BaseAddress;
|
||||
ULONG Attributes;
|
||||
LARGE_INTEGER Size;
|
||||
} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION;
|
||||
|
||||
typedef ULONG(__stdcall* NtQuerySectionType)(
|
||||
HANDLE SectionHandle, SECTION_INFORMATION_CLASS SectionInformationClass,
|
||||
PVOID SectionInformation, ULONG SectionInformationLength,
|
||||
PULONG ResultLength);
|
||||
|
||||
// Checks if the section object is safe to map. At the moment this just means
|
||||
// it's not an image section.
|
||||
bool IsSectionSafeToMap(HANDLE handle) {
|
||||
static NtQuerySectionType nt_query_section_func =
|
||||
reinterpret_cast<NtQuerySectionType>(
|
||||
::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), "NtQuerySection"));
|
||||
DCHECK(nt_query_section_func);
|
||||
|
||||
// The handle must have SECTION_QUERY access for this to succeed.
|
||||
SECTION_BASIC_INFORMATION basic_information = {};
|
||||
ULONG status =
|
||||
nt_query_section_func(handle, SectionBasicInformation, &basic_information,
|
||||
sizeof(basic_information), nullptr);
|
||||
if (status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (basic_information.Attributes & SEC_IMAGE) != SEC_IMAGE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
void SharedMemory::MappingDeleter::operator()(void* ptr) {
|
||||
UnmapViewOfFile(ptr);
|
||||
}
|
||||
|
||||
SharedMemory::~SharedMemory() = default;
|
||||
|
||||
bool SharedMemory::SetHandle(SharedMemoryHandle handle, bool read_only) {
|
||||
DCHECK(!mapped_file_);
|
||||
|
||||
external_section_ = true;
|
||||
freezeable_ = false; // just in case
|
||||
mapped_file_ = std::move(handle);
|
||||
read_only_ = read_only;
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) {
|
||||
return handle != nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
SharedMemoryHandle SharedMemory::NULLHandle() { return nullptr; }
|
||||
|
||||
// Wrapper around CreateFileMappingW for pagefile-backed regions. When out of
|
||||
// memory, may attempt to stall and retry rather than returning immediately, in
|
||||
// hopes that the page file is about to be expanded by Windows. (bug 1822383,
|
||||
// bug 1716727)
|
||||
//
|
||||
// This method is largely a copy of the MozVirtualAlloc method from
|
||||
// mozjemalloc.cpp, which implements this strategy for VirtualAlloc calls,
|
||||
// except re-purposed to handle CreateFileMapping.
|
||||
static HANDLE MozCreateFileMappingW(
|
||||
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect,
|
||||
DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCWSTR lpName) {
|
||||
#ifdef MOZ_MEMORY
|
||||
constexpr auto IsOOMError = [] {
|
||||
return ::GetLastError() == ERROR_COMMITMENT_LIMIT;
|
||||
};
|
||||
|
||||
{
|
||||
HANDLE handle = ::CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE, lpFileMappingAttributes, flProtect,
|
||||
dwMaximumSizeHigh, dwMaximumSizeLow, lpName);
|
||||
if (MOZ_LIKELY(handle)) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(handle != INVALID_HANDLE_VALUE,
|
||||
"::CreateFileMapping should return NULL, not "
|
||||
"INVALID_HANDLE_VALUE, on failure");
|
||||
return handle;
|
||||
}
|
||||
|
||||
// We can't do anything for errors other than OOM.
|
||||
if (!IsOOMError()) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Retry as many times as desired (possibly zero).
|
||||
const mozilla::StallSpecs stallSpecs = mozilla::GetAllocatorStallSpecs();
|
||||
|
||||
const auto ret =
|
||||
stallSpecs.StallAndRetry(&::Sleep, [&]() -> std::optional<HANDLE> {
|
||||
HANDLE handle = ::CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE, lpFileMappingAttributes, flProtect,
|
||||
dwMaximumSizeHigh, dwMaximumSizeLow, lpName);
|
||||
|
||||
if (handle) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(handle != INVALID_HANDLE_VALUE,
|
||||
"::CreateFileMapping should return NULL, not "
|
||||
"INVALID_HANDLE_VALUE, on failure");
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Failure for some reason other than OOM.
|
||||
if (!IsOOMError()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
return ret.value_or(nullptr);
|
||||
#else
|
||||
return ::CreateFileMappingW(INVALID_HANDLE_VALUE, lpFileMappingAttributes,
|
||||
flProtect, dwMaximumSizeHigh, dwMaximumSizeLow,
|
||||
lpName);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SharedMemory::CreateInternal(size_t size, bool freezeable) {
|
||||
DCHECK(!mapped_file_);
|
||||
read_only_ = false;
|
||||
|
||||
// If the shared memory object has no DACL, any process can
|
||||
// duplicate its handles with any access rights; e.g., re-add write
|
||||
// access to a read-only handle. To prevent that, we give it an
|
||||
// empty DACL, so that no process can do that.
|
||||
SECURITY_ATTRIBUTES sa, *psa = nullptr;
|
||||
SECURITY_DESCRIPTOR sd;
|
||||
ACL dacl;
|
||||
|
||||
if (freezeable) {
|
||||
psa = &sa;
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = &sd;
|
||||
sa.bInheritHandle = FALSE;
|
||||
|
||||
if (NS_WARN_IF(!InitializeAcl(&dacl, sizeof(dacl), ACL_REVISION)) ||
|
||||
NS_WARN_IF(
|
||||
!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) ||
|
||||
NS_WARN_IF(!SetSecurityDescriptorDacl(&sd, TRUE, &dacl, FALSE))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
mapped_file_.reset(MozCreateFileMappingW(psa, PAGE_READWRITE, 0,
|
||||
static_cast<DWORD>(size), nullptr));
|
||||
if (!mapped_file_) return false;
|
||||
|
||||
max_size_ = size;
|
||||
freezeable_ = freezeable;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedMemory::ReadOnlyCopy(SharedMemory* ro_out) {
|
||||
DCHECK(!read_only_);
|
||||
CHECK(freezeable_);
|
||||
|
||||
if (ro_out == this) {
|
||||
DCHECK(!memory_);
|
||||
}
|
||||
|
||||
HANDLE ro_handle;
|
||||
if (!::DuplicateHandle(GetCurrentProcess(), mapped_file_.release(),
|
||||
GetCurrentProcess(), &ro_handle,
|
||||
GENERIC_READ | FILE_MAP_READ, false,
|
||||
DUPLICATE_CLOSE_SOURCE)) {
|
||||
// DUPLICATE_CLOSE_SOURCE applies even if there is an error.
|
||||
return false;
|
||||
}
|
||||
|
||||
freezeable_ = false;
|
||||
|
||||
ro_out->Close();
|
||||
ro_out->mapped_file_.reset(ro_handle);
|
||||
ro_out->max_size_ = max_size_;
|
||||
ro_out->read_only_ = true;
|
||||
ro_out->freezeable_ = false;
|
||||
ro_out->external_section_ = external_section_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SharedMemory::Map(size_t bytes, void* fixed_address) {
|
||||
if (!mapped_file_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (external_section_ && !IsSectionSafeToMap(mapped_file_.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void* mem = MapViewOfFileEx(
|
||||
mapped_file_.get(),
|
||||
read_only_ ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, bytes,
|
||||
fixed_address);
|
||||
if (mem) {
|
||||
MOZ_ASSERT(!fixed_address || mem == fixed_address,
|
||||
"MapViewOfFileEx returned an expected address");
|
||||
memory_.reset(mem);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void* SharedMemory::FindFreeAddressSpace(size_t size) {
|
||||
void* memory = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS);
|
||||
if (memory) {
|
||||
VirtualFree(memory, 0, MEM_RELEASE);
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
|
||||
SharedMemoryHandle SharedMemory::CloneHandle() {
|
||||
freezeable_ = false;
|
||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||
if (DuplicateHandle(GetCurrentProcess(), mapped_file_.get(),
|
||||
GetCurrentProcess(), &handle, 0, false,
|
||||
DUPLICATE_SAME_ACCESS)) {
|
||||
return SharedMemoryHandle(handle);
|
||||
}
|
||||
NS_WARNING("DuplicateHandle Failed!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SharedMemory::Close(bool unmap_view) {
|
||||
if (unmap_view) {
|
||||
Unmap();
|
||||
}
|
||||
|
||||
mapped_file_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace base
|
||||
Reference in New Issue
Block a user