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
33namespace {
34size_t gNextCodeObject = 0;
35
36#ifdef ENABLE_SLOW_DCHECKS
37constexpr bool kEnableSlowChecks = true;
38#else
39constexpr bool kEnableSlowChecks = false;
40#endif
41}  // namespace
42
43namespace v8 {
44namespace internal {
45namespace trap_handler {
46
47constexpr size_t kInitialCodeObjectSize = 1024;
48constexpr size_t kCodeObjectGrowthFactor = 2;
49
50constexpr size_t HandlerDataSize(size_t num_protected_instructions) {
51  return offsetof(CodeProtectionInfo, instructions) +
52         num_protected_instructions * sizeof(ProtectedInstructionData);
53}
54
55namespace {
56#ifdef DEBUG
57bool 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.
67void VerifyCodeRangeIsDisjoint(const CodeProtectionInfo* code_info) {
68  for (size_t i = 0; i < gNumCodeObjects; ++i) {
69    TH_DCHECK(IsDisjoint(code_info, gCodeObjects[i].code_info));
70  }
71}
72
73void 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 j = 0; j < data->num_protected_instructions; ++j) {
82      TH_DCHECK(data->instructions[j].instr_offset >= 0);
83      TH_DCHECK(data->instructions[j].instr_offset < data->size);
84      TH_DCHECK(data->instructions[j].landing_offset >= 0);
85      TH_DCHECK(data->instructions[j].landing_offset < data->size);
86      TH_DCHECK(data->instructions[j].landing_offset >
87                data->instructions[j].instr_offset);
88    }
89  }
90
91  // Check the validity of the free list.
92#ifdef DEBUG
93  size_t free_count = 0;
94  for (size_t i = gNextCodeObject; i != gNumCodeObjects;
95       i = gCodeObjects[i].next_free) {
96    TH_DCHECK(i < gNumCodeObjects);
97    ++free_count;
98    // This check will fail if we encounter a cycle.
99    TH_DCHECK(free_count <= gNumCodeObjects);
100  }
101
102  // Check that all free entries are reachable via the free list.
103  size_t free_count2 = 0;
104  for (size_t i = 0; i < gNumCodeObjects; ++i) {
105    if (gCodeObjects[i].code_info == nullptr) {
106      ++free_count2;
107    }
108  }
109  TH_DCHECK(free_count == free_count2);
110#endif
111}
112}  // namespace
113
114CodeProtectionInfo* CreateHandlerData(
115    uintptr_t base, size_t size, size_t num_protected_instructions,
116    const ProtectedInstructionData* protected_instructions) {
117  const size_t alloc_size = HandlerDataSize(num_protected_instructions);
118  CodeProtectionInfo* data =
119      reinterpret_cast<CodeProtectionInfo*>(malloc(alloc_size));
120
121  if (data == nullptr) {
122    return nullptr;
123  }
124
125  data->base = base;
126  data->size = size;
127  data->num_protected_instructions = num_protected_instructions;
128
129  memcpy(data->instructions, protected_instructions,
130         num_protected_instructions * sizeof(ProtectedInstructionData));
131
132  return data;
133}
134
135int RegisterHandlerData(
136    uintptr_t base, size_t size, size_t num_protected_instructions,
137    const ProtectedInstructionData* protected_instructions) {
138  CodeProtectionInfo* data = CreateHandlerData(
139      base, size, num_protected_instructions, protected_instructions);
140
141  if (data == nullptr) {
142    abort();
143  }
144
145  MetadataLock lock;
146
147  if (kEnableSlowChecks) {
148    VerifyCodeRangeIsDisjoint(data);
149  }
150
151  size_t i = gNextCodeObject;
152
153  // Explicitly convert std::numeric_limits<int>::max() to unsigned to avoid
154  // compiler warnings about signed/unsigned comparisons. We aren't worried
155  // about sign extension because we know std::numeric_limits<int>::max() is
156  // positive.
157  const size_t int_max = std::numeric_limits<int>::max();
158
159  // We didn't find an opening in the available space, so grow.
160  if (i == gNumCodeObjects) {
161    size_t new_size = gNumCodeObjects > 0
162                          ? gNumCodeObjects * kCodeObjectGrowthFactor
163                          : kInitialCodeObjectSize;
164
165    // Because we must return an int, there is no point in allocating space for
166    // more objects than can fit in an int.
167    if (new_size > int_max) {
168      new_size = int_max;
169    }
170    if (new_size == gNumCodeObjects) {
171      free(data);
172      return kInvalidIndex;
173    }
174
175    // Now that we know our new size is valid, we can go ahead and realloc the
176    // array.
177    gCodeObjects = static_cast<CodeProtectionInfoListEntry*>(
178        realloc(gCodeObjects, sizeof(*gCodeObjects) * new_size));
179
180    if (gCodeObjects == nullptr) {
181      abort();
182    }
183
184    memset(gCodeObjects + gNumCodeObjects, 0,
185           sizeof(*gCodeObjects) * (new_size - gNumCodeObjects));
186    for (size_t j = gNumCodeObjects; j < new_size; ++j) {
187      gCodeObjects[j].next_free = j + 1;
188    }
189    gNumCodeObjects = new_size;
190  }
191
192  TH_DCHECK(gCodeObjects[i].code_info == nullptr);
193
194  // Find out where the next entry should go.
195  gNextCodeObject = gCodeObjects[i].next_free;
196
197  if (i <= int_max) {
198    gCodeObjects[i].code_info = data;
199
200    if (kEnableSlowChecks) {
201      ValidateCodeObjects();
202    }
203
204    return static_cast<int>(i);
205  } else {
206    free(data);
207    return kInvalidIndex;
208  }
209}
210
211void ReleaseHandlerData(int index) {
212  if (index == kInvalidIndex) {
213    return;
214  }
215  TH_DCHECK(index >= 0);
216
217  // Remove the data from the global list if it's there.
218  CodeProtectionInfo* data = nullptr;
219  {
220    MetadataLock lock;
221
222    data = gCodeObjects[index].code_info;
223    gCodeObjects[index].code_info = nullptr;
224
225    gCodeObjects[index].next_free = gNextCodeObject;
226    gNextCodeObject = index;
227
228    if (kEnableSlowChecks) {
229      ValidateCodeObjects();
230    }
231  }
232  // TODO(eholk): on debug builds, ensure there are no more copies in
233  // the list.
234  TH_DCHECK(data);  // make sure we're releasing legitimate handler data.
235  free(data);
236}
237
238int* GetThreadInWasmThreadLocalAddress() { return &g_thread_in_wasm_code; }
239
240size_t GetRecoveredTrapCount() {
241  return gRecoveredTrapCount.load(std::memory_order_relaxed);
242}
243
244#if !V8_TRAP_HANDLER_SUPPORTED
245// This version is provided for systems that do not support trap handlers.
246// Otherwise, the correct one should be implemented in the appropriate
247// platform-specific handler-outside.cc.
248bool RegisterDefaultTrapHandler() { return false; }
249
250void RemoveTrapHandler() {}
251#endif
252
253bool g_is_trap_handler_enabled{false};
254std::atomic<bool> g_can_enable_trap_handler{true};
255
256bool EnableTrapHandler(bool use_v8_handler) {
257  // We should only enable the trap handler once, and before any call to
258  // {IsTrapHandlerEnabled}. Enabling the trap handler late can lead to problems
259  // because code or objects might have been generated under the assumption that
260  // trap handlers are disabled.
261  bool can_enable =
262      g_can_enable_trap_handler.exchange(false, std::memory_order_relaxed);
263  // EnableTrapHandler called twice, or after IsTrapHandlerEnabled.
264  TH_CHECK(can_enable);
265
266  if (!V8_TRAP_HANDLER_SUPPORTED) {
267    return false;
268  }
269  if (use_v8_handler) {
270    g_is_trap_handler_enabled = RegisterDefaultTrapHandler();
271    return g_is_trap_handler_enabled;
272  }
273  g_is_trap_handler_enabled = true;
274  return true;
275}
276
277}  // namespace trap_handler
278}  // namespace internal
279}  // namespace v8
280