bmalloc StaticMutex's constructor should be constexpr
[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 char g_gigacageBasePtrs[GIGACAGE_BASE_PTRS_SIZE] __attribute__((aligned(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 } // anonymous namespace
103
104 void ensureGigacage()
105 {
106 #if GIGACAGE_ENABLED
107     static std::once_flag onceFlag;
108     std::call_once(
109         onceFlag,
110         [] {
111             if (!shouldBeEnabled())
112                 return;
113             
114             Kind shuffledKinds[numKinds];
115             for (unsigned i = 0; i < numKinds; ++i)
116                 shuffledKinds[i] = static_cast<Kind>(i);
117             
118             // We just go ahead and assume that 64 bits is enough randomness. That's trivially true right
119             // now, but would stop being true if we went crazy with gigacages. Based on my math, 21 is the
120             // largest value of n so that n! <= 2^64.
121             static_assert(numKinds <= 21, "too many kinds");
122             uint64_t random;
123             cryptoRandom(reinterpret_cast<unsigned char*>(&random), sizeof(random));
124             for (unsigned i = numKinds; i--;) {
125                 unsigned limit = i + 1;
126                 unsigned j = static_cast<unsigned>(random % limit);
127                 random /= limit;
128                 std::swap(shuffledKinds[i], shuffledKinds[j]);
129             }
130
131             auto alignTo = [] (Kind kind, size_t totalSize) -> size_t {
132                 return roundUpToMultipleOf(alignment(kind), totalSize);
133             };
134             auto bump = [] (Kind kind, size_t totalSize) -> size_t {
135                 return totalSize + size(kind);
136             };
137             
138             size_t totalSize = 0;
139             size_t maxAlignment = 0;
140             
141             for (Kind kind : shuffledKinds) {
142                 totalSize = bump(kind, alignTo(kind, totalSize));
143                 maxAlignment = std::max(maxAlignment, alignment(kind));
144             }
145             totalSize += GIGACAGE_RUNWAY;
146             
147             // FIXME: Randomize where this goes.
148             // https://bugs.webkit.org/show_bug.cgi?id=175245
149             void* base = tryVMAllocate(maxAlignment, totalSize);
150             if (!base) {
151                 if (GIGACAGE_ALLOCATION_CAN_FAIL)
152                     return;
153                 fprintf(stderr, "FATAL: Could not allocate gigacage memory with maxAlignment = %lu, totalSize = %lu.\n", maxAlignment, totalSize);
154                 fprintf(stderr, "(Make sure you have not set a virtual memory limit.)\n");
155                 BCRASH();
156             }
157
158             if (GIGACAGE_RUNWAY > 0) {
159                 char* runway = reinterpret_cast<char*>(base) + totalSize - GIGACAGE_RUNWAY;
160                 // Make OOB accesses into the runway crash.
161                 vmRevokePermissions(runway, GIGACAGE_RUNWAY);
162             }
163
164             vmDeallocatePhysicalPages(base, totalSize);
165             
166             size_t nextCage = 0;
167             for (Kind kind : shuffledKinds) {
168                 nextCage = alignTo(kind, nextCage);
169                 basePtr(kind) = reinterpret_cast<char*>(base) + nextCage;
170                 nextCage = bump(kind, nextCage);
171             }
172             
173             protectGigacageBasePtrs();
174             g_wasEnabled = true;
175         });
176 #endif // GIGACAGE_ENABLED
177 }
178
179 void disablePrimitiveGigacage()
180 {
181     ensureGigacage();
182     if (!basePtrs().primitive) {
183         // It was never enabled. That means that we never even saved any callbacks. Or, we had already disabled
184         // it before, and already called the callbacks.
185         return;
186     }
187     
188     PrimitiveDisableCallbacks& callbacks = *PerProcess<PrimitiveDisableCallbacks>::get();
189     std::unique_lock<Mutex> lock(PerProcess<PrimitiveDisableCallbacks>::mutex());
190     for (Callback& callback : callbacks.callbacks)
191         callback.function(callback.argument);
192     callbacks.callbacks.shrink(0);
193     UnprotectGigacageBasePtrsScope unprotectScope;
194     basePtrs().primitive = nullptr;
195 }
196
197 void addPrimitiveDisableCallback(void (*function)(void*), void* argument)
198 {
199     ensureGigacage();
200     if (!basePtrs().primitive) {
201         // It was already disabled or we were never able to enable it.
202         function(argument);
203         return;
204     }
205     
206     PrimitiveDisableCallbacks& callbacks = *PerProcess<PrimitiveDisableCallbacks>::get();
207     std::unique_lock<Mutex> lock(PerProcess<PrimitiveDisableCallbacks>::mutex());
208     callbacks.callbacks.push(Callback(function, argument));
209 }
210
211 void removePrimitiveDisableCallback(void (*function)(void*), void* argument)
212 {
213     PrimitiveDisableCallbacks& callbacks = *PerProcess<PrimitiveDisableCallbacks>::get();
214     std::unique_lock<Mutex> lock(PerProcess<PrimitiveDisableCallbacks>::mutex());
215     for (size_t i = 0; i < callbacks.callbacks.size(); ++i) {
216         if (callbacks.callbacks[i].function == function
217             && callbacks.callbacks[i].argument == argument) {
218             callbacks.callbacks[i] = callbacks.callbacks.last();
219             callbacks.callbacks.pop();
220             return;
221         }
222     }
223 }
224
225 static void primitiveGigacageDisabled(void*)
226 {
227     static bool s_false;
228     fprintf(stderr, "FATAL: Primitive gigacage disabled, but we don't want that in this process.\n");
229     if (!s_false)
230         BCRASH();
231 }
232
233 void disableDisablingPrimitiveGigacageIfShouldBeEnabled()
234 {
235     if (shouldBeEnabled()) {
236         addPrimitiveDisableCallback(primitiveGigacageDisabled, nullptr);
237         s_isDisablingPrimitiveGigacageDisabled = true;
238     }
239 }
240
241 bool isDisablingPrimitiveGigacageDisabled()
242 {
243     return s_isDisablingPrimitiveGigacageDisabled;
244 }
245
246 bool shouldBeEnabled()
247 {
248     static bool cached = false;
249
250 #if GIGACAGE_ENABLED
251     static std::once_flag onceFlag;
252     std::call_once(
253         onceFlag,
254         [] {
255             if (!gigacageEnabledForProcess())
256                 return;
257
258             bool result = !PerProcess<Environment>::get()->isDebugHeapEnabled();
259             if (!result)
260                 return;
261             
262             if (char* gigacageEnabled = getenv("GIGACAGE_ENABLED")) {
263                 if (!strcasecmp(gigacageEnabled, "no") || !strcasecmp(gigacageEnabled, "false") || !strcasecmp(gigacageEnabled, "0")) {
264                     fprintf(stderr, "Warning: disabling gigacage because GIGACAGE_ENABLED=%s!\n", gigacageEnabled);
265                     return;
266                 } else if (strcasecmp(gigacageEnabled, "yes") && strcasecmp(gigacageEnabled, "true") && strcasecmp(gigacageEnabled, "1"))
267                     fprintf(stderr, "Warning: invalid argument to GIGACAGE_ENABLED: %s\n", gigacageEnabled);
268             }
269             
270             cached = true;
271         });
272 #endif // GIGACAGE_ENABLED
273     
274     return cached;
275 }
276
277 } // namespace Gigacage
278
279
280