handler-outside.cc 8.21 KB
Newer Older
eholk's avatar
eholk committed
1 2 3 4 5 6
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// PLEASE READ BEFORE CHANGING THIS FILE!
//
7 8 9
// This file implements the support code for the out of bounds trap handler.
// Nothing in here actually runs in the trap handler, but the code here
// manipulates data structures used by the trap handler so we still need to be
eholk's avatar
eholk committed
10 11 12 13 14 15
// careful. In order to minimize this risk, here are some rules to follow.
//
// 1. Avoid introducing new external dependencies. The files in src/trap-handler
//    should be as self-contained as possible to make it easy to audit the code.
//
// 2. Any changes must be reviewed by someone from the crash reporting
16
//    or security team. See OWNERS for suggested reviewers.
eholk's avatar
eholk committed
17 18 19
//
// For more information, see https://goo.gl/yMeyUY.
//
20
// For the code that runs in the trap handler itself, see handler-inside.cc.
eholk's avatar
eholk committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <atomic>
#include <limits>

#include "src/trap-handler/trap-handler-internal.h"
#include "src/trap-handler/trap-handler.h"

namespace {
size_t gNextCodeObject = 0;
35

36 37
#ifdef ENABLE_SLOW_DCHECKS
constexpr bool kEnableSlowChecks = true;
38
#else
39
constexpr bool kEnableSlowChecks = false;
40
#endif
41
}  // namespace
eholk's avatar
eholk committed
42 43 44 45 46

namespace v8 {
namespace internal {
namespace trap_handler {

47 48
constexpr size_t kInitialCodeObjectSize = 1024;
constexpr size_t kCodeObjectGrowthFactor = 2;
eholk's avatar
eholk committed
49 50 51 52 53 54

constexpr size_t HandlerDataSize(size_t num_protected_instructions) {
  return offsetof(CodeProtectionInfo, instructions) +
         num_protected_instructions * sizeof(ProtectedInstructionData);
}

55
namespace {
56
#ifdef DEBUG
57 58 59 60
bool IsDisjoint(const CodeProtectionInfo* a, const CodeProtectionInfo* b) {
  if (a == nullptr || b == nullptr) {
    return true;
  }
61
  return a->base >= b->base + b->size || b->base >= a->base + a->size;
62
}
63
#endif
64 65 66 67 68

// Verify that the code range does not overlap any that have already been
// registered.
void VerifyCodeRangeIsDisjoint(const CodeProtectionInfo* code_info) {
  for (size_t i = 0; i < gNumCodeObjects; ++i) {
69
    TH_DCHECK(IsDisjoint(code_info, gCodeObjects[i].code_info));
70 71 72 73 74 75 76 77 78 79 80
  }
}

void ValidateCodeObjects() {
  // Sanity-check the code objects
  for (unsigned i = 0; i < gNumCodeObjects; ++i) {
    const auto* data = gCodeObjects[i].code_info;

    if (data == nullptr) continue;

    // Do some sanity checks on the protected instruction data
81 82 83 84 85 86 87
    for (unsigned j = 0; j < data->num_protected_instructions; ++j) {
      TH_DCHECK(data->instructions[j].instr_offset >= 0);
      TH_DCHECK(data->instructions[j].instr_offset < data->size);
      TH_DCHECK(data->instructions[j].landing_offset >= 0);
      TH_DCHECK(data->instructions[j].landing_offset < data->size);
      TH_DCHECK(data->instructions[j].landing_offset >
                data->instructions[j].instr_offset);
88 89 90 91
    }
  }

  // Check the validity of the free list.
92
#ifdef DEBUG
93 94 95
  size_t free_count = 0;
  for (size_t i = gNextCodeObject; i != gNumCodeObjects;
       i = gCodeObjects[i].next_free) {
96
    TH_DCHECK(i < gNumCodeObjects);
97 98
    ++free_count;
    // This check will fail if we encounter a cycle.
99
    TH_DCHECK(free_count <= gNumCodeObjects);
100 101 102 103 104 105 106 107 108
  }

  // Check that all free entries are reachable via the free list.
  size_t free_count2 = 0;
  for (size_t i = 0; i < gNumCodeObjects; ++i) {
    if (gCodeObjects[i].code_info == nullptr) {
      ++free_count2;
    }
  }
109
  TH_DCHECK(free_count == free_count2);
110
#endif
111 112 113
}
}  // namespace

eholk's avatar
eholk committed
114
CodeProtectionInfo* CreateHandlerData(
115
    uintptr_t base, size_t size, size_t num_protected_instructions,
116
    const ProtectedInstructionData* protected_instructions) {
eholk's avatar
eholk committed
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
  const size_t alloc_size = HandlerDataSize(num_protected_instructions);
  CodeProtectionInfo* data =
      reinterpret_cast<CodeProtectionInfo*>(malloc(alloc_size));

  if (data == nullptr) {
    return nullptr;
  }

  data->base = base;
  data->size = size;
  data->num_protected_instructions = num_protected_instructions;

  memcpy(data->instructions, protected_instructions,
         num_protected_instructions * sizeof(ProtectedInstructionData));

  return data;
}

135
int RegisterHandlerData(
136
    uintptr_t base, size_t size, size_t num_protected_instructions,
137
    const ProtectedInstructionData* protected_instructions) {
eholk's avatar
eholk committed
138 139 140 141 142 143 144 145 146
  CodeProtectionInfo* data = CreateHandlerData(
      base, size, num_protected_instructions, protected_instructions);

  if (data == nullptr) {
    abort();
  }

  MetadataLock lock;

147
  if (kEnableSlowChecks) {
148 149 150
    VerifyCodeRangeIsDisjoint(data);
  }

eholk's avatar
eholk committed
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
  size_t i = gNextCodeObject;

  // Explicitly convert std::numeric_limits<int>::max() to unsigned to avoid
  // compiler warnings about signed/unsigned comparisons. We aren't worried
  // about sign extension because we know std::numeric_limits<int>::max() is
  // positive.
  const size_t int_max = std::numeric_limits<int>::max();

  // We didn't find an opening in the available space, so grow.
  if (i == gNumCodeObjects) {
    size_t new_size = gNumCodeObjects > 0
                          ? gNumCodeObjects * kCodeObjectGrowthFactor
                          : kInitialCodeObjectSize;

    // Because we must return an int, there is no point in allocating space for
    // more objects than can fit in an int.
    if (new_size > int_max) {
      new_size = int_max;
    }
    if (new_size == gNumCodeObjects) {
171
      free(data);
172
      return kInvalidIndex;
eholk's avatar
eholk committed
173 174 175 176 177 178 179 180 181 182 183 184 185
    }

    // Now that we know our new size is valid, we can go ahead and realloc the
    // array.
    gCodeObjects = static_cast<CodeProtectionInfoListEntry*>(
        realloc(gCodeObjects, sizeof(*gCodeObjects) * new_size));

    if (gCodeObjects == nullptr) {
      abort();
    }

    memset(gCodeObjects + gNumCodeObjects, 0,
           sizeof(*gCodeObjects) * (new_size - gNumCodeObjects));
186 187 188
    for (size_t j = gNumCodeObjects; j < new_size; ++j) {
      gCodeObjects[j].next_free = j + 1;
    }
eholk's avatar
eholk committed
189 190 191
    gNumCodeObjects = new_size;
  }

192
  TH_DCHECK(gCodeObjects[i].code_info == nullptr);
eholk's avatar
eholk committed
193 194

  // Find out where the next entry should go.
195
  gNextCodeObject = gCodeObjects[i].next_free;
eholk's avatar
eholk committed
196 197 198

  if (i <= int_max) {
    gCodeObjects[i].code_info = data;
199

200
    if (kEnableSlowChecks) {
201 202 203
      ValidateCodeObjects();
    }

eholk's avatar
eholk committed
204 205
    return static_cast<int>(i);
  } else {
206
    free(data);
207
    return kInvalidIndex;
eholk's avatar
eholk committed
208 209 210 211
  }
}

void ReleaseHandlerData(int index) {
212 213 214
  if (index == kInvalidIndex) {
    return;
  }
215
  TH_DCHECK(index >= 0);
216

eholk's avatar
eholk committed
217 218 219 220 221 222 223 224
  // Remove the data from the global list if it's there.
  CodeProtectionInfo* data = nullptr;
  {
    MetadataLock lock;

    data = gCodeObjects[index].code_info;
    gCodeObjects[index].code_info = nullptr;

225
    gCodeObjects[index].next_free = gNextCodeObject;
eholk's avatar
eholk committed
226
    gNextCodeObject = index;
227

228
    if (kEnableSlowChecks) {
229 230
      ValidateCodeObjects();
    }
eholk's avatar
eholk committed
231 232 233
  }
  // TODO(eholk): on debug builds, ensure there are no more copies in
  // the list.
234
  TH_DCHECK(data);  // make sure we're releasing legitimate handler data.
eholk's avatar
eholk committed
235 236 237
  free(data);
}

238 239
int* GetThreadInWasmThreadLocalAddress() { return &g_thread_in_wasm_code; }

240 241 242 243
size_t GetRecoveredTrapCount() {
  return gRecoveredTrapCount.load(std::memory_order_relaxed);
}

244 245 246 247 248
#if !V8_TRAP_HANDLER_SUPPORTED
// This version is provided for systems that do not support trap handlers.
// Otherwise, the correct one should be implemented in the appropriate
// platform-specific handler-outside.cc.
bool RegisterDefaultTrapHandler() { return false; }
249 250

void RemoveTrapHandler() {}
251 252
#endif

253 254
bool g_is_trap_handler_enabled{false};
std::atomic<bool> g_can_enable_trap_handler{true};
255

256
bool EnableTrapHandler(bool use_v8_handler) {
257 258 259 260 261 262
  // We should only enable the trap handler once, and before any call to
  // {IsTrapHandlerEnabled}. Enabling the trap handler late can lead to problems
  // because code or objects might have been generated under the assumption that
  // trap handlers are disabled.
  bool can_enable =
      g_can_enable_trap_handler.exchange(false, std::memory_order_relaxed);
263 264 265
  // EnableTrapHandler called twice, or after IsTrapHandlerEnabled.
  TH_CHECK(can_enable);

266 267 268
  if (!V8_TRAP_HANDLER_SUPPORTED) {
    return false;
  }
269
  if (use_v8_handler) {
270
    g_is_trap_handler_enabled = RegisterDefaultTrapHandler();
271 272 273 274 275 276
    return g_is_trap_handler_enabled;
  }
  g_is_trap_handler_enabled = true;
  return true;
}

eholk's avatar
eholk committed
277 278 279
}  // namespace trap_handler
}  // namespace internal
}  // namespace v8