• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::__anonc4134bcc0111::ScopedPathUnlinkerTraits22   static const FilePath* InvalidValue() { return nullptr; }
23 
Freebase::subtle::__anonc4134bcc0111::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