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 {
InvalidValuebase::subtle::__anoneea276e30111::ScopedPathUnlinkerTraits22 static const FilePath* InvalidValue() { return nullptr; }
23
Freebase::subtle::__anoneea276e30111::ScopedPathUnlinkerTraits24 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)
CheckFDAccessMode(int fd,int expected_mode)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
ScopedFDPair(ScopedFD in_fd,ScopedFD in_readonly_fd)63 ScopedFDPair::ScopedFDPair(ScopedFD in_fd, ScopedFD in_readonly_fd)
64 : fd(std::move(in_fd)), readonly_fd(std::move(in_readonly_fd)) {}
65
get() const66 FDPair ScopedFDPair::get() const {
67 return {fd.get(), readonly_fd.get()};
68 }
69
70 // static
Take(ScopedFDPair handle,Mode mode,size_t size,const UnguessableToken & guid)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
GetPlatformHandle() const112 FDPair PlatformSharedMemoryRegion::GetPlatformHandle() const {
113 return handle_.get();
114 }
115
IsValid() const116 bool PlatformSharedMemoryRegion::IsValid() const {
117 return handle_.fd.is_valid() &&
118 (mode_ == Mode::kWritable ? handle_.readonly_fd.is_valid() : true);
119 }
120
Duplicate() const121 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
ConvertToReadOnly()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
ConvertToUnsafe()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
MapAt(off_t offset,size_t size,void ** memory,size_t * mapped_size) const162 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
Create(Mode mode,size_t size)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
CheckPlatformHandlePermissionsCorrespondToMode(PlatformHandle handle,Mode mode,size_t size)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
PlatformSharedMemoryRegion(ScopedFDPair handle,Mode mode,size_t size,const UnguessableToken & guid)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