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