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 | |
40 | namespace Gigacage { |
41 | |
42 | struct 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 | |
57 | namespace bmalloc { |
58 | |
59 | struct PrimitiveDisableCallbacks : public StaticPerProcess<PrimitiveDisableCallbacks> { |
60 | PrimitiveDisableCallbacks(std::lock_guard<Mutex>&) { } |
61 | |
62 | Vector<Gigacage::Callback> callbacks; |
63 | }; |
64 | DECLARE_STATIC_PER_PROCESS_STORAGE(PrimitiveDisableCallbacks); |
65 | DEFINE_STATIC_PER_PROCESS_STORAGE(PrimitiveDisableCallbacks); |
66 | |
67 | } // namespace bmalloc |
68 | |
69 | namespace 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. |
76 | constexpr 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. |
82 | alignas(gigacageBasePtrsSize) char g_gigacageBasePtrs[gigacageBasePtrsSize]; |
83 | |
84 | using namespace bmalloc; |
85 | |
86 | namespace { |
87 | |
88 | bool s_isDisablingPrimitiveGigacageDisabled; |
89 | |
90 | void 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 | |
98 | void unprotectGigacageBasePtrs() |
99 | { |
100 | mprotect(g_gigacageBasePtrs, gigacageBasePtrsSize, PROT_READ | PROT_WRITE); |
101 | } |
102 | |
103 | class { |
104 | public: |
105 | () |
106 | { |
107 | unprotectGigacageBasePtrs(); |
108 | } |
109 | |
110 | () |
111 | { |
112 | protectGigacageBasePtrs(); |
113 | } |
114 | }; |
115 | |
116 | size_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 | |
131 | void 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 | |
203 | void 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 | |
221 | void 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 | |
235 | void 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 | |
249 | static 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 | |
260 | void disableDisablingPrimitiveGigacageIfShouldBeEnabled() |
261 | { |
262 | if (shouldBeEnabled()) { |
263 | addPrimitiveDisableCallback(primitiveGigacageDisabled, nullptr); |
264 | s_isDisablingPrimitiveGigacageDisabled = true; |
265 | } |
266 | } |
267 | |
268 | bool isDisablingPrimitiveGigacageDisabled() |
269 | { |
270 | return s_isDisablingPrimitiveGigacageDisabled; |
271 | } |
272 | |
273 | bool 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 | |