1/*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "OSAllocator.h"
28
29#if OS(UNIX)
30
31#include <cstdlib>
32
33#include "PageAllocation.h"
34#include <errno.h>
35#include <sys/mman.h>
36#include <string.h>
37#include <wtf/Assertions.h>
38#include <wtf/UnusedParam.h>
39
40#if OS(LINUX)
41#include <sys/syscall.h>
42#ifndef MFD_CLOEXEC
43#define MFD_CLOEXEC 0x0001U
44#endif
45#endif
46
47#if defined(__ANDROID__) && defined(SYS_memfd_create)
48 // On Android it's been observed that permissions of memory mappings
49 // backed by a memfd could not be changed via mprotect for no obvious
50 // reason.
51# undef SYS_memfd_create
52#endif
53
54namespace WTF {
55
56#ifdef SYS_memfd_create
57static int memfdForUsage(size_t bytes, OSAllocator::Usage usage)
58{
59 const char *type = "unknown-usage:";
60 switch (usage) {
61 case OSAllocator::UnknownUsage:
62 break;
63 case OSAllocator::FastMallocPages:
64 type = "fastmalloc:";
65 break;
66 case OSAllocator::JSGCHeapPages:
67 type = "JSGCHeap:";
68 break;
69 case OSAllocator::JSVMStackPages:
70 type = "JSVMStack:";
71 break;
72 case OSAllocator::JSJITCodePages:
73 type = "JITCode:";
74 break;
75 }
76
77 char buf[PATH_MAX];
78 strcpy(dest: buf, src: type);
79 strcat(dest: buf, src: "QtQml");
80
81 int fd = syscall(SYS_memfd_create, buf, MFD_CLOEXEC);
82 if (fd != -1) {
83 if (ftruncate(fd: fd, length: bytes) == 0)
84 return fd;
85 }
86 close(fd: fd);
87 return -1;
88}
89#elif OS(LINUX)
90static int memfdForUsage(size_t bytes, OSAllocator::Usage usage)
91{
92 UNUSED_PARAM(bytes);
93 UNUSED_PARAM(usage);
94 return -1;
95}
96#endif
97
98void* OSAllocator::reserveUncommitted(size_t bytes, Usage usage, bool writable, bool executable)
99{
100#if OS(QNX)
101 UNUSED_PARAM(usage);
102 UNUSED_PARAM(writable);
103 UNUSED_PARAM(executable);
104 // Reserve memory with PROT_NONE and MAP_LAZY so it isn't committed now.
105 void* result = mmap(0, bytes, PROT_NONE, MAP_LAZY | MAP_PRIVATE | MAP_ANON, -1, 0);
106 if (result == MAP_FAILED)
107 CRASH();
108#elif OS(LINUX)
109 UNUSED_PARAM(writable);
110 UNUSED_PARAM(executable);
111 int fd = memfdForUsage(bytes, usage);
112
113 void* result = mmap(addr: 0, len: bytes, PROT_NONE, MAP_NORESERVE | MAP_PRIVATE |
114 (fd == -1 ? MAP_ANON : 0), fd: fd, offset: 0);
115 if (result == MAP_FAILED)
116 CRASH();
117
118 if (fd != -1)
119 close(fd: fd);
120#else
121 void* result = reserveAndCommit(bytes, usage, writable, executable);
122#if HAVE(MADV_FREE_REUSE)
123 // To support the "reserve then commit" model, we have to initially decommit.
124 while (madvise(result, bytes, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN) { }
125#endif
126
127#endif // OS(QNX)
128
129 return result;
130}
131
132void* OSAllocator::reserveAndCommit(size_t bytes, Usage usage, bool writable, bool executable, bool includesGuardPages)
133{
134 // All POSIX reservations start out logically committed.
135 int protection = PROT_READ;
136 if (writable)
137 protection |= PROT_WRITE;
138 if (executable)
139 protection |= PROT_EXEC;
140
141 int flags = MAP_PRIVATE | MAP_ANON;
142#if OS(DARWIN)
143 if (executable || usage == OSAllocator::JSJITCodePages)
144 flags |= MAP_JIT;
145#endif
146
147#if OS(DARWIN)
148 int fd = usage;
149#elif OS(LINUX)
150 int fd = memfdForUsage(bytes, usage);
151 if (fd != -1)
152 flags &= ~MAP_ANON;
153#else
154 UNUSED_PARAM(usage);
155 int fd = -1;
156#endif
157
158 void* result = 0;
159#if (OS(DARWIN) && CPU(X86_64))
160 if (executable) {
161 ASSERT(includesGuardPages);
162 // Cook up an address to allocate at, using the following recipe:
163 // 17 bits of zero, stay in userspace kids.
164 // 26 bits of randomness for ASLR.
165 // 21 bits of zero, at least stay aligned within one level of the pagetables.
166 //
167 // But! - as a temporary workaround for some plugin problems (rdar://problem/6812854),
168 // for now instead of 2^26 bits of ASLR lets stick with 25 bits of randomization plus
169 // 2^24, which should put up somewhere in the middle of userspace (in the address range
170 // 0x200000000000 .. 0x5fffffffffff).
171 intptr_t randomLocation = 0;
172 randomLocation = arc4random() & ((1 << 25) - 1);
173 randomLocation += (1 << 24);
174 randomLocation <<= 21;
175 result = reinterpret_cast<void*>(randomLocation);
176 }
177#endif
178
179 result = mmap(addr: result, len: bytes, prot: protection, flags: flags, fd: fd, offset: 0);
180 if (result == MAP_FAILED) {
181#if ENABLE(LLINT)
182 if (executable)
183 result = 0;
184 else
185#endif
186 CRASH();
187 }
188#if OS(VXWORKS) && defined(QT_RTP_MEM_FILL) && QT_RTP_MEM_FILL
189 // page allocator expects mmap'ed memory to be zero'ed
190 memset(result, 0, bytes);
191#endif
192
193 if (result && includesGuardPages) {
194 // We use mmap to remap the guardpages rather than using mprotect as
195 // mprotect results in multiple references to the code region. This
196 // breaks the madvise based mechanism we use to return physical memory
197 // to the OS.
198 mmap(addr: result, len: pageSize(), PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON, fd: fd, offset: 0);
199 mmap(addr: static_cast<char*>(result) + bytes - pageSize(), len: pageSize(), PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON, fd: fd, offset: 0);
200 }
201
202#if OS(LINUX)
203 if (fd != -1)
204 close(fd: fd);
205#endif
206
207 return result;
208}
209
210void OSAllocator::commit(void* address, size_t bytes, bool writable, bool executable)
211{
212#if OS(QNX)
213 int protection = PROT_READ;
214 if (writable)
215 protection |= PROT_WRITE;
216 if (executable)
217 protection |= PROT_EXEC;
218 if (MAP_FAILED == mmap(address, bytes, protection, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0))
219 CRASH();
220#elif OS(LINUX)
221 int protection = PROT_READ;
222 if (writable)
223 protection |= PROT_WRITE;
224 if (executable)
225 protection |= PROT_EXEC;
226 if (mprotect(addr: address, len: bytes, prot: protection))
227 CRASH();
228
229 while (madvise(addr: address, len: bytes, MADV_WILLNEED)) {
230 if (errno != EAGAIN)
231 break; // We don't have to crash here. MADV_WILLNEED is only advisory
232 }
233
234#elif HAVE(MADV_FREE_REUSE)
235 UNUSED_PARAM(writable);
236 UNUSED_PARAM(executable);
237 while (madvise(address, bytes, MADV_FREE_REUSE) == -1 && errno == EAGAIN) { }
238#else
239 // Non-MADV_FREE_REUSE reservations automatically commit on demand.
240 UNUSED_PARAM(address);
241 UNUSED_PARAM(bytes);
242 UNUSED_PARAM(writable);
243 UNUSED_PARAM(executable);
244#endif
245}
246
247void OSAllocator::decommit(void* address, size_t bytes)
248{
249#if OS(QNX)
250 // Use PROT_NONE and MAP_LAZY to decommit the pages.
251 mmap(address, bytes, PROT_NONE, MAP_FIXED | MAP_LAZY | MAP_PRIVATE | MAP_ANON, -1, 0);
252#elif OS(LINUX)
253 while (madvise(addr: address, len: bytes, MADV_DONTNEED)) {
254 if (errno != EAGAIN) {
255 memset(s: address, c: 0, n: bytes); // We rely on madvise to zero-out the memory
256 break;
257 }
258 }
259 if (mprotect(addr: address, len: bytes, PROT_NONE))
260 CRASH();
261#elif HAVE(MADV_FREE_REUSE)
262 while (madvise(address, bytes, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN) { }
263#elif HAVE(MADV_FREE)
264 while (madvise(address, bytes, MADV_FREE) == -1 && errno == EAGAIN) { }
265#elif HAVE(MADV_DONTNEED)
266 while (madvise(address, bytes, MADV_DONTNEED) == -1 && errno == EAGAIN) { }
267#else
268 UNUSED_PARAM(address);
269 UNUSED_PARAM(bytes);
270#endif
271}
272
273void OSAllocator::releaseDecommitted(void* address, size_t bytes)
274{
275 int result = munmap(addr: address, len: bytes);
276 if (result == -1)
277 CRASH();
278}
279
280#if OS(MAC_OS_X) && CPU(X86_64)
281
282// For unknown reasons, the correct detection (see below) causes crashes on macOS on x86_64.
283// Use the known good one from Qt 6.8.0 for now.
284// TODO: Delete this when macOS on x86_64 is not a thing anymore.
285bool OSAllocator::canAllocateExecutableMemory()
286{
287 int flags = MAP_PRIVATE | MAP_ANON;
288 const auto size = pageSize();
289 void *testPage = mmap(
290 nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, flags, /*fd*/-1, /*offset*/0);
291 if (testPage == MAP_FAILED)
292 return false;
293 munmap(testPage, size);
294 return true;
295}
296
297#else
298
299bool OSAllocator::canAllocateExecutableMemory()
300{
301 int flags = MAP_PRIVATE;
302#if OS(DARWIN)
303 flags |= MAP_JIT;
304#endif
305
306 // Get a read/write memfd page
307 const auto size = pageSize();
308#if OS(LINUX)
309 const int fd = memfdForUsage(bytes: size, usage: OSAllocator::JSJITCodePages);
310#else
311 const int fd = -1;
312#endif
313 if (fd == -1)
314 flags |= MAP_ANON;
315 void *testPage = mmap(addr: nullptr, len: size, PROT_READ | PROT_WRITE, flags: flags, fd: fd, /*offset*/0);
316 if (fd != -1)
317 close(fd: fd);
318
319 if (testPage == MAP_FAILED)
320 return false;
321
322 // Write something into the page, to trigger CoW
323 memset(s: testPage, c: 0xab, n: sizeof(quintptr));
324
325 // Then make it executable
326 const bool result = mprotect(addr: testPage, len: size, PROT_READ | PROT_EXEC) == 0;
327
328 munmap(addr: testPage, len: size);
329 return result;
330}
331
332#endif
333
334} // namespace WTF
335
336#endif // OS(UNIX)
337

source code of qtdeclarative/src/3rdparty/masm/wtf/OSAllocatorPosix.cpp