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