Home | History | Annotate | Download | only in memory
      1 // Copyright 2018 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/memory/platform_shared_memory_region.h"
      6 
      7 #include <fcntl.h>
      8 #include <sys/mman.h>
      9 #include <sys/stat.h>
     10 
     11 #include "base/files/file_util.h"
     12 #include "base/numerics/checked_math.h"
     13 #include "base/threading/thread_restrictions.h"
     14 #include "build/build_config.h"
     15 
     16 namespace base {
     17 namespace subtle {
     18 
     19 namespace {
     20 
     21 struct ScopedPathUnlinkerTraits {
     22   static const FilePath* InvalidValue() { return nullptr; }
     23 
     24   static void Free(const FilePath* path) {
     25     if (unlink(path->value().c_str()))
     26       PLOG(WARNING) << "unlink";
     27   }
     28 };
     29 
     30 // Unlinks the FilePath when the object is destroyed.
     31 using ScopedPathUnlinker =
     32     ScopedGeneric<const FilePath*, ScopedPathUnlinkerTraits>;
     33 
     34 #if !defined(OS_NACL)
     35 bool CheckFDAccessMode(int fd, int expected_mode) {
     36   int fd_status = fcntl(fd, F_GETFL);
     37   if (fd_status == -1) {
     38     DPLOG(ERROR) << "fcntl(" << fd << ", F_GETFL) failed";
     39     return false;
     40   }
     41 
     42   int mode = fd_status & O_ACCMODE;
     43   if (mode != expected_mode) {
     44     DLOG(ERROR) << "Descriptor access mode (" << mode
     45                 << ") differs from expected (" << expected_mode << ")";
     46     return false;
     47   }
     48 
     49   return true;
     50 }
     51 #endif  // !defined(OS_NACL)
     52 
     53 }  // namespace
     54 
     55 ScopedFDPair::ScopedFDPair() = default;
     56 
     57 ScopedFDPair::ScopedFDPair(ScopedFDPair&&) = default;
     58 
     59 ScopedFDPair& ScopedFDPair::operator=(ScopedFDPair&&) = default;
     60 
     61 ScopedFDPair::~ScopedFDPair() = default;
     62 
     63 ScopedFDPair::ScopedFDPair(ScopedFD in_fd, ScopedFD in_readonly_fd)
     64     : fd(std::move(in_fd)), readonly_fd(std::move(in_readonly_fd)) {}
     65 
     66 FDPair ScopedFDPair::get() const {
     67   return {fd.get(), readonly_fd.get()};
     68 }
     69 
     70 // static
     71 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
     72     ScopedFDPair handle,
     73     Mode mode,
     74     size_t size,
     75     const UnguessableToken& guid) {
     76   if (!handle.fd.is_valid())
     77     return {};
     78 
     79   if (size == 0)
     80     return {};
     81 
     82   if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
     83     return {};
     84 
     85   CHECK(
     86       CheckPlatformHandlePermissionsCorrespondToMode(handle.get(), mode, size));
     87 
     88   switch (mode) {
     89     case Mode::kReadOnly:
     90     case Mode::kUnsafe:
     91       if (handle.readonly_fd.is_valid()) {
     92         handle.readonly_fd.reset();
     93         DLOG(WARNING) << "Readonly handle shouldn't be valid for a "
     94                          "non-writable memory region; closing";
     95       }
     96       break;
     97     case Mode::kWritable:
     98       if (!handle.readonly_fd.is_valid()) {
     99         DLOG(ERROR)
    100             << "Readonly handle must be valid for writable memory region";
    101         return {};
    102       }
    103       break;
    104     default:
    105       DLOG(ERROR) << "Invalid permission mode: " << static_cast<int>(mode);
    106       return {};
    107   }
    108 
    109   return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid);
    110 }
    111 
    112 FDPair PlatformSharedMemoryRegion::GetPlatformHandle() const {
    113   return handle_.get();
    114 }
    115 
    116 bool PlatformSharedMemoryRegion::IsValid() const {
    117   return handle_.fd.is_valid() &&
    118          (mode_ == Mode::kWritable ? handle_.readonly_fd.is_valid() : true);
    119 }
    120 
    121 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
    122   if (!IsValid())
    123     return {};
    124 
    125   CHECK_NE(mode_, Mode::kWritable)
    126       << "Duplicating a writable shared memory region is prohibited";
    127 
    128   ScopedFD duped_fd(HANDLE_EINTR(dup(handle_.fd.get())));
    129   if (!duped_fd.is_valid()) {
    130     DPLOG(ERROR) << "dup(" << handle_.fd.get() << ") failed";
    131     return {};
    132   }
    133 
    134   return PlatformSharedMemoryRegion({std::move(duped_fd), ScopedFD()}, mode_,
    135                                     size_, guid_);
    136 }
    137 
    138 bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
    139   if (!IsValid())
    140     return false;
    141 
    142   CHECK_EQ(mode_, Mode::kWritable)
    143       << "Only writable shared memory region can be converted to read-only";
    144 
    145   handle_.fd.reset(handle_.readonly_fd.release());
    146   mode_ = Mode::kReadOnly;
    147   return true;
    148 }
    149 
    150 bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
    151   if (!IsValid())
    152     return false;
    153 
    154   CHECK_EQ(mode_, Mode::kWritable)
    155       << "Only writable shared memory region can be converted to unsafe";
    156 
    157   handle_.readonly_fd.reset();
    158   mode_ = Mode::kUnsafe;
    159   return true;
    160 }
    161 
    162 bool PlatformSharedMemoryRegion::MapAt(off_t offset,
    163                                        size_t size,
    164                                        void** memory,
    165                                        size_t* mapped_size) const {
    166   if (!IsValid())
    167     return false;
    168 
    169   size_t end_byte;
    170   if (!CheckAdd(offset, size).AssignIfValid(&end_byte) || end_byte > size_) {
    171     return false;
    172   }
    173 
    174   bool write_allowed = mode_ != Mode::kReadOnly;
    175   *memory = mmap(nullptr, size, PROT_READ | (write_allowed ? PROT_WRITE : 0),
    176                  MAP_SHARED, handle_.fd.get(), offset);
    177 
    178   bool mmap_succeeded = *memory && *memory != MAP_FAILED;
    179   if (!mmap_succeeded) {
    180     DPLOG(ERROR) << "mmap " << handle_.fd.get() << " failed";
    181     return false;
    182   }
    183 
    184   *mapped_size = size;
    185   DCHECK_EQ(0U,
    186             reinterpret_cast<uintptr_t>(*memory) & (kMapMinimumAlignment - 1));
    187   return true;
    188 }
    189 
    190 // static
    191 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
    192                                                               size_t size) {
    193 #if defined(OS_NACL)
    194   // Untrusted code can't create descriptors or handles.
    195   return {};
    196 #else
    197   if (size == 0)
    198     return {};
    199 
    200   if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
    201     return {};
    202 
    203   CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
    204                                      "lead to this region being non-modifiable";
    205 
    206   // This function theoretically can block on the disk, but realistically
    207   // the temporary files we create will just go into the buffer cache
    208   // and be deleted before they ever make it out to disk.
    209   ThreadRestrictions::ScopedAllowIO allow_io;
    210 
    211   // We don't use shm_open() API in order to support the --disable-dev-shm-usage
    212   // flag.
    213   FilePath directory;
    214   if (!GetShmemTempDir(false /* executable */, &directory))
    215     return {};
    216 
    217   ScopedFD fd;
    218   FilePath path;
    219   fd.reset(CreateAndOpenFdForTemporaryFileInDir(directory, &path));
    220 
    221   if (!fd.is_valid()) {
    222     PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
    223     FilePath dir = path.DirName();
    224     if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
    225       PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value();
    226       if (dir.value() == "/dev/shm") {
    227         LOG(FATAL) << "This is frequently caused by incorrect permissions on "
    228                    << "/dev/shm.  Try 'sudo chmod 1777 /dev/shm' to fix.";
    229       }
    230     }
    231     return {};
    232   }
    233 
    234   // Deleting the file prevents anyone else from mapping it in (making it
    235   // private), and prevents the need for cleanup (once the last fd is
    236   // closed, it is truly freed).
    237   ScopedPathUnlinker path_unlinker(&path);
    238 
    239   ScopedFD readonly_fd;
    240   if (mode == Mode::kWritable) {
    241     // Also open as readonly so that we can ConvertToReadOnly().
    242     readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
    243     if (!readonly_fd.is_valid()) {
    244       DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
    245       return {};
    246     }
    247   }
    248 
    249   // Get current size.
    250   struct stat stat = {};
    251   if (fstat(fd.get(), &stat) != 0)
    252     return {};
    253   const size_t current_size = stat.st_size;
    254   if (current_size != size) {
    255     if (HANDLE_EINTR(ftruncate(fd.get(), size)) != 0)
    256       return {};
    257   }
    258 
    259   if (readonly_fd.is_valid()) {
    260     struct stat readonly_stat = {};
    261     if (fstat(readonly_fd.get(), &readonly_stat))
    262       NOTREACHED();
    263 
    264     if (stat.st_dev != readonly_stat.st_dev ||
    265         stat.st_ino != readonly_stat.st_ino) {
    266       LOG(ERROR) << "Writable and read-only inodes don't match; bailing";
    267       return {};
    268     }
    269   }
    270 
    271   return PlatformSharedMemoryRegion({std::move(fd), std::move(readonly_fd)},
    272                                     mode, size, UnguessableToken::Create());
    273 #endif  // !defined(OS_NACL)
    274 }
    275 
    276 bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
    277     PlatformHandle handle,
    278     Mode mode,
    279     size_t size) {
    280 #if !defined(OS_NACL)
    281   if (!CheckFDAccessMode(handle.fd,
    282                          mode == Mode::kReadOnly ? O_RDONLY : O_RDWR)) {
    283     return false;
    284   }
    285 
    286   if (mode == Mode::kWritable)
    287     return CheckFDAccessMode(handle.readonly_fd, O_RDONLY);
    288 
    289   // The second descriptor must be invalid in kReadOnly and kUnsafe modes.
    290   if (handle.readonly_fd != -1) {
    291     DLOG(ERROR) << "The second descriptor must be invalid";
    292     return false;
    293   }
    294 
    295   return true;
    296 #else
    297   // fcntl(_, F_GETFL) is not implemented on NaCl.
    298   void* temp_memory = nullptr;
    299   temp_memory =
    300       mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle.fd, 0);
    301 
    302   bool mmap_succeeded = temp_memory && temp_memory != MAP_FAILED;
    303   if (mmap_succeeded)
    304     munmap(temp_memory, size);
    305 
    306   bool is_read_only = !mmap_succeeded;
    307   bool expected_read_only = mode == Mode::kReadOnly;
    308 
    309   if (is_read_only != expected_read_only) {
    310     DLOG(ERROR) << "Descriptor has a wrong access mode: it is"
    311                 << (is_read_only ? " " : " not ") << "read-only but it should"
    312                 << (expected_read_only ? " " : " not ") << "be";
    313     return false;
    314   }
    315 
    316   return true;
    317 #endif  // !defined(OS_NACL)
    318 }
    319 
    320 PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
    321     ScopedFDPair handle,
    322     Mode mode,
    323     size_t size,
    324     const UnguessableToken& guid)
    325     : handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {}
    326 
    327 }  // namespace subtle
    328 }  // namespace base
    329