blob: f2c4ff6221767eab113e152e52dffd87fbf72b76 [file] [log] [blame]
// Copyright 2018 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/memory/platform_shared_memory_region.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include "base/files/file_util.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "starboard/types.h"
namespace base {
namespace subtle {
namespace {
struct ScopedPathUnlinkerTraits {
static const FilePath* InvalidValue() { return nullptr; }
static void Free(const FilePath* path) {
if (unlink(path->value().c_str()))
PLOG(WARNING) << "unlink";
}
};
// Unlinks the FilePath when the object is destroyed.
using ScopedPathUnlinker =
ScopedGeneric<const FilePath*, ScopedPathUnlinkerTraits>;
#if !defined(OS_NACL)
bool CheckFDAccessMode(int fd, int expected_mode) {
int fd_status = fcntl(fd, F_GETFL);
if (fd_status == -1) {
DPLOG(ERROR) << "fcntl(" << fd << ", F_GETFL) failed";
return false;
}
int mode = fd_status & O_ACCMODE;
if (mode != expected_mode) {
DLOG(ERROR) << "Descriptor access mode (" << mode
<< ") differs from expected (" << expected_mode << ")";
return false;
}
return true;
}
#endif // !defined(OS_NACL)
} // namespace
ScopedFDPair::ScopedFDPair() = default;
ScopedFDPair::ScopedFDPair(ScopedFDPair&&) = default;
ScopedFDPair& ScopedFDPair::operator=(ScopedFDPair&&) = default;
ScopedFDPair::~ScopedFDPair() = default;
ScopedFDPair::ScopedFDPair(ScopedFD in_fd, ScopedFD in_readonly_fd)
: fd(std::move(in_fd)), readonly_fd(std::move(in_readonly_fd)) {}
FDPair ScopedFDPair::get() const {
return {fd.get(), readonly_fd.get()};
}
// static
PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
ScopedFDPair handle,
Mode mode,
size_t size,
const UnguessableToken& guid) {
if (!handle.fd.is_valid())
return {};
if (size == 0)
return {};
if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
return {};
CHECK(
CheckPlatformHandlePermissionsCorrespondToMode(handle.get(), mode, size));
switch (mode) {
case Mode::kReadOnly:
case Mode::kUnsafe:
if (handle.readonly_fd.is_valid()) {
handle.readonly_fd.reset();
DLOG(WARNING) << "Readonly handle shouldn't be valid for a "
"non-writable memory region; closing";
}
break;
case Mode::kWritable:
if (!handle.readonly_fd.is_valid()) {
DLOG(ERROR)
<< "Readonly handle must be valid for writable memory region";
return {};
}
break;
default:
DLOG(ERROR) << "Invalid permission mode: " << static_cast<int>(mode);
return {};
}
return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid);
}
// static
PlatformSharedMemoryRegion
PlatformSharedMemoryRegion::TakeFromSharedMemoryHandle(
const SharedMemoryHandle& handle,
Mode mode) {
CHECK(mode == Mode::kReadOnly || mode == Mode::kUnsafe);
if (!handle.IsValid())
return {};
return Take(
base::subtle::ScopedFDPair(ScopedFD(handle.GetHandle()), ScopedFD()),
mode, handle.GetSize(), handle.GetGUID());
}
FDPair PlatformSharedMemoryRegion::GetPlatformHandle() const {
return handle_.get();
}
bool PlatformSharedMemoryRegion::IsValid() const {
return handle_.fd.is_valid() &&
(mode_ == Mode::kWritable ? handle_.readonly_fd.is_valid() : true);
}
PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
if (!IsValid())
return {};
CHECK_NE(mode_, Mode::kWritable)
<< "Duplicating a writable shared memory region is prohibited";
ScopedFD duped_fd(HANDLE_EINTR(dup(handle_.fd.get())));
if (!duped_fd.is_valid()) {
DPLOG(ERROR) << "dup(" << handle_.fd.get() << ") failed";
return {};
}
return PlatformSharedMemoryRegion({std::move(duped_fd), ScopedFD()}, mode_,
size_, guid_);
}
bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
if (!IsValid())
return false;
CHECK_EQ(mode_, Mode::kWritable)
<< "Only writable shared memory region can be converted to read-only";
handle_.fd.reset(handle_.readonly_fd.release());
mode_ = Mode::kReadOnly;
return true;
}
bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
if (!IsValid())
return false;
CHECK_EQ(mode_, Mode::kWritable)
<< "Only writable shared memory region can be converted to unsafe";
handle_.readonly_fd.reset();
mode_ = Mode::kUnsafe;
return true;
}
bool PlatformSharedMemoryRegion::MapAtInternal(off_t offset,
size_t size,
void** memory,
size_t* mapped_size) const {
bool write_allowed = mode_ != Mode::kReadOnly;
*memory = mmap(nullptr, size, PROT_READ | (write_allowed ? PROT_WRITE : 0),
MAP_SHARED, handle_.fd.get(), offset);
bool mmap_succeeded = *memory && *memory != MAP_FAILED;
if (!mmap_succeeded) {
DPLOG(ERROR) << "mmap " << handle_.fd.get() << " failed";
return false;
}
*mapped_size = size;
return true;
}
// static
PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
size_t size) {
#if defined(OS_NACL)
// Untrusted code can't create descriptors or handles.
return {};
#else
if (size == 0)
return {};
if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
return {};
CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
"lead to this region being non-modifiable";
// This function theoretically can block on the disk, but realistically
// the temporary files we create will just go into the buffer cache
// and be deleted before they ever make it out to disk.
ThreadRestrictions::ScopedAllowIO allow_io;
// We don't use shm_open() API in order to support the --disable-dev-shm-usage
// flag.
FilePath directory;
if (!GetShmemTempDir(false /* executable */, &directory))
return {};
ScopedFD fd;
FilePath path;
fd.reset(CreateAndOpenFdForTemporaryFileInDir(directory, &path));
if (!fd.is_valid()) {
PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
FilePath dir = path.DirName();
if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value();
if (dir.value() == "/dev/shm") {
LOG(FATAL) << "This is frequently caused by incorrect permissions on "
<< "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix.";
}
}
return {};
}
// Deleting the file prevents anyone else from mapping it in (making it
// private), and prevents the need for cleanup (once the last fd is
// closed, it is truly freed).
ScopedPathUnlinker path_unlinker(&path);
ScopedFD readonly_fd;
if (mode == Mode::kWritable) {
// Also open as readonly so that we can ConvertToReadOnly().
readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
if (!readonly_fd.is_valid()) {
DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
return {};
}
}
// Get current size.
struct stat stat = {};
if (fstat(fd.get(), &stat) != 0)
return {};
const size_t current_size = stat.st_size;
if (current_size != size) {
if (HANDLE_EINTR(ftruncate(fd.get(), size)) != 0)
return {};
}
if (readonly_fd.is_valid()) {
struct stat readonly_stat = {};
if (fstat(readonly_fd.get(), &readonly_stat))
NOTREACHED();
if (stat.st_dev != readonly_stat.st_dev ||
stat.st_ino != readonly_stat.st_ino) {
LOG(ERROR) << "Writable and read-only inodes don't match; bailing";
return {};
}
}
return PlatformSharedMemoryRegion({std::move(fd), std::move(readonly_fd)},
mode, size, UnguessableToken::Create());
#endif // !defined(OS_NACL)
}
bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
PlatformHandle handle,
Mode mode,
size_t size) {
#if !defined(OS_NACL)
if (!CheckFDAccessMode(handle.fd,
mode == Mode::kReadOnly ? O_RDONLY : O_RDWR)) {
return false;
}
if (mode == Mode::kWritable)
return CheckFDAccessMode(handle.readonly_fd, O_RDONLY);
// The second descriptor must be invalid in kReadOnly and kUnsafe modes.
if (handle.readonly_fd != -1) {
DLOG(ERROR) << "The second descriptor must be invalid";
return false;
}
return true;
#else
// fcntl(_, F_GETFL) is not implemented on NaCl.
void* temp_memory = nullptr;
temp_memory =
mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle.fd, 0);
bool mmap_succeeded = temp_memory && temp_memory != MAP_FAILED;
if (mmap_succeeded)
munmap(temp_memory, size);
bool is_read_only = !mmap_succeeded;
bool expected_read_only = mode == Mode::kReadOnly;
if (is_read_only != expected_read_only) {
DLOG(ERROR) << "Descriptor has a wrong access mode: it is"
<< (is_read_only ? " " : " not ") << "read-only but it should"
<< (expected_read_only ? " " : " not ") << "be";
return false;
}
return true;
#endif // !defined(OS_NACL)
}
PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
ScopedFDPair handle,
Mode mode,
size_t size,
const UnguessableToken& guid)
: handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {}
} // namespace subtle
} // namespace base