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