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 <aclapi.h>
8 #include <stddef.h>
9 #include <stdint.h>
10
11 #include "base/allocator/partition_allocator/page_allocator.h"
12 #include "base/bits.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/process/process_handle.h"
16 #include "base/rand_util.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/win/windows_version.h"
21
22 namespace base {
23 namespace subtle {
24
25 namespace {
26
27 typedef enum _SECTION_INFORMATION_CLASS {
28 SectionBasicInformation,
29 } SECTION_INFORMATION_CLASS;
30
31 typedef struct _SECTION_BASIC_INFORMATION {
32 PVOID BaseAddress;
33 ULONG Attributes;
34 LARGE_INTEGER Size;
35 } SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION;
36
37 typedef ULONG(__stdcall* NtQuerySectionType)(
38 HANDLE SectionHandle,
39 SECTION_INFORMATION_CLASS SectionInformationClass,
40 PVOID SectionInformation,
41 ULONG SectionInformationLength,
42 PULONG ResultLength);
43
44 // Returns the length of the memory section starting at the supplied address.
GetMemorySectionSize(void * address)45 size_t GetMemorySectionSize(void* address) {
46 MEMORY_BASIC_INFORMATION memory_info;
47 if (!::VirtualQuery(address, &memory_info, sizeof(memory_info)))
48 return 0;
49 return memory_info.RegionSize -
50 (static_cast<char*>(address) -
51 static_cast<char*>(memory_info.AllocationBase));
52 }
53
54 // Checks if the section object is safe to map. At the moment this just means
55 // it's not an image section.
IsSectionSafeToMap(HANDLE handle)56 bool IsSectionSafeToMap(HANDLE handle) {
57 static NtQuerySectionType nt_query_section_func =
58 reinterpret_cast<NtQuerySectionType>(
59 ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), "NtQuerySection"));
60 DCHECK(nt_query_section_func);
61
62 // The handle must have SECTION_QUERY access for this to succeed.
63 SECTION_BASIC_INFORMATION basic_information = {};
64 ULONG status =
65 nt_query_section_func(handle, SectionBasicInformation, &basic_information,
66 sizeof(basic_information), nullptr);
67 if (status)
68 return false;
69 return (basic_information.Attributes & SEC_IMAGE) != SEC_IMAGE;
70 }
71
72 // Returns a HANDLE on success and |nullptr| on failure.
73 // This function is similar to CreateFileMapping, but removes the permissions
74 // WRITE_DAC, WRITE_OWNER, READ_CONTROL, and DELETE.
75 //
76 // A newly created file mapping has two sets of permissions. It has access
77 // control permissions (WRITE_DAC, WRITE_OWNER, READ_CONTROL, and DELETE) and
78 // file permissions (FILE_MAP_READ, FILE_MAP_WRITE, etc.). The Chrome sandbox
79 // prevents HANDLEs with the WRITE_DAC permission from being duplicated into
80 // unprivileged processes.
81 //
82 // In order to remove the access control permissions, after being created the
83 // handle is duplicated with only the file access permissions.
CreateFileMappingWithReducedPermissions(SECURITY_ATTRIBUTES * sa,size_t rounded_size,LPCWSTR name)84 HANDLE CreateFileMappingWithReducedPermissions(SECURITY_ATTRIBUTES* sa,
85 size_t rounded_size,
86 LPCWSTR name) {
87 HANDLE h = CreateFileMapping(INVALID_HANDLE_VALUE, sa, PAGE_READWRITE, 0,
88 static_cast<DWORD>(rounded_size), name);
89 if (!h) {
90 return nullptr;
91 }
92
93 HANDLE h2;
94 ProcessHandle process = GetCurrentProcess();
95 BOOL success = ::DuplicateHandle(
96 process, h, process, &h2, FILE_MAP_READ | FILE_MAP_WRITE | SECTION_QUERY,
97 FALSE, 0);
98 BOOL rv = ::CloseHandle(h);
99 DCHECK(rv);
100
101 if (!success) {
102 return nullptr;
103 }
104
105 return h2;
106 }
107
108 } // namespace
109
110 // static
Take(win::ScopedHandle handle,Mode mode,size_t size,const UnguessableToken & guid)111 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Take(
112 win::ScopedHandle handle,
113 Mode mode,
114 size_t size,
115 const UnguessableToken& guid) {
116 if (!handle.IsValid())
117 return {};
118
119 if (size == 0)
120 return {};
121
122 if (size > static_cast<size_t>(std::numeric_limits<int>::max()))
123 return {};
124
125 if (!IsSectionSafeToMap(handle.Get()))
126 return {};
127
128 CHECK(
129 CheckPlatformHandlePermissionsCorrespondToMode(handle.Get(), mode, size));
130
131 return PlatformSharedMemoryRegion(std::move(handle), mode, size, guid);
132 }
133
GetPlatformHandle() const134 HANDLE PlatformSharedMemoryRegion::GetPlatformHandle() const {
135 return handle_.Get();
136 }
137
IsValid() const138 bool PlatformSharedMemoryRegion::IsValid() const {
139 return handle_.IsValid();
140 }
141
Duplicate() const142 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Duplicate() const {
143 if (!IsValid())
144 return {};
145
146 CHECK_NE(mode_, Mode::kWritable)
147 << "Duplicating a writable shared memory region is prohibited";
148
149 HANDLE duped_handle;
150 ProcessHandle process = GetCurrentProcess();
151 BOOL success =
152 ::DuplicateHandle(process, handle_.Get(), process, &duped_handle, 0,
153 FALSE, DUPLICATE_SAME_ACCESS);
154 if (!success)
155 return {};
156
157 return PlatformSharedMemoryRegion(win::ScopedHandle(duped_handle), mode_,
158 size_, guid_);
159 }
160
ConvertToReadOnly()161 bool PlatformSharedMemoryRegion::ConvertToReadOnly() {
162 if (!IsValid())
163 return false;
164
165 CHECK_EQ(mode_, Mode::kWritable)
166 << "Only writable shared memory region can be converted to read-only";
167
168 win::ScopedHandle handle_copy(handle_.Take());
169
170 HANDLE duped_handle;
171 ProcessHandle process = GetCurrentProcess();
172 BOOL success =
173 ::DuplicateHandle(process, handle_copy.Get(), process, &duped_handle,
174 FILE_MAP_READ | SECTION_QUERY, FALSE, 0);
175 if (!success)
176 return false;
177
178 handle_.Set(duped_handle);
179 mode_ = Mode::kReadOnly;
180 return true;
181 }
182
ConvertToUnsafe()183 bool PlatformSharedMemoryRegion::ConvertToUnsafe() {
184 if (!IsValid())
185 return false;
186
187 CHECK_EQ(mode_, Mode::kWritable)
188 << "Only writable shared memory region can be converted to unsafe";
189
190 mode_ = Mode::kUnsafe;
191 return true;
192 }
193
MapAtInternal(off_t offset,size_t size,void ** memory,size_t * mapped_size) const194 bool PlatformSharedMemoryRegion::MapAtInternal(off_t offset,
195 size_t size,
196 void** memory,
197 size_t* mapped_size) const {
198 bool write_allowed = mode_ != Mode::kReadOnly;
199 // Try to map the shared memory. On the first failure, release any reserved
200 // address space for a single entry.
201 for (int i = 0; i < 2; ++i) {
202 *memory = MapViewOfFile(
203 handle_.Get(), FILE_MAP_READ | (write_allowed ? FILE_MAP_WRITE : 0),
204 static_cast<uint64_t>(offset) >> 32, static_cast<DWORD>(offset), size);
205 if (*memory)
206 break;
207 ReleaseReservation();
208 }
209 if (!*memory) {
210 DPLOG(ERROR) << "Failed executing MapViewOfFile";
211 return false;
212 }
213
214 *mapped_size = GetMemorySectionSize(*memory);
215 return true;
216 }
217
218 // static
Create(Mode mode,size_t size)219 PlatformSharedMemoryRegion PlatformSharedMemoryRegion::Create(Mode mode,
220 size_t size) {
221 // TODO(crbug.com/210609): NaCl forces us to round up 64k here, wasting 32k
222 // per mapping on average.
223 static const size_t kSectionSize = 65536;
224 if (size == 0) {
225 return {};
226 }
227
228 // Aligning may overflow so check that the result doesn't decrease.
229 size_t rounded_size = bits::Align(size, kSectionSize);
230 if (rounded_size < size ||
231 rounded_size > static_cast<size_t>(std::numeric_limits<int>::max())) {
232 return {};
233 }
234
235 CHECK_NE(mode, Mode::kReadOnly) << "Creating a region in read-only mode will "
236 "lead to this region being non-modifiable";
237
238 // Add an empty DACL to enforce anonymous read-only sections.
239 ACL dacl;
240 SECURITY_DESCRIPTOR sd;
241 if (!InitializeAcl(&dacl, sizeof(dacl), ACL_REVISION)) {
242 return {};
243 }
244 if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) {
245 return {};
246 }
247 if (!SetSecurityDescriptorDacl(&sd, TRUE, &dacl, FALSE)) {
248 return {};
249 }
250
251 string16 name;
252 if (win::GetVersion() < win::Version::WIN8_1) {
253 // Windows < 8.1 ignores DACLs on certain unnamed objects (like shared
254 // sections). So, we generate a random name when we need to enforce
255 // read-only.
256 uint64_t rand_values[4];
257 RandBytes(&rand_values, sizeof(rand_values));
258 name = ASCIIToUTF16(StringPrintf("CrSharedMem_%016llx%016llx%016llx%016llx",
259 rand_values[0], rand_values[1],
260 rand_values[2], rand_values[3]));
261 DCHECK(!name.empty());
262 }
263
264 SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, FALSE};
265 // Ask for the file mapping with reduced permisions to avoid passing the
266 // access control permissions granted by default into unpriviledged process.
267 HANDLE h = CreateFileMappingWithReducedPermissions(
268 &sa, rounded_size, name.empty() ? nullptr : as_wcstr(name));
269 if (h == nullptr) {
270 // The error is logged within CreateFileMappingWithReducedPermissions().
271 return {};
272 }
273
274 win::ScopedHandle scoped_h(h);
275 // Check if the shared memory pre-exists.
276 if (GetLastError() == ERROR_ALREADY_EXISTS) {
277 return {};
278 }
279
280 return PlatformSharedMemoryRegion(std::move(scoped_h), mode, size,
281 UnguessableToken::Create());
282 }
283
284 // static
CheckPlatformHandlePermissionsCorrespondToMode(PlatformHandle handle,Mode mode,size_t size)285 bool PlatformSharedMemoryRegion::CheckPlatformHandlePermissionsCorrespondToMode(
286 PlatformHandle handle,
287 Mode mode,
288 size_t size) {
289 // Call ::DuplicateHandle() with FILE_MAP_WRITE as a desired access to check
290 // if the |handle| has a write access.
291 ProcessHandle process = GetCurrentProcess();
292 HANDLE duped_handle;
293 BOOL success = ::DuplicateHandle(process, handle, process, &duped_handle,
294 FILE_MAP_WRITE, FALSE, 0);
295 if (success) {
296 BOOL rv = ::CloseHandle(duped_handle);
297 DCHECK(rv);
298 }
299
300 bool is_read_only = !success;
301 bool expected_read_only = mode == Mode::kReadOnly;
302
303 if (is_read_only != expected_read_only) {
304 DLOG(ERROR) << "File mapping handle has wrong access rights: it is"
305 << (is_read_only ? " " : " not ") << "read-only but it should"
306 << (expected_read_only ? " " : " not ") << "be";
307 return false;
308 }
309
310 return true;
311 }
312
PlatformSharedMemoryRegion(win::ScopedHandle handle,Mode mode,size_t size,const UnguessableToken & guid)313 PlatformSharedMemoryRegion::PlatformSharedMemoryRegion(
314 win::ScopedHandle handle,
315 Mode mode,
316 size_t size,
317 const UnguessableToken& guid)
318 : handle_(std::move(handle)), mode_(mode), size_(size), guid_(guid) {}
319
320 } // namespace subtle
321 } // namespace base
322