1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // PLEASE READ BEFORE CHANGING THIS FILE!
6 //
7 // This file implements the support code for the out of bounds trap handler.
8 // Nothing in here actually runs in the trap handler, but the code here
9 // manipulates data structures used by the trap handler so we still need to be
10 // careful. In order to minimize this risk, here are some rules to follow.
11 //
12 // 1. Avoid introducing new external dependencies. The files in src/trap-handler
13 // should be as self-contained as possible to make it easy to audit the code.
14 //
15 // 2. Any changes must be reviewed by someone from the crash reporting
16 // or security team. See OWNERS for suggested reviewers.
17 //
18 // For more information, see https://goo.gl/yMeyUY.
19 //
20 // For the code that runs in the trap handler itself, see handler-inside.cc.
21
22 #include <stddef.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <atomic>
28 #include <limits>
29
30 #include "src/trap-handler/trap-handler-internal.h"
31 #include "src/trap-handler/trap-handler.h"
32
33 namespace {
34 size_t gNextCodeObject = 0;
35
36 #ifdef ENABLE_SLOW_DCHECKS
37 constexpr bool kEnableSlowChecks = true;
38 #else
39 constexpr bool kEnableSlowChecks = false;
40 #endif
41 } // namespace
42
43 namespace v8 {
44 namespace internal {
45 namespace trap_handler {
46
47 constexpr size_t kInitialCodeObjectSize = 1024;
48 constexpr size_t kCodeObjectGrowthFactor = 2;
49
HandlerDataSize(size_t num_protected_instructions)50 constexpr size_t HandlerDataSize(size_t num_protected_instructions) {
51 return offsetof(CodeProtectionInfo, instructions) +
52 num_protected_instructions * sizeof(ProtectedInstructionData);
53 }
54
55 namespace {
56 #ifdef DEBUG
IsDisjoint(const CodeProtectionInfo * a,const CodeProtectionInfo * b)57 bool IsDisjoint(const CodeProtectionInfo* a, const CodeProtectionInfo* b) {
58 if (a == nullptr || b == nullptr) {
59 return true;
60 }
61 return a->base >= b->base + b->size || b->base >= a->base + a->size;
62 }
63 #endif
64
65 // Verify that the code range does not overlap any that have already been
66 // registered.
VerifyCodeRangeIsDisjoint(const CodeProtectionInfo * code_info)67 void VerifyCodeRangeIsDisjoint(const CodeProtectionInfo* code_info) {
68 for (size_t i = 0; i < gNumCodeObjects; ++i) {
69 DCHECK(IsDisjoint(code_info, gCodeObjects[i].code_info));
70 }
71 }
72
ValidateCodeObjects()73 void ValidateCodeObjects() {
74 // Sanity-check the code objects
75 for (unsigned i = 0; i < gNumCodeObjects; ++i) {
76 const auto* data = gCodeObjects[i].code_info;
77
78 if (data == nullptr) continue;
79
80 // Do some sanity checks on the protected instruction data
81 for (unsigned i = 0; i < data->num_protected_instructions; ++i) {
82 DCHECK_GE(data->instructions[i].instr_offset, 0);
83 DCHECK_LT(data->instructions[i].instr_offset, data->size);
84 DCHECK_GE(data->instructions[i].landing_offset, 0);
85 DCHECK_LT(data->instructions[i].landing_offset, data->size);
86 DCHECK_GT(data->instructions[i].landing_offset,
87 data->instructions[i].instr_offset);
88 }
89 }
90
91 // Check the validity of the free list.
92 size_t free_count = 0;
93 for (size_t i = gNextCodeObject; i != gNumCodeObjects;
94 i = gCodeObjects[i].next_free) {
95 DCHECK_LT(i, gNumCodeObjects);
96 ++free_count;
97 // This check will fail if we encounter a cycle.
98 DCHECK_LE(free_count, gNumCodeObjects);
99 }
100
101 // Check that all free entries are reachable via the free list.
102 size_t free_count2 = 0;
103 for (size_t i = 0; i < gNumCodeObjects; ++i) {
104 if (gCodeObjects[i].code_info == nullptr) {
105 ++free_count2;
106 }
107 }
108 DCHECK_EQ(free_count, free_count2);
109 }
110 } // namespace
111
CreateHandlerData(Address base,size_t size,size_t num_protected_instructions,const ProtectedInstructionData * protected_instructions)112 CodeProtectionInfo* CreateHandlerData(
113 Address base, size_t size, size_t num_protected_instructions,
114 const ProtectedInstructionData* protected_instructions) {
115 const size_t alloc_size = HandlerDataSize(num_protected_instructions);
116 CodeProtectionInfo* data =
117 reinterpret_cast<CodeProtectionInfo*>(malloc(alloc_size));
118
119 if (data == nullptr) {
120 return nullptr;
121 }
122
123 data->base = base;
124 data->size = size;
125 data->num_protected_instructions = num_protected_instructions;
126
127 memcpy(data->instructions, protected_instructions,
128 num_protected_instructions * sizeof(ProtectedInstructionData));
129
130 return data;
131 }
132
RegisterHandlerData(Address base,size_t size,size_t num_protected_instructions,const ProtectedInstructionData * protected_instructions)133 int RegisterHandlerData(
134 Address base, size_t size, size_t num_protected_instructions,
135 const ProtectedInstructionData* protected_instructions) {
136
137 CodeProtectionInfo* data = CreateHandlerData(
138 base, size, num_protected_instructions, protected_instructions);
139
140 if (data == nullptr) {
141 abort();
142 }
143
144 MetadataLock lock;
145
146 if (kEnableSlowChecks) {
147 VerifyCodeRangeIsDisjoint(data);
148 }
149
150 size_t i = gNextCodeObject;
151
152 // Explicitly convert std::numeric_limits<int>::max() to unsigned to avoid
153 // compiler warnings about signed/unsigned comparisons. We aren't worried
154 // about sign extension because we know std::numeric_limits<int>::max() is
155 // positive.
156 const size_t int_max = std::numeric_limits<int>::max();
157
158 // We didn't find an opening in the available space, so grow.
159 if (i == gNumCodeObjects) {
160 size_t new_size = gNumCodeObjects > 0
161 ? gNumCodeObjects * kCodeObjectGrowthFactor
162 : kInitialCodeObjectSize;
163
164 // Because we must return an int, there is no point in allocating space for
165 // more objects than can fit in an int.
166 if (new_size > int_max) {
167 new_size = int_max;
168 }
169 if (new_size == gNumCodeObjects) {
170 free(data);
171 return kInvalidIndex;
172 }
173
174 // Now that we know our new size is valid, we can go ahead and realloc the
175 // array.
176 gCodeObjects = static_cast<CodeProtectionInfoListEntry*>(
177 realloc(gCodeObjects, sizeof(*gCodeObjects) * new_size));
178
179 if (gCodeObjects == nullptr) {
180 abort();
181 }
182
183 memset(gCodeObjects + gNumCodeObjects, 0,
184 sizeof(*gCodeObjects) * (new_size - gNumCodeObjects));
185 for (size_t j = gNumCodeObjects; j < new_size; ++j) {
186 gCodeObjects[j].next_free = j + 1;
187 }
188 gNumCodeObjects = new_size;
189 }
190
191 DCHECK(gCodeObjects[i].code_info == nullptr);
192
193 // Find out where the next entry should go.
194 gNextCodeObject = gCodeObjects[i].next_free;
195
196 if (i <= int_max) {
197 gCodeObjects[i].code_info = data;
198
199 if (kEnableSlowChecks) {
200 ValidateCodeObjects();
201 }
202
203 return static_cast<int>(i);
204 } else {
205 free(data);
206 return kInvalidIndex;
207 }
208 }
209
ReleaseHandlerData(int index)210 void ReleaseHandlerData(int index) {
211 if (index == kInvalidIndex) {
212 return;
213 }
214 DCHECK_GE(index, 0);
215
216 // Remove the data from the global list if it's there.
217 CodeProtectionInfo* data = nullptr;
218 {
219 MetadataLock lock;
220
221 data = gCodeObjects[index].code_info;
222 gCodeObjects[index].code_info = nullptr;
223
224 gCodeObjects[index].next_free = gNextCodeObject;
225 gNextCodeObject = index;
226
227 if (kEnableSlowChecks) {
228 ValidateCodeObjects();
229 }
230 }
231 // TODO(eholk): on debug builds, ensure there are no more copies in
232 // the list.
233 DCHECK_NOT_NULL(data); // make sure we're releasing legitimate handler data.
234 free(data);
235 }
236
GetThreadInWasmThreadLocalAddress()237 int* GetThreadInWasmThreadLocalAddress() { return &g_thread_in_wasm_code; }
238
GetRecoveredTrapCount()239 size_t GetRecoveredTrapCount() {
240 return gRecoveredTrapCount.load(std::memory_order_relaxed);
241 }
242
243 #if !V8_TRAP_HANDLER_SUPPORTED
244 // This version is provided for systems that do not support trap handlers.
245 // Otherwise, the correct one should be implemented in the appropriate
246 // platform-specific handler-outside.cc.
RegisterDefaultTrapHandler()247 bool RegisterDefaultTrapHandler() { return false; }
248
RemoveTrapHandler()249 void RemoveTrapHandler() {}
250 #endif
251
252 bool g_is_trap_handler_enabled{false};
253 std::atomic<bool> g_can_enable_trap_handler{true};
254
EnableTrapHandler(bool use_v8_handler)255 bool EnableTrapHandler(bool use_v8_handler) {
256 // We should only enable the trap handler once, and before any call to
257 // {IsTrapHandlerEnabled}. Enabling the trap handler late can lead to problems
258 // because code or objects might have been generated under the assumption that
259 // trap handlers are disabled.
260 bool can_enable =
261 g_can_enable_trap_handler.exchange(false, std::memory_order_relaxed);
262 if (!can_enable) {
263 FATAL("EnableTrapHandler called twice, or after IsTrapHandlerEnabled");
264 }
265 if (!V8_TRAP_HANDLER_SUPPORTED) {
266 return false;
267 }
268 if (use_v8_handler) {
269 g_is_trap_handler_enabled = RegisterDefaultTrapHandler();
270 return g_is_trap_handler_enabled;
271 }
272 g_is_trap_handler_enabled = true;
273 return true;
274 }
275
276 } // namespace trap_handler
277 } // namespace internal
278 } // namespace v8
279