// 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 #include #include "base/files/file.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.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; #if !BUILDFLAG(IS_NACL) bool CheckFDAccessMode(int fd, int expected_mode) { int fd_status = fcntl(fd, F_GETFL); if (fd_status == -1) { // TODO(crbug.com/838365): convert to DLOG when bug fixed. PLOG(ERROR) << "fcntl(" << fd << ", F_GETFL) failed"; return false; } int mode = fd_status & O_ACCMODE; if (mode != expected_mode) { // TODO(crbug.com/838365): convert to DLOG when bug fixed. LOG(ERROR) << "Descriptor access mode (" << mode << ") differs from expected (" << expected_mode << ")"; return false; } return true; } #endif // !BUILDFLAG(IS_NACL) } // namespace #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // static ScopedFD PlatformSharedMemoryRegion::ExecutableRegion::CreateFD(size_t size) { PlatformSharedMemoryRegion region = Create(Mode::kUnsafe, size, true /* executable */); if (region.IsValid()) return region.PassPlatformHandle().fd; return ScopedFD(); } #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) // 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(std::numeric_limits::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(mode); return {}; } return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid); } // static PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take( ScopedFD handle, Mode mode, size_t size, const UnguessableToken& guid) { CHECK_NE(mode, Mode::kWritable); return Take(ScopedFDPair(std::move(handle), ScopedFD()), mode, size, guid); } 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; } // static PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode, size_t size #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) , bool executable #endif ) { #if BUILDFLAG(IS_NACL) // Untrusted code can't create descriptors or handles. return {}; #else if (size == 0) { return {}; } if (size > static_cast(std::numeric_limits::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( #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) executable, #else false /* executable */, #endif &directory)) { return {}; } FilePath path; ScopedFD fd = CreateAndOpenFdForTemporaryFileInDir(directory, &path); File shm_file(fd.release()); if (!shm_file.IsValid()) { 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 {}; } } if (!AllocateFileRegion(&shm_file, 0, size)) { return {}; } if (readonly_fd.is_valid()) { stat_wrapper_t shm_stat; if (File::Fstat(shm_file.GetPlatformFile(), &shm_stat) != 0) { DPLOG(ERROR) << "fstat(fd) failed"; return {}; } stat_wrapper_t readonly_stat; if (File::Fstat(readonly_fd.get(), &readonly_stat) != 0) { DPLOG(ERROR) << "fstat(readonly_fd) failed"; return {}; } if (shm_stat.st_dev != readonly_stat.st_dev || shm_stat.st_ino != readonly_stat.st_ino) { LOG(ERROR) << "Writable and read-only inodes don't match; bailing"; return {}; } } return PlatformSharedMemoryRegion( {ScopedFD(shm_file.TakePlatformFile()), std::move(readonly_fd)}, mode, size, UnguessableToken::Create()); #endif // !BUILDFLAG(IS_NACL) } bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode( PlatformSharedMemoryHandle handle, Mode mode, size_t size) { #if !BUILDFLAG(IS_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) { // TODO(crbug.com/838365): convert to DLOG when bug fixed. LOG(ERROR) << "The second descriptor must be invalid"; return false; } return true; #else // fcntl(_, F_GETFL) is not implemented on NaCl. // We also cannot try to mmap() a region as writable and look at the return // status because the plugin process crashes if system mmap() fails. return true; #endif // !BUILDFLAG(IS_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