1/*
2 * Copyright (C) 2017-2019 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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "Gigacage.h"
27
28#include "CryptoRandom.h"
29#include "Environment.h"
30#include "ProcessCheck.h"
31#include "StaticPerProcess.h"
32#include "VMAllocate.h"
33#include "Vector.h"
34#include "bmalloc.h"
35#include <cstdio>
36#include <mutex>
37
38#if GIGACAGE_ENABLED
39
40namespace Gigacage {
41
42struct Callback {
43 Callback() { }
44
45 Callback(void (*function)(void*), void *argument)
46 : function(function)
47 , argument(argument)
48 {
49 }
50
51 void (*function)(void*) { nullptr };
52 void* argument { nullptr };
53};
54
55}
56
57namespace bmalloc {
58
59struct PrimitiveDisableCallbacks : public StaticPerProcess<PrimitiveDisableCallbacks> {
60 PrimitiveDisableCallbacks(std::lock_guard<Mutex>&) { }
61
62 Vector<Gigacage::Callback> callbacks;
63};
64DECLARE_STATIC_PER_PROCESS_STORAGE(PrimitiveDisableCallbacks);
65DEFINE_STATIC_PER_PROCESS_STORAGE(PrimitiveDisableCallbacks);
66
67} // namespace bmalloc
68
69namespace Gigacage {
70
71// This is exactly 32GB because inside JSC, indexed accesses for arrays, typed arrays, etc,
72// use unsigned 32-bit ints as indices. The items those indices access are 8 bytes or less
73// in size. 2^32 * 8 = 32GB. This means if an access on a caged type happens to go out of
74// bounds, the access is guaranteed to land somewhere else in the cage or inside the runway.
75// If this were less than 32GB, those OOB accesses could reach outside of the cage.
76constexpr size_t gigacageRunway = 32llu * 1024 * 1024 * 1024;
77
78// Note: g_gigacageBasePtrs[0] is reserved for storing the wasEnabled flag.
79// The first gigacageBasePtr will start at g_gigacageBasePtrs[sizeof(void*)].
80// This is done so that the wasEnabled flag will also be protected along with the
81// gigacageBasePtrs.
82alignas(gigacageBasePtrsSize) char g_gigacageBasePtrs[gigacageBasePtrsSize];
83
84using namespace bmalloc;
85
86namespace {
87
88bool s_isDisablingPrimitiveGigacageDisabled;
89
90void protectGigacageBasePtrs()
91{
92 uintptr_t basePtrs = reinterpret_cast<uintptr_t>(g_gigacageBasePtrs);
93 // We might only get page size alignment, but that's also the minimum we need.
94 RELEASE_BASSERT(!(basePtrs & (vmPageSize() - 1)));
95 mprotect(g_gigacageBasePtrs, gigacageBasePtrsSize, PROT_READ);
96}
97
98void unprotectGigacageBasePtrs()
99{
100 mprotect(g_gigacageBasePtrs, gigacageBasePtrsSize, PROT_READ | PROT_WRITE);
101}
102
103class UnprotectGigacageBasePtrsScope {
104public:
105 UnprotectGigacageBasePtrsScope()
106 {
107 unprotectGigacageBasePtrs();
108 }
109
110 ~UnprotectGigacageBasePtrsScope()
111 {
112 protectGigacageBasePtrs();
113 }
114};
115
116size_t runwaySize(Kind kind)
117{
118 switch (kind) {
119 case Kind::ReservedForFlagsAndNotABasePtr:
120 RELEASE_BASSERT_NOT_REACHED();
121 case Kind::Primitive:
122 return gigacageRunway;
123 case Kind::JSValue:
124 return 0;
125 }
126 return 0;
127}
128
129} // anonymous namespace
130
131void ensureGigacage()
132{
133 static std::once_flag onceFlag;
134 std::call_once(
135 onceFlag,
136 [] {
137 if (!shouldBeEnabled())
138 return;
139
140 Kind shuffledKinds[numKinds];
141 for (unsigned i = 0; i < numKinds; ++i)
142 shuffledKinds[i] = static_cast<Kind>(i + 1); // + 1 to skip Kind::ReservedForFlagsAndNotABasePtr.
143
144 // We just go ahead and assume that 64 bits is enough randomness. That's trivially true right
145 // now, but would stop being true if we went crazy with gigacages. Based on my math, 21 is the
146 // largest value of n so that n! <= 2^64.
147 static_assert(numKinds <= 21, "too many kinds");
148 uint64_t random;
149 cryptoRandom(reinterpret_cast<unsigned char*>(&random), sizeof(random));
150 for (unsigned i = numKinds; i--;) {
151 unsigned limit = i + 1;
152 unsigned j = static_cast<unsigned>(random % limit);
153 random /= limit;
154 std::swap(shuffledKinds[i], shuffledKinds[j]);
155 }
156
157 auto alignTo = [] (Kind kind, size_t totalSize) -> size_t {
158 return roundUpToMultipleOf(alignment(kind), totalSize);
159 };
160 auto bump = [] (Kind kind, size_t totalSize) -> size_t {
161 return totalSize + size(kind);
162 };
163
164 size_t totalSize = 0;
165 size_t maxAlignment = 0;
166
167 for (Kind kind : shuffledKinds) {
168 totalSize = bump(kind, alignTo(kind, totalSize));
169 totalSize += runwaySize(kind);
170 maxAlignment = std::max(maxAlignment, alignment(kind));
171 }
172
173 // FIXME: Randomize where this goes.
174 // https://bugs.webkit.org/show_bug.cgi?id=175245
175 void* base = tryVMAllocate(maxAlignment, totalSize, VMTag::JSGigacage);
176 if (!base) {
177 if (GIGACAGE_ALLOCATION_CAN_FAIL)
178 return;
179 fprintf(stderr, "FATAL: Could not allocate gigacage memory with maxAlignment = %lu, totalSize = %lu.\n", maxAlignment, totalSize);
180 fprintf(stderr, "(Make sure you have not set a virtual memory limit.)\n");
181 BCRASH();
182 }
183
184 size_t nextCage = 0;
185 for (Kind kind : shuffledKinds) {
186 nextCage = alignTo(kind, nextCage);
187 basePtr(kind) = reinterpret_cast<char*>(base) + nextCage;
188 nextCage = bump(kind, nextCage);
189 if (runwaySize(kind) > 0) {
190 char* runway = reinterpret_cast<char*>(base) + nextCage;
191 // Make OOB accesses into the runway crash.
192 vmRevokePermissions(runway, runwaySize(kind));
193 nextCage += runwaySize(kind);
194 }
195 }
196
197 vmDeallocatePhysicalPages(base, totalSize);
198 setWasEnabled();
199 protectGigacageBasePtrs();
200 });
201}
202
203void disablePrimitiveGigacage()
204{
205 ensureGigacage();
206 if (!basePtrs().primitive) {
207 // It was never enabled. That means that we never even saved any callbacks. Or, we had already disabled
208 // it before, and already called the callbacks.
209 return;
210 }
211
212 PrimitiveDisableCallbacks& callbacks = *PrimitiveDisableCallbacks::get();
213 std::unique_lock<Mutex> lock(PrimitiveDisableCallbacks::mutex());
214 for (Callback& callback : callbacks.callbacks)
215 callback.function(callback.argument);
216 callbacks.callbacks.shrink(0);
217 UnprotectGigacageBasePtrsScope unprotectScope;
218 basePtrs().primitive = nullptr;
219}
220
221void addPrimitiveDisableCallback(void (*function)(void*), void* argument)
222{
223 ensureGigacage();
224 if (!basePtrs().primitive) {
225 // It was already disabled or we were never able to enable it.
226 function(argument);
227 return;
228 }
229
230 PrimitiveDisableCallbacks& callbacks = *PrimitiveDisableCallbacks::get();
231 std::unique_lock<Mutex> lock(PrimitiveDisableCallbacks::mutex());
232 callbacks.callbacks.push(Callback(function, argument));
233}
234
235void removePrimitiveDisableCallback(void (*function)(void*), void* argument)
236{
237 PrimitiveDisableCallbacks& callbacks = *PrimitiveDisableCallbacks::get();
238 std::unique_lock<Mutex> lock(PrimitiveDisableCallbacks::mutex());
239 for (size_t i = 0; i < callbacks.callbacks.size(); ++i) {
240 if (callbacks.callbacks[i].function == function
241 && callbacks.callbacks[i].argument == argument) {
242 callbacks.callbacks[i] = callbacks.callbacks.last();
243 callbacks.callbacks.pop();
244 return;
245 }
246 }
247}
248
249static void primitiveGigacageDisabled(void*)
250{
251 if (GIGACAGE_ALLOCATION_CAN_FAIL && !wasEnabled())
252 return;
253
254 static bool s_false;
255 fprintf(stderr, "FATAL: Primitive gigacage disabled, but we don't want that in this process.\n");
256 if (!s_false)
257 BCRASH();
258}
259
260void disableDisablingPrimitiveGigacageIfShouldBeEnabled()
261{
262 if (shouldBeEnabled()) {
263 addPrimitiveDisableCallback(primitiveGigacageDisabled, nullptr);
264 s_isDisablingPrimitiveGigacageDisabled = true;
265 }
266}
267
268bool isDisablingPrimitiveGigacageDisabled()
269{
270 return s_isDisablingPrimitiveGigacageDisabled;
271}
272
273bool shouldBeEnabled()
274{
275 static bool cached = false;
276 static std::once_flag onceFlag;
277 std::call_once(
278 onceFlag,
279 [] {
280 bool debugHeapEnabled = Environment::get()->isDebugHeapEnabled();
281 if (debugHeapEnabled)
282 return;
283
284 if (!gigacageEnabledForProcess())
285 return;
286
287 if (char* gigacageEnabled = getenv("GIGACAGE_ENABLED")) {
288 if (!strcasecmp(gigacageEnabled, "no") || !strcasecmp(gigacageEnabled, "false") || !strcasecmp(gigacageEnabled, "0")) {
289 fprintf(stderr, "Warning: disabling gigacage because GIGACAGE_ENABLED=%s!\n", gigacageEnabled);
290 return;
291 } else if (strcasecmp(gigacageEnabled, "yes") && strcasecmp(gigacageEnabled, "true") && strcasecmp(gigacageEnabled, "1"))
292 fprintf(stderr, "Warning: invalid argument to GIGACAGE_ENABLED: %s\n", gigacageEnabled);
293 }
294
295 cached = true;
296 });
297 return cached;
298}
299
300} // namespace Gigacage
301
302#endif // GIGACAGE_ENABLED
303
304
305