• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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