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 <stddef.h>
10 #include <sys/mman.h>
11 #include <sys/stat.h>
12 #include <unistd.h>
13
14 #include "base/files/file_util.h"
15 #include "base/files/scoped_file.h"
16 #include "base/logging.h"
17 #include "base/macros.h"
18 #include "base/memory/shared_memory_helper.h"
19 // Unsupported in libchrome.
20 // #include "base/memory/shared_memory_tracker.h"
21 #include "base/posix/eintr_wrapper.h"
22 #include "base/posix/safe_strerror.h"
23 #include "base/process/process_metrics.h"
24 #include "base/scoped_generic.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "base/threading/thread_restrictions.h"
27 #include "base/trace_event/trace_event.h"
28 #include "base/unguessable_token.h"
29 #include "build/build_config.h"
30
31 #if defined(OS_ANDROID)
32 #include "base/os_compat_android.h"
33 #endif
34 #if defined(OS_ANDROID) || defined(__ANDROID__)
35 #include "third_party/ashmem/ashmem.h"
36 #endif
37
38 #if defined(OS_MACOSX) && !defined(OS_IOS)
39 #error "MacOS uses shared_memory_mac.cc"
40 #endif
41
42 namespace base {
43
44 SharedMemory::SharedMemory() = default;
45
SharedMemory(const SharedMemoryHandle & handle,bool read_only)46 SharedMemory::SharedMemory(const SharedMemoryHandle& handle, bool read_only)
47 : shm_(handle), read_only_(read_only) {}
48
~SharedMemory()49 SharedMemory::~SharedMemory() {
50 Unmap();
51 Close();
52 }
53
54 // static
IsHandleValid(const SharedMemoryHandle & handle)55 bool SharedMemory::IsHandleValid(const SharedMemoryHandle& handle) {
56 return handle.IsValid();
57 }
58
59 // static
CloseHandle(const SharedMemoryHandle & handle)60 void SharedMemory::CloseHandle(const SharedMemoryHandle& handle) {
61 DCHECK(handle.IsValid());
62 handle.Close();
63 }
64
65 // static
GetHandleLimit()66 size_t SharedMemory::GetHandleLimit() {
67 return GetMaxFds();
68 }
69
70 // static
DuplicateHandle(const SharedMemoryHandle & handle)71 SharedMemoryHandle SharedMemory::DuplicateHandle(
72 const SharedMemoryHandle& handle) {
73 return handle.Duplicate();
74 }
75
76 // static
GetFdFromSharedMemoryHandle(const SharedMemoryHandle & handle)77 int SharedMemory::GetFdFromSharedMemoryHandle(
78 const SharedMemoryHandle& handle) {
79 return handle.GetHandle();
80 }
81
CreateAndMapAnonymous(size_t size)82 bool SharedMemory::CreateAndMapAnonymous(size_t size) {
83 return CreateAnonymous(size) && Map(size);
84 }
85
86 #if !defined(OS_ANDROID) && !defined(__ANDROID__)
87
88 // Chromium mostly only uses the unique/private shmem as specified by
89 // "name == L"". The exception is in the StatsTable.
90 // TODO(jrg): there is no way to "clean up" all unused named shmem if
91 // we restart from a crash. (That isn't a new problem, but it is a problem.)
92 // In case we want to delete it later, it may be useful to save the value
93 // of mem_filename after FilePathForMemoryName().
Create(const SharedMemoryCreateOptions & options)94 bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
95 DCHECK(!shm_.IsValid());
96 if (options.size == 0) return false;
97
98 if (options.size > static_cast<size_t>(std::numeric_limits<int>::max()))
99 return false;
100
101 // This function theoretically can block on the disk, but realistically
102 // the temporary files we create will just go into the buffer cache
103 // and be deleted before they ever make it out to disk.
104 ThreadRestrictions::ScopedAllowIO allow_io;
105
106 bool fix_size = true;
107 ScopedFD fd;
108 ScopedFD readonly_fd;
109 FilePath path;
110 if (!options.name_deprecated || options.name_deprecated->empty()) {
111 bool result =
112 CreateAnonymousSharedMemory(options, &fd, &readonly_fd, &path);
113 if (!result)
114 return false;
115 } else {
116 if (!FilePathForMemoryName(*options.name_deprecated, &path))
117 return false;
118
119 // Make sure that the file is opened without any permission
120 // to other users on the system.
121 const mode_t kOwnerOnly = S_IRUSR | S_IWUSR;
122
123 // First, try to create the file.
124 fd.reset(HANDLE_EINTR(
125 open(path.value().c_str(), O_RDWR | O_CREAT | O_EXCL, kOwnerOnly)));
126 if (!fd.is_valid() && options.open_existing_deprecated) {
127 // If this doesn't work, try and open an existing file in append mode.
128 // Opening an existing file in a world writable directory has two main
129 // security implications:
130 // - Attackers could plant a file under their control, so ownership of
131 // the file is checked below.
132 // - Attackers could plant a symbolic link so that an unexpected file
133 // is opened, so O_NOFOLLOW is passed to open().
134 #if !defined(OS_AIX)
135 fd.reset(HANDLE_EINTR(
136 open(path.value().c_str(), O_RDWR | O_APPEND | O_NOFOLLOW)));
137 #else
138 // AIX has no 64-bit support for open flags such as -
139 // O_CLOEXEC, O_NOFOLLOW and O_TTY_INIT.
140 fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDWR | O_APPEND)));
141 #endif
142 // Check that the current user owns the file.
143 // If uid != euid, then a more complex permission model is used and this
144 // API is not appropriate.
145 const uid_t real_uid = getuid();
146 const uid_t effective_uid = geteuid();
147 struct stat sb;
148 if (fd.is_valid() &&
149 (fstat(fd.get(), &sb) != 0 || sb.st_uid != real_uid ||
150 sb.st_uid != effective_uid)) {
151 LOG(ERROR) <<
152 "Invalid owner when opening existing shared memory file.";
153 close(fd.get());
154 return false;
155 }
156
157 // An existing file was opened, so its size should not be fixed.
158 fix_size = false;
159 }
160
161 if (options.share_read_only) {
162 // Also open as readonly so that we can GetReadOnlyHandle.
163 readonly_fd.reset(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
164 if (!readonly_fd.is_valid()) {
165 DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
166 close(fd.get());
167 return false;
168 }
169 }
170 }
171 if (fd.is_valid() && fix_size) {
172 // Get current size.
173 struct stat stat;
174 if (fstat(fd.get(), &stat) != 0)
175 return false;
176 const size_t current_size = stat.st_size;
177 if (current_size != options.size) {
178 if (HANDLE_EINTR(ftruncate(fd.get(), options.size)) != 0)
179 return false;
180 }
181 requested_size_ = options.size;
182 }
183 if (!fd.is_valid()) {
184 PLOG(ERROR) << "Creating shared memory in " << path.value() << " failed";
185 FilePath dir = path.DirName();
186 if (access(dir.value().c_str(), W_OK | X_OK) < 0) {
187 PLOG(ERROR) << "Unable to access(W_OK|X_OK) " << dir.value();
188 if (dir.value() == "/dev/shm") {
189 LOG(FATAL) << "This is frequently caused by incorrect permissions on "
190 << "/dev/shm. Try 'sudo chmod 1777 /dev/shm' to fix.";
191 }
192 }
193 return false;
194 }
195
196 int mapped_file = -1;
197 int readonly_mapped_file = -1;
198
199 bool result = PrepareMapFile(std::move(fd), std::move(readonly_fd),
200 &mapped_file, &readonly_mapped_file);
201 shm_ = SharedMemoryHandle(FileDescriptor(mapped_file, false), options.size,
202 UnguessableToken::Create());
203 readonly_shm_ =
204 SharedMemoryHandle(FileDescriptor(readonly_mapped_file, false),
205 options.size, shm_.GetGUID());
206 return result;
207 }
208
209 // Our current implementation of shmem is with mmap()ing of files.
210 // These files need to be deleted explicitly.
211 // In practice this call is only needed for unit tests.
Delete(const std::string & name)212 bool SharedMemory::Delete(const std::string& name) {
213 FilePath path;
214 if (!FilePathForMemoryName(name, &path))
215 return false;
216
217 if (PathExists(path))
218 return DeleteFile(path, false);
219
220 // Doesn't exist, so success.
221 return true;
222 }
223
Open(const std::string & name,bool read_only)224 bool SharedMemory::Open(const std::string& name, bool read_only) {
225 FilePath path;
226 if (!FilePathForMemoryName(name, &path))
227 return false;
228
229 read_only_ = read_only;
230
231 int mode = read_only ? O_RDONLY : O_RDWR;
232 ScopedFD fd(HANDLE_EINTR(open(path.value().c_str(), mode)));
233 ScopedFD readonly_fd(HANDLE_EINTR(open(path.value().c_str(), O_RDONLY)));
234 if (!readonly_fd.is_valid()) {
235 DPLOG(ERROR) << "open(\"" << path.value() << "\", O_RDONLY) failed";
236 return false;
237 }
238 int mapped_file = -1;
239 int readonly_mapped_file = -1;
240 bool result = PrepareMapFile(std::move(fd), std::move(readonly_fd),
241 &mapped_file, &readonly_mapped_file);
242 // This form of sharing shared memory is deprecated. https://crbug.com/345734.
243 // However, we can't get rid of it without a significant refactor because its
244 // used to communicate between two versions of the same service process, very
245 // early in the life cycle.
246 // Technically, we should also pass the GUID from the original shared memory
247 // region. We don't do that - this means that we will overcount this memory,
248 // which thankfully isn't relevant since Chrome only communicates with a
249 // single version of the service process.
250 // We pass the size |0|, which is a dummy size and wrong, but otherwise
251 // harmless.
252 shm_ = SharedMemoryHandle(FileDescriptor(mapped_file, false), 0u,
253 UnguessableToken::Create());
254 readonly_shm_ = SharedMemoryHandle(
255 FileDescriptor(readonly_mapped_file, false), 0, shm_.GetGUID());
256 return result;
257 }
258 #endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
259
MapAt(off_t offset,size_t bytes)260 bool SharedMemory::MapAt(off_t offset, size_t bytes) {
261 if (!shm_.IsValid())
262 return false;
263
264 if (bytes > static_cast<size_t>(std::numeric_limits<int>::max()))
265 return false;
266
267 if (memory_)
268 return false;
269
270 #if defined(OS_ANDROID) || defined(__ANDROID__)
271 // On Android, Map can be called with a size and offset of zero to use the
272 // ashmem-determined size.
273 if (bytes == 0) {
274 DCHECK_EQ(0, offset);
275 int ashmem_bytes = ashmem_get_size_region(shm_.GetHandle());
276 if (ashmem_bytes < 0)
277 return false;
278 bytes = ashmem_bytes;
279 }
280
281 // Sanity check. This shall catch invalid uses of the SharedMemory APIs
282 // but will not protect against direct mmap() attempts.
283 // if (shm_.IsReadOnly()) {
284 // // Use a DCHECK() to call writable mappings with read-only descriptors
285 // // in debug builds immediately. Return an error for release builds
286 // // or during unit-testing (assuming a ScopedLogAssertHandler was installed).
287 // DCHECK(read_only_)
288 // << "Trying to map a region writable with a read-only descriptor.";
289 // if (!read_only_) {
290 // return false;
291 // }
292 // if (!shm_.SetRegionReadOnly()) { // Ensure the region is read-only.
293 // return false;
294 // }
295 // }
296 #endif
297
298 memory_ = mmap(nullptr, bytes, PROT_READ | (read_only_ ? 0 : PROT_WRITE),
299 MAP_SHARED, shm_.GetHandle(), offset);
300
301 bool mmap_succeeded = memory_ && memory_ != reinterpret_cast<void*>(-1);
302 if (mmap_succeeded) {
303 mapped_size_ = bytes;
304 mapped_id_ = shm_.GetGUID();
305 DCHECK_EQ(0U,
306 reinterpret_cast<uintptr_t>(memory_) &
307 (SharedMemory::MAP_MINIMUM_ALIGNMENT - 1));
308 // Unsupported in libchrome.
309 // SharedMemoryTracker::GetInstance()->IncrementMemoryUsage(*this);
310 } else {
311 memory_ = nullptr;
312 }
313
314 return mmap_succeeded;
315 }
316
Unmap()317 bool SharedMemory::Unmap() {
318 if (!memory_)
319 return false;
320
321 // Unsupported in libchrome.
322 // SharedMemoryTracker::GetInstance()->DecrementMemoryUsage(*this);
323 munmap(memory_, mapped_size_);
324 memory_ = nullptr;
325 mapped_size_ = 0;
326 mapped_id_ = UnguessableToken();
327 return true;
328 }
329
handle() const330 SharedMemoryHandle SharedMemory::handle() const {
331 return shm_;
332 }
333
TakeHandle()334 SharedMemoryHandle SharedMemory::TakeHandle() {
335 SharedMemoryHandle handle_copy = shm_;
336 handle_copy.SetOwnershipPassesToIPC(true);
337 Unmap();
338 shm_ = SharedMemoryHandle();
339 return handle_copy;
340 }
341
342 #if !defined(OS_ANDROID) && !defined(__ANDROID__)
Close()343 void SharedMemory::Close() {
344 if (shm_.IsValid()) {
345 shm_.Close();
346 shm_ = SharedMemoryHandle();
347 }
348 if (readonly_shm_.IsValid()) {
349 readonly_shm_.Close();
350 readonly_shm_ = SharedMemoryHandle();
351 }
352 }
353
354 // For the given shmem named |mem_name|, return a filename to mmap()
355 // (and possibly create). Modifies |filename|. Return false on
356 // error, or true of we are happy.
FilePathForMemoryName(const std::string & mem_name,FilePath * path)357 bool SharedMemory::FilePathForMemoryName(const std::string& mem_name,
358 FilePath* path) {
359 // mem_name will be used for a filename; make sure it doesn't
360 // contain anything which will confuse us.
361 DCHECK_EQ(std::string::npos, mem_name.find('/'));
362 DCHECK_EQ(std::string::npos, mem_name.find('\0'));
363
364 FilePath temp_dir;
365 if (!GetShmemTempDir(false, &temp_dir))
366 return false;
367
368 #if defined(GOOGLE_CHROME_BUILD)
369 static const char kShmem[] = "com.google.Chrome.shmem.";
370 #else
371 static const char kShmem[] = "org.chromium.Chromium.shmem.";
372 #endif
373 CR_DEFINE_STATIC_LOCAL(const std::string, name_base, (kShmem));
374 *path = temp_dir.AppendASCII(name_base + mem_name);
375 return true;
376 }
377
GetReadOnlyHandle() const378 SharedMemoryHandle SharedMemory::GetReadOnlyHandle() const {
379 CHECK(readonly_shm_.IsValid());
380 return readonly_shm_.Duplicate();
381 }
382 #endif // !defined(OS_ANDROID) && !defined(__ANDROID__)
383
384 } // namespace base
385