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