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 <sys/mman.h>
8 
9 #include "base/bits.h"
10 #include "base/memory/shared_memory_tracker.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "base/posix/eintr_wrapper.h"
13 #include "base/process/process_metrics.h"
14 #include "third_party/ashmem/ashmem.h"
15 
16 namespace base {
17 namespace subtle {
18 
19 // For Android, we use ashmem to implement SharedMemory. ashmem_create_region
20 // will automatically pin the region. We never explicitly call pin/unpin. When
21 // all the file descriptors from different processes associated with the region
22 // are closed, the memory buffer will go away.
23 
24 namespace {
25 
GetAshmemRegionProtectionMask(int fd)26 int GetAshmemRegionProtectionMask(int fd) {
27   int prot = ashmem_get_prot_region(fd);
28   if (prot < 0) {
29     // TODO(crbug.com/838365): convert to DLOG when bug fixed.
30     PLOG(ERROR) << "ashmem_get_prot_region failed";
31     return -1;
32   }
33   return prot;
34 }
35 
36 }  // namespace
37 
38 // static
Take(ScopedFD fd,Mode mode,size_t size,const UnguessableToken & guid)39 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
40     ScopedFD fd,
41     Mode mode,
42     size_t size,
43     const UnguessableToken& guid) {
44   if (!fd.is_valid())
45     return {};
46 
47   if (size == 0)
48     return {};
49 
50   if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
51     return {};
52 
53   CHECK(CheckPlatformHandlePermissionsCorrespondToMode(fd.get(), mode, size));
54 
55   return PlatformSharedMemoryRegion(std::move(fd), mode, size, guid);
56 }
57 
GetPlatformHandle() const58 int PlatformSharedMemoryRegion::GetPlatformHandle() const {
59   return handle_.get();
60 }
61 
IsValid() const62 bool PlatformSharedMemoryRegion::IsValid() const {
63   return handle_.is_valid();
64 }
65 
Duplicate() const66 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
67   if (!IsValid())
68     return {};
69 
70   CHECK_NE(mode_, Mode::kWritable)
71       << "Duplicating a writable shared memory region is prohibited";
72 
73   ScopedFD duped_fd(HANDLE_EINTR(dup(handle_.get())));
74   if (!duped_fd.is_valid()) {
75     DPLOG(ERROR) << "dup(" << handle_.get() << ") failed";
76     return {};
77   }
78 
79   return PlatformSharedMemoryRegion(std::move(duped_fd), mode_, size_, guid_);
80 }
81 
ConvertToReadOnly()82 bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
83   if (!IsValid())
84     return false;
85 
86   CHECK_EQ(mode_, Mode::kWritable)
87       << "Only writable shared memory region can be converted to read-only";
88 
89   ScopedFD handle_copy(handle_.release());
90 
91   int prot = GetAshmemRegionProtectionMask(handle_copy.get());
92   if (prot < 0)
93     return false;
94 
95   prot &= ~PROT_WRITE;
96   int ret = ashmem_set_prot_region(handle_copy.get(), prot);
97   if (ret != 0) {
98     DPLOG(ERROR) << "ashmem_set_prot_region failed";
99     return false;
100   }
101 
102   handle_ = std::move(handle_copy);
103   mode_ = Mode::kReadOnly;
104   return true;
105 }
106 
ConvertToUnsafe()107 bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
108   if (!IsValid())
109     return false;
110 
111   CHECK_EQ(mode_, Mode::kWritable)
112       << "Only writable shared memory region can be converted to unsafe";
113 
114   mode_ = Mode::kUnsafe;
115   return true;
116 }
117 
MapAtInternal(off_t offset,size_t size,void ** memory,size_t * mapped_size) const118 bool PlatformSharedMemoryRegion::MapAtInternal(off_t offset,
119                                                size_t size,
120                                                void** memory,
121                                                size_t* mapped_size) const {
122   // IMPORTANT: Even if the mapping is readonly and the mapped data is not
123   // changing, the region must ALWAYS be mapped with MAP_SHARED, otherwise with
124   // ashmem the mapping is equivalent to a private anonymous mapping.
125   bool write_allowed = mode_ != Mode::kReadOnly;
126   *memory = mmap(nullptr, size, PROT_READ | (write_allowed ? PROT_WRITE : 0),
127                  MAP_SHARED, handle_.get(), offset);
128 
129   bool mmap_succeeded = *memory && *memory != reinterpret_cast<void*>(-1);
130   if (!mmap_succeeded) {
131     DPLOG(ERROR) << "mmap " << handle_.get() << " failed";
132     return false;
133   }
134 
135   *mapped_size = size;
136   return true;
137 }
138 
139 // static
Create(Mode mode,size_t size)140 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
141                                                               size_t size) {
142   if (size == 0) {
143     return {};
144   }
145 
146   // Align size as required by ashmem_create_region() API documentation. This
147   // operation may overflow so check that the result doesn't decrease.
148   size_t rounded_size = bits::Align(size, GetPageSize());
149   if (rounded_size < size ||
150       rounded_size > static_cast<size_t>(std::numeric_limits<int>::max())) {
151     return {};
152   }
153 
154   CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
155                                      "lead to this region being non-modifiable";
156 
157   UnguessableToken guid = UnguessableToken::Create();
158 
159   int fd = ashmem_create_region(
160       SharedMemoryTracker::GetDumpNameForTracing(guid).c_str(), rounded_size);
161   if (fd < 0) {
162     DPLOG(ERROR) << "ashmem_create_region failed";
163     return {};
164   }
165 
166   ScopedFD scoped_fd(fd);
167   int err = ashmem_set_prot_region(scoped_fd.get(), PROT_READ | PROT_WRITE);
168   if (err < 0) {
169     DPLOG(ERROR) << "ashmem_set_prot_region failed";
170     return {};
171   }
172 
173   return PlatformSharedMemoryRegion(std::move(scoped_fd), mode, size, guid);
174 }
175 
CheckPlatformHandlePermissionsCorrespondToMode(PlatformHandle handle,Mode mode,size_t size)176 bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
177     PlatformHandle handle,
178     Mode mode,
179     size_t size) {
180   int prot = GetAshmemRegionProtectionMask(handle);
181   if (prot < 0)
182     return false;
183 
184   bool is_read_only = (prot & PROT_WRITE) == 0;
185   bool expected_read_only = mode == Mode::kReadOnly;
186 
187   if (is_read_only != expected_read_only) {
188     // TODO(crbug.com/838365): convert to DLOG when bug fixed.
189     LOG(ERROR) << "Ashmem region has a wrong protection mask: it is"
190                << (is_read_only ? " " : " not ") << "read-only but it should"
191                << (expected_read_only ? " " : " not ") << "be";
192     return false;
193   }
194 
195   return true;
196 }
197 
PlatformSharedMemoryRegion(ScopedFD fd,Mode mode,size_t size,const UnguessableToken & guid)198 PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
199     ScopedFD fd,
200     Mode mode,
201     size_t size,
202     const UnguessableToken& guid)
203     : handle_(std::move(fd)), mode_(mode), size_(size), guid_(guid) {}
204 
205 }  // namespace subtle
206 }  // namespace base
207