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