Home | History | Annotate | Download | only in memory
      1 // Copyright (c) 2012 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/shared_memory.h"
      6 
      7 #include <errno.h>
      8 #include <fcntl.h>
      9 #include <sys/mman.h>
     10 #include <sys/stat.h>
     11 #include <sys/types.h>
     12 #include <unistd.h>
     13 
     14 #include "base/files/file_util.h"
     15 #include "base/files/scoped_file.h"
     16 #include "base/lazy_instance.h"
     17 #include "base/logging.h"
     18 #include "base/process/process_metrics.h"
     19 #include "base/safe_strerror_posix.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "base/synchronization/lock.h"
     22 #include "base/threading/platform_thread.h"
     23 #include "base/threading/thread_restrictions.h"
     24 
     25 #if defined(OS_MACOSX)
     26 #include "base/mac/foundation_util.h"
     27 #endif  // OS_MACOSX
     28 
     29 #if defined(OS_ANDROID)
     30 #include "base/os_compat_android.h"
     31 #include "third_party/ashmem/ashmem.h"
     32 #endif
     33 
     34 namespace base {
     35 
     36 namespace {
     37 
     38 LazyInstance<Lock>::Leaky g_thread_lock_ = LAZY_INSTANCE_INITIALIZER;
     39 
     40 }
     41 
     42 SharedMemory::SharedMemory()
     43     : mapped_file_(-1),
     44       readonly_mapped_file_(-1),
     45       inode_(0),
     46       mapped_size_(0),
     47       memory_(NULL),
     48       read_only_(false),
     49       requested_size_(0) {
     50 }
     51 
     52 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only)
     53     : mapped_file_(handle.fd),
     54       readonly_mapped_file_(-1),
     55       inode_(0),
     56       mapped_size_(0),
     57       memory_(NULL),
     58       read_only_(read_only),
     59       requested_size_(0) {
     60   struct stat st;
     61   if (fstat(handle.fd, &st) == 0) {
     62     // If fstat fails, then the file descriptor is invalid and we'll learn this
     63     // fact when Map() fails.
     64     inode_ = st.st_ino;
     65   }
     66 }
     67 
     68 SharedMemory::SharedMemory(SharedMemoryHandle handle, bool read_only,
     69                            ProcessHandle process)
     70     : mapped_file_(handle.fd),
     71       readonly_mapped_file_(-1),
     72       inode_(0),
     73       mapped_size_(0),
     74       memory_(NULL),
     75       read_only_(read_only),
     76       requested_size_(0) {
     77   // We don't handle this case yet (note the ignored parameter); let's die if
     78   // someone comes calling.
     79   NOTREACHED();
     80 }
     81 
     82 SharedMemory::~SharedMemory() {
     83   Close();
     84 }
     85 
     86 // static
     87 bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) {
     88   return handle.fd >= 0;
     89 }
     90 
     91 // static
     92 SharedMemoryHandle SharedMemory::NULLHandle() {
     93   return SharedMemoryHandle();
     94 }
     95 
     96 // static
     97 void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) {
     98   DCHECK_GE(handle.fd, 0);
     99   if (close(handle.fd) < 0)
    100     DPLOG(ERROR) << "close";
    101 }
    102 
    103 // static
    104 size_t SharedMemory::GetHandleLimit() {
    105   return base::GetMaxFds();
    106 }
    107 
    108 bool SharedMemory::CreateAndMapAnonymous(size_t size) {
    109   return CreateAnonymous(size) && Map(size);
    110 }
    111 
    112 #if !defined(OS_ANDROID)
    113 // Chromium mostly only uses the unique/private shmem as specified by
    114 // "name == L"". The exception is in the StatsTable.
    115 // TODO(jrg): there is no way to "clean up" all unused named shmem if
    116 // we restart from a crash.  (That isn't a new problem, but it is a problem.)
    117 // In case we want to delete it later, it may be useful to save the value
    118 // of mem_filename after FilePathForMemoryName().
    119 bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
    120   DCHECK_EQ(-1, mapped_file_);
    121   if (options.size == 0) return false;
    122 
    123   if (options.size > static_cast<size_t>(std::numeric_limits<int>::max()))
    124     return false;
    125 
    126   // This function theoretically can block on the disk, but realistically
    127   // the temporary files we create will just go into the buffer cache
    128   // and be deleted before they ever make it out to disk.
    129   base::ThreadRestrictions::ScopedAllowIO allow_io;
    130 
    131   ScopedFILE fp;
    132   bool fix_size = true;
    133   ScopedFD readonly_fd;
    134 
    135   FilePath path;
    136   if (options.name_deprecated == NULL || options.name_deprecated->empty()) {
    137     // It doesn't make sense to have a open-existing private piece of shmem
    138     DCHECK(!options.open_existing_deprecated);
    139     // Q: Why not use the shm_open() etc. APIs?
    140     // A: Because they're limited to 4mb on OS X.  FFFFFFFUUUUUUUUUUU
    141     FilePath directory;
    142     if (GetShmemTempDir(options.executable, &directory))
    143       fp.reset(CreateAndOpenTemporaryFileInDir(directory, &path));
    144 
    145     if (fp) {
    146       if (options.share_read_only) {
    147         // Also open as readonly so that we can ShareReadOnlyToProcess.
    148         readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
    149         if (!readonly_fd.is_valid()) {
    150           DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
    151           fp.reset();
    152           return false;
    153         }
    154       }
    155       // Deleting the file prevents anyone else from mapping it in (making it
    156       // private), and prevents the need for cleanup (once the last fd is
    157       // closed, it is truly freed).
    158       if (unlink(path.value().c_str()))
    159         PLOG(WARNING) << "unlink";
    160     }
    161   } else {
    162     if (!FilePathForMemoryName(*options.name_deprecated, &path))
    163       return false;
    164 
    165     // Make sure that the file is opened without any permission
    166     // to other users on the system.
    167     const mode_t kOwnerOnly = S_IRUSR | S_IWUSR;
    168 
    169     // First, try to create the file.
    170     int fd = HANDLE_EINTR(
    171         open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly));
    172     if (fd == -1 && options.open_existing_deprecated) {
    173       // If this doesn't work, try and open an existing file in append mode.
    174       // Opening an existing file in a world writable directory has two main
    175       // security implications:
    176       // - Attackers could plant a file under their control, so ownership of
    177       //   the file is checked below.
    178       // - Attackers could plant a symbolic link so that an unexpected file
    179       //   is opened, so O_NOFOLLOW is passed to open().
    180       fd = HANDLE_EINTR(
    181           open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW));
    182 
    183       // Check that the current user owns the file.
    184       // If uid != euid, then a more complex permission model is used and this
    185       // API is not appropriate.
    186       const uid_t real_uid = getuid();
    187       const uid_t effective_uid = geteuid();
    188       struct stat sb;
    189       if (fd >= 0 &&
    190           (fstat(fd, &sb) != 0 || sb.st_uid != real_uid ||
    191            sb.st_uid != effective_uid)) {
    192         LOG(ERROR) <<
    193             "Invalid owner when opening existing shared memory file.";
    194         close(fd);
    195         return false;
    196       }
    197 
    198       // An existing file was opened, so its size should not be fixed.
    199       fix_size = false;
    200     }
    201 
    202     if (options.share_read_only) {
    203       // Also open as readonly so that we can ShareReadOnlyToProcess.
    204       readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
    205       if (!readonly_fd.is_valid()) {
    206         DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
    207         close(fd);
    208         fd = -1;
    209         return false;
    210       }
    211     }
    212     if (fd >= 0) {
    213       // "a+" is always appropriate: if it's a new file, a+ is similar to w+.
    214       fp.reset(fdopen(fd, "a+"));
    215     }
    216   }
    217   if (fp && fix_size) {
    218     // Get current size.
    219     struct stat stat;
    220     if (fstat(fileno(fp.get()), &stat) != 0)
    221       return false;
    222     const size_t current_size = stat.st_size;
    223     if (current_size != options.size) {
    224       if (HANDLE_EINTR(ftruncate(fileno(fp.get()), options.size)) != 0)
    225         return false;
    226     }
    227     requested_size_ = options.size;
    228   }
    229   if (fp == NULL) {
    230 #if !defined(OS_MACOSX)
    231     PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
    232     FilePath dir = path.DirName();
    233     if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
    234       PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value();
    235       if (dir.value() == "/dev/shm") {
    236         LOG(FATAL) << "This is frequently caused by incorrect permissions on "
    237                    << "/dev/shm.  Try 'sudo chmod 1777 /dev/shm' to fix.";
    238       }
    239     }
    240 #else
    241     PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
    242 #endif
    243     return false;
    244   }
    245 
    246   return PrepareMapFile(fp.Pass(), readonly_fd.Pass());
    247 }
    248 
    249 // Our current implementation of shmem is with mmap()ing of files.
    250 // These files need to be deleted explicitly.
    251 // In practice this call is only needed for unit tests.
    252 bool SharedMemory::Delete(const std::string& name) {
    253   FilePath path;
    254   if (!FilePathForMemoryName(name, &path))
    255     return false;
    256 
    257   if (PathExists(path))
    258     return base::DeleteFile(path, false);
    259 
    260   // Doesn't exist, so success.
    261   return true;
    262 }
    263 
    264 bool SharedMemory::Open(const std::string& name, bool read_only) {
    265   FilePath path;
    266   if (!FilePathForMemoryName(name, &path))
    267     return false;
    268 
    269   read_only_ = read_only;
    270 
    271   const char *mode = read_only ? "r" : "r+";
    272   ScopedFILE fp(base::OpenFile(path, mode));
    273   ScopedFD readonly_fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
    274   if (!readonly_fd.is_valid()) {
    275     DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
    276     return false;
    277   }
    278   return PrepareMapFile(fp.Pass(), readonly_fd.Pass());
    279 }
    280 #endif  // !defined(OS_ANDROID)
    281 
    282 bool SharedMemory::MapAt(off_t offset, size_t bytes) {
    283   if (mapped_file_ == -1)
    284     return false;
    285 
    286   if (bytes > static_cast<size_t>(std::numeric_limits<int>::max()))
    287     return false;
    288 
    289   if (memory_)
    290     return false;
    291 
    292 #if defined(OS_ANDROID)
    293   // On Android, Map can be called with a size and offset of zero to use the
    294   // ashmem-determined size.
    295   if (bytes == 0) {
    296     DCHECK_EQ(0, offset);
    297     int ashmem_bytes = ashmem_get_size_region(mapped_file_);
    298     if (ashmem_bytes < 0)
    299       return false;
    300     bytes = ashmem_bytes;
    301   }
    302 #endif
    303 
    304   memory_ = mmap(NULL, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
    305                  MAP_SHARED, mapped_file_, offset);
    306 
    307   bool mmap_succeeded = memory_ != (void*)-1 && memory_ != NULL;
    308   if (mmap_succeeded) {
    309     mapped_size_ = bytes;
    310     DCHECK_EQ(0U, reinterpret_cast<uintptr_t>(memory_) &
    311         (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1));
    312   } else {
    313     memory_ = NULL;
    314   }
    315 
    316   return mmap_succeeded;
    317 }
    318 
    319 bool SharedMemory::Unmap() {
    320   if (memory_ == NULL)
    321     return false;
    322 
    323   munmap(memory_, mapped_size_);
    324   memory_ = NULL;
    325   mapped_size_ = 0;
    326   return true;
    327 }
    328 
    329 SharedMemoryHandle SharedMemory::handle() const {
    330   return FileDescriptor(mapped_file_, false);
    331 }
    332 
    333 void SharedMemory::Close() {
    334   Unmap();
    335 
    336   if (mapped_file_ > 0) {
    337     if (close(mapped_file_) < 0)
    338       PLOG(ERROR) << "close";
    339     mapped_file_ = -1;
    340   }
    341   if (readonly_mapped_file_ > 0) {
    342     if (close(readonly_mapped_file_) < 0)
    343       PLOG(ERROR) << "close";
    344     readonly_mapped_file_ = -1;
    345   }
    346 }
    347 
    348 void SharedMemory::LockDeprecated() {
    349   g_thread_lock_.Get().Acquire();
    350   LockOrUnlockCommon(F_LOCK);
    351 }
    352 
    353 void SharedMemory::UnlockDeprecated() {
    354   LockOrUnlockCommon(F_ULOCK);
    355   g_thread_lock_.Get().Release();
    356 }
    357 
    358 #if !defined(OS_ANDROID)
    359 bool SharedMemory::PrepareMapFile(ScopedFILE fp, ScopedFD readonly_fd) {
    360   DCHECK_EQ(-1, mapped_file_);
    361   DCHECK_EQ(-1, readonly_mapped_file_);
    362   if (fp == NULL)
    363     return false;
    364 
    365   // This function theoretically can block on the disk, but realistically
    366   // the temporary files we create will just go into the buffer cache
    367   // and be deleted before they ever make it out to disk.
    368   base::ThreadRestrictions::ScopedAllowIO allow_io;
    369 
    370   struct stat st = {};
    371   if (fstat(fileno(fp.get()), &st))
    372     NOTREACHED();
    373   if (readonly_fd.is_valid()) {
    374     struct stat readonly_st = {};
    375     if (fstat(readonly_fd.get(), &readonly_st))
    376       NOTREACHED();
    377     if (st.st_dev != readonly_st.st_dev || st.st_ino != readonly_st.st_ino) {
    378       LOG(ERROR) << "writable and read-only inodes don't match; bailing";
    379       return false;
    380     }
    381   }
    382 
    383   mapped_file_ = dup(fileno(fp.get()));
    384   if (mapped_file_ == -1) {
    385     if (errno == EMFILE) {
    386       LOG(WARNING) << "Shared memory creation failed; out of file descriptors";
    387       return false;
    388     } else {
    389       NOTREACHED() << "Call to dup failed, errno=" << errno;
    390     }
    391   }
    392   inode_ = st.st_ino;
    393   readonly_mapped_file_ = readonly_fd.release();
    394 
    395   return true;
    396 }
    397 
    398 // For the given shmem named |mem_name|, return a filename to mmap()
    399 // (and possibly create).  Modifies |filename|.  Return false on
    400 // error, or true of we are happy.
    401 bool SharedMemory::FilePathForMemoryName(const std::string& mem_name,
    402                                          FilePath* path) {
    403   // mem_name will be used for a filename; make sure it doesn't
    404   // contain anything which will confuse us.
    405   DCHECK_EQ(std::string::npos, mem_name.find('/'));
    406   DCHECK_EQ(std::string::npos, mem_name.find('\0'));
    407 
    408   FilePath temp_dir;
    409   if (!GetShmemTempDir(false, &temp_dir))
    410     return false;
    411 
    412 #if !defined(OS_MACOSX)
    413 #if defined(GOOGLE_CHROME_BUILD)
    414   std::string name_base = std::string("com.google.Chrome");
    415 #else
    416   std::string name_base = std::string("org.chromium.Chromium");
    417 #endif
    418 #else  // OS_MACOSX
    419   std::string name_base = std::string(base::mac::BaseBundleID());
    420 #endif  // OS_MACOSX
    421   *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name);
    422   return true;
    423 }
    424 #endif  // !defined(OS_ANDROID)
    425 
    426 void SharedMemory::LockOrUnlockCommon(int function) {
    427   DCHECK_GE(mapped_file_, 0);
    428   while (lockf(mapped_file_, function, 0) < 0) {
    429     if (errno == EINTR) {
    430       continue;
    431     } else if (errno == ENOLCK) {
    432       // temporary kernel resource exaustion
    433       base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(500));
    434       continue;
    435     } else {
    436       NOTREACHED() << "lockf() failed."
    437                    << " function:" << function
    438                    << " fd:" << mapped_file_
    439                    << " errno:" << errno
    440                    << " msg:" << safe_strerror(errno);
    441     }
    442   }
    443 }
    444 
    445 bool SharedMemory::ShareToProcessCommon(ProcessHandle process,
    446                                         SharedMemoryHandle* new_handle,
    447                                         bool close_self,
    448                                         ShareMode share_mode) {
    449   int handle_to_dup = -1;
    450   switch(share_mode) {
    451     case SHARE_CURRENT_MODE:
    452       handle_to_dup = mapped_file_;
    453       break;
    454     case SHARE_READONLY:
    455       // We could imagine re-opening the file from /dev/fd, but that can't make
    456       // it readonly on Mac: https://codereview.chromium.org/27265002/#msg10
    457       CHECK(readonly_mapped_file_ >= 0);
    458       handle_to_dup = readonly_mapped_file_;
    459       break;
    460   }
    461 
    462   const int new_fd = dup(handle_to_dup);
    463   if (new_fd < 0) {
    464     DPLOG(ERROR) << "dup() failed.";
    465     return false;
    466   }
    467 
    468   new_handle->fd = new_fd;
    469   new_handle->auto_close = true;
    470 
    471   if (close_self)
    472     Close();
    473 
    474   return true;
    475 }
    476 
    477 }  // namespace base
    478