handler-outside.cc 7.66 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
eholk's avatar
eholk committed
41 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 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111

// 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) {
    DCHECK(IsDisjoint(code_info, gCodeObjects[i].code_info));
  }
}

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
    for (unsigned i = 0; i < data->num_protected_instructions; ++i) {
      DCHECK_GE(data->instructions[i].instr_offset, 0);
      DCHECK_LT(data->instructions[i].instr_offset, data->size);
      DCHECK_GE(data->instructions[i].landing_offset, 0);
      DCHECK_LT(data->instructions[i].landing_offset, data->size);
      DCHECK_GT(data->instructions[i].landing_offset,
                data->instructions[i].instr_offset);
    }
  }

  // Check the validity of the free list.
  size_t free_count = 0;
  for (size_t i = gNextCodeObject; i != gNumCodeObjects;
       i = gCodeObjects[i].next_free) {
    DCHECK_LT(i, gNumCodeObjects);
    ++free_count;
    // This check will fail if we encounter a cycle.
    DCHECK_LE(free_count, gNumCodeObjects);
  }

  // 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;
    }
  }
  DCHECK_EQ(free_count, free_count2);
}
}  // namespace

eholk's avatar
eholk committed
112
CodeProtectionInfo* CreateHandlerData(
113
    Address base, size_t size, size_t num_protected_instructions,
114
    const ProtectedInstructionData* protected_instructions) {
eholk's avatar
eholk committed
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
  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;
}

133
int RegisterHandlerData(
134
    Address base, size_t size, size_t num_protected_instructions,
135
    const ProtectedInstructionData* protected_instructions) {
eholk's avatar
eholk committed
136 137 138 139 140 141 142 143 144 145

  CodeProtectionInfo* data = CreateHandlerData(
      base, size, num_protected_instructions, protected_instructions);

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

  MetadataLock lock;

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

eholk's avatar
eholk committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
  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) {
170
      free(data);
171
      return kInvalidIndex;
eholk's avatar
eholk committed
172 173 174 175 176 177 178 179 180 181 182 183 184
    }

    // 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));
185 186 187
    for (size_t j = gNumCodeObjects; j < new_size; ++j) {
      gCodeObjects[j].next_free = j + 1;
    }
eholk's avatar
eholk committed
188 189 190 191 192 193
    gNumCodeObjects = new_size;
  }

  DCHECK(gCodeObjects[i].code_info == nullptr);

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

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

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

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

void ReleaseHandlerData(int index) {
211 212 213 214 215
  if (index == kInvalidIndex) {
    return;
  }
  DCHECK_GE(index, 0);

eholk's avatar
eholk committed
216 217 218 219 220 221 222 223
  // 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;

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

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

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

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

243 244 245 246 247
#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; }
248 249

void RemoveTrapHandler() {}
250 251
#endif

252 253
bool g_is_trap_handler_enabled;

254
bool EnableTrapHandler(bool use_v8_handler) {
255 256 257
  if (!V8_TRAP_HANDLER_SUPPORTED) {
    return false;
  }
258
  if (use_v8_handler) {
259
    g_is_trap_handler_enabled = RegisterDefaultTrapHandler();
260 261 262 263 264 265
    return g_is_trap_handler_enabled;
  }
  g_is_trap_handler_enabled = true;
  return true;
}

eholk's avatar
eholk committed
266 267 268
}  // namespace trap_handler
}  // namespace internal
}  // namespace v8