Gigacage runway should immediately follow the primitive cage
[WebKit-https.git] / Source / bmalloc / bmalloc / Gigacage.cpp
1 /*
2  * Copyright (C) 2017-2018 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 "PerProcess.h"
31 #include "ProcessCheck.h"
32 #include "VMAllocate.h"
33 #include "Vector.h"
34 #include "bmalloc.h"
35 #include <cstdio>
36 #include <mutex>
37
38 // This is exactly 32GB because inside JSC, indexed accesses for arrays, typed arrays, etc,
39 // use unsigned 32-bit ints as indices. The items those indices access are 8 bytes or less
40 // in size. 2^32 * 8 = 32GB. This means if an access on a caged type happens to go out of
41 // bounds, the access is guaranteed to land somewhere else in the cage or inside the runway.
42 // If this were less than 32GB, those OOB accesses could reach outside of the cage.
43 #define GIGACAGE_RUNWAY (32llu * 1024 * 1024 * 1024)
44
45 alignas(GIGACAGE_BASE_PTRS_SIZE) char g_gigacageBasePtrs[GIGACAGE_BASE_PTRS_SIZE];
46
47 using namespace bmalloc;
48
49 namespace Gigacage {
50
51 bool g_wasEnabled;
52
53 namespace {
54
55 bool s_isDisablingPrimitiveGigacageDisabled;
56
57 void protectGigacageBasePtrs()
58 {
59     uintptr_t basePtrs = reinterpret_cast<uintptr_t>(g_gigacageBasePtrs);
60     // We might only get page size alignment, but that's also the minimum we need.
61     RELEASE_BASSERT(!(basePtrs & (vmPageSize() - 1)));
62     mprotect(g_gigacageBasePtrs, GIGACAGE_BASE_PTRS_SIZE, PROT_READ);
63 }
64
65 void unprotectGigacageBasePtrs()
66 {
67     mprotect(g_gigacageBasePtrs, GIGACAGE_BASE_PTRS_SIZE, PROT_READ | PROT_WRITE);
68 }
69
70 class UnprotectGigacageBasePtrsScope {
71 public:
72     UnprotectGigacageBasePtrsScope()
73     {
74         unprotectGigacageBasePtrs();
75     }
76     
77     ~UnprotectGigacageBasePtrsScope()
78     {
79         protectGigacageBasePtrs();
80     }
81 };
82
83 struct Callback {
84     Callback() { }
85     
86     Callback(void (*function)(void*), void *argument)
87         : function(function)
88         , argument(argument)
89     {
90     }
91     
92     void (*function)(void*) { nullptr };
93     void* argument { nullptr };
94 };
95
96 struct PrimitiveDisableCallbacks {
97     PrimitiveDisableCallbacks(std::lock_guard<Mutex>&) { }
98     
99     Vector<Callback> callbacks;
100 };
101
102 #if GIGACAGE_ENABLED
103 size_t runwaySize(Kind kind)
104 {
105     switch (kind) {
106     case Kind::Primitive:
107         return static_cast<size_t>(GIGACAGE_RUNWAY);
108     case Kind::JSValue:
109         return static_cast<size_t>(0);
110     }
111 }
112 #endif
113
114 } // anonymous namespace
115
116 void ensureGigacage()
117 {
118 #if GIGACAGE_ENABLED
119     static std::once_flag onceFlag;
120     std::call_once(
121         onceFlag,
122         [] {
123             if (!shouldBeEnabled())
124                 return;
125             
126             Kind shuffledKinds[numKinds];
127             for (unsigned i = 0; i < numKinds; ++i)
128                 shuffledKinds[i] = static_cast<Kind>(i);
129             
130             // We just go ahead and assume that 64 bits is enough randomness. That's trivially true right
131             // now, but would stop being true if we went crazy with gigacages. Based on my math, 21 is the
132             // largest value of n so that n! <= 2^64.
133             static_assert(numKinds <= 21, "too many kinds");
134             uint64_t random;
135             cryptoRandom(reinterpret_cast<unsigned char*>(&random), sizeof(random));
136             for (unsigned i = numKinds; i--;) {
137                 unsigned limit = i + 1;
138                 unsigned j = static_cast<unsigned>(random % limit);
139                 random /= limit;
140                 std::swap(shuffledKinds[i], shuffledKinds[j]);
141             }
142
143             auto alignTo = [] (Kind kind, size_t totalSize) -> size_t {
144                 return roundUpToMultipleOf(alignment(kind), totalSize);
145             };
146             auto bump = [] (Kind kind, size_t totalSize) -> size_t {
147                 return totalSize + size(kind);
148             };
149             
150             size_t totalSize = 0;
151             size_t maxAlignment = 0;
152             
153             for (Kind kind : shuffledKinds) {
154                 totalSize = bump(kind, alignTo(kind, totalSize));
155                 totalSize += runwaySize(kind);
156                 maxAlignment = std::max(maxAlignment, alignment(kind));
157             }
158
159             // FIXME: Randomize where this goes.
160             // https://bugs.webkit.org/show_bug.cgi?id=175245
161             void* base = tryVMAllocate(maxAlignment, totalSize);
162             if (!base) {
163                 if (GIGACAGE_ALLOCATION_CAN_FAIL)
164                     return;
165                 fprintf(stderr, "FATAL: Could not allocate gigacage memory with maxAlignment = %lu, totalSize = %lu.\n", maxAlignment, totalSize);
166                 fprintf(stderr, "(Make sure you have not set a virtual memory limit.)\n");
167                 BCRASH();
168             }
169
170             size_t nextCage = 0;
171             for (Kind kind : shuffledKinds) {
172                 nextCage = alignTo(kind, nextCage);
173                 basePtr(kind) = reinterpret_cast<char*>(base) + nextCage;
174                 nextCage = bump(kind, nextCage);
175                 if (runwaySize(kind) > 0) {
176                     char* runway = reinterpret_cast<char*>(base) + nextCage;
177                     // Make OOB accesses into the runway crash.
178                     vmRevokePermissions(runway, runwaySize(kind));
179                     nextCage += runwaySize(kind);
180                 }
181             }
182             
183             vmDeallocatePhysicalPages(base, totalSize);
184             protectGigacageBasePtrs();
185             g_wasEnabled = true;
186         });
187 #endif // GIGACAGE_ENABLED
188 }
189
190 void disablePrimitiveGigacage()
191 {
192     ensureGigacage();
193     if (!basePtrs().primitive) {
194         // It was never enabled. That means that we never even saved any callbacks. Or, we had already disabled
195         // it before, and already called the callbacks.
196         return;
197     }
198     
199     PrimitiveDisableCallbacks& callbacks = *PerProcess<PrimitiveDisableCallbacks>::get();
200     std::unique_lock<Mutex> lock(PerProcess<PrimitiveDisableCallbacks>::mutex());
201     for (Callback& callback : callbacks.callbacks)
202         callback.function(callback.argument);
203     callbacks.callbacks.shrink(0);
204     UnprotectGigacageBasePtrsScope unprotectScope;
205     basePtrs().primitive = nullptr;
206 }
207
208 void addPrimitiveDisableCallback(void (*function)(void*), void* argument)
209 {
210     ensureGigacage();
211     if (!basePtrs().primitive) {
212         // It was already disabled or we were never able to enable it.
213         function(argument);
214         return;
215     }
216     
217     PrimitiveDisableCallbacks& callbacks = *PerProcess<PrimitiveDisableCallbacks>::get();
218     std::unique_lock<Mutex> lock(PerProcess<PrimitiveDisableCallbacks>::mutex());
219     callbacks.callbacks.push(Callback(function, argument));
220 }
221
222 void removePrimitiveDisableCallback(void (*function)(void*), void* argument)
223 {
224     PrimitiveDisableCallbacks& callbacks = *PerProcess<PrimitiveDisableCallbacks>::get();
225     std::unique_lock<Mutex> lock(PerProcess<PrimitiveDisableCallbacks>::mutex());
226     for (size_t i = 0; i < callbacks.callbacks.size(); ++i) {
227         if (callbacks.callbacks[i].function == function
228             && callbacks.callbacks[i].argument == argument) {
229             callbacks.callbacks[i] = callbacks.callbacks.last();
230             callbacks.callbacks.pop();
231             return;
232         }
233     }
234 }
235
236 static void primitiveGigacageDisabled(void*)
237 {
238     static bool s_false;
239     fprintf(stderr, "FATAL: Primitive gigacage disabled, but we don't want that in this process.\n");
240     if (!s_false)
241         BCRASH();
242 }
243
244 void disableDisablingPrimitiveGigacageIfShouldBeEnabled()
245 {
246     if (shouldBeEnabled()) {
247         addPrimitiveDisableCallback(primitiveGigacageDisabled, nullptr);
248         s_isDisablingPrimitiveGigacageDisabled = true;
249     }
250 }
251
252 bool isDisablingPrimitiveGigacageDisabled()
253 {
254     return s_isDisablingPrimitiveGigacageDisabled;
255 }
256
257 bool shouldBeEnabled()
258 {
259     static bool cached = false;
260
261 #if GIGACAGE_ENABLED
262     static std::once_flag onceFlag;
263     std::call_once(
264         onceFlag,
265         [] {
266             if (!gigacageEnabledForProcess())
267                 return;
268
269             bool result = !PerProcess<Environment>::get()->isDebugHeapEnabled();
270             if (!result)
271                 return;
272             
273             if (char* gigacageEnabled = getenv("GIGACAGE_ENABLED")) {
274                 if (!strcasecmp(gigacageEnabled, "no") || !strcasecmp(gigacageEnabled, "false") || !strcasecmp(gigacageEnabled, "0")) {
275                     fprintf(stderr, "Warning: disabling gigacage because GIGACAGE_ENABLED=%s!\n", gigacageEnabled);
276                     return;
277                 } else if (strcasecmp(gigacageEnabled, "yes") && strcasecmp(gigacageEnabled, "true") && strcasecmp(gigacageEnabled, "1"))
278                     fprintf(stderr, "Warning: invalid argument to GIGACAGE_ENABLED: %s\n", gigacageEnabled);
279             }
280             
281             cached = true;
282         });
283 #endif // GIGACAGE_ENABLED
284     
285     return cached;
286 }
287
288 } // namespace Gigacage
289
290
291