| // 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 |