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