/* -*- 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 #include #include #include #include #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 #endif #ifdef MOZ_VALGRIND # include #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(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 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 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(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(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