summaryrefslogtreecommitdiffstats
path: root/ipc/chromium/src/base/shared_memory_posix.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /ipc/chromium/src/base/shared_memory_posix.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ipc/chromium/src/base/shared_memory_posix.cc')
-rw-r--r--ipc/chromium/src/base/shared_memory_posix.cc563
1 files changed, 563 insertions, 0 deletions
diff --git a/ipc/chromium/src/base/shared_memory_posix.cc b/ipc/chromium/src/base/shared_memory_posix.cc
new file mode 100644
index 0000000000..bfe20e343a
--- /dev/null
+++ b/ipc/chromium/src/base/shared_memory_posix.cc
@@ -0,0 +1,563 @@
+/* -*- 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