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