// 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! // // This file implements the support code for the out of bounds signal handler. // Nothing in here actually runs in the signal handler, but the code here // manipulates data structures used by the signal handler so we still need to be // 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 // or security team. Se OWNERS for suggested reviewers. // // For more information, see https://goo.gl/yMeyUY. // // For the code that runs in the signal handler itself, see handler-inside.cc. #include <signal.h> #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; } namespace v8 { namespace internal { namespace trap_handler { const size_t kInitialCodeObjectSize = 1024; const size_t kCodeObjectGrowthFactor = 2; constexpr size_t HandlerDataSize(size_t num_protected_instructions) { return offsetof(CodeProtectionInfo, instructions) + num_protected_instructions * sizeof(ProtectedInstructionData); } CodeProtectionInfo* CreateHandlerData( void* base, size_t size, size_t num_protected_instructions, ProtectedInstructionData* protected_instructions) { 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; } void UpdateHandlerDataCodePointer(int index, void* base) { MetadataLock lock; if (static_cast<size_t>(index) >= gNumCodeObjects) { abort(); } CodeProtectionInfo* data = gCodeObjects[index].code_info; data->base = base; } int RegisterHandlerData(void* base, size_t size, size_t num_protected_instructions, ProtectedInstructionData* protected_instructions) { // TODO(eholk): in debug builds, make sure this data isn't already registered. CodeProtectionInfo* data = CreateHandlerData( base, size, num_protected_instructions, protected_instructions); if (data == nullptr) { abort(); } MetadataLock lock; 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) { return -1; } // 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)); gNumCodeObjects = new_size; } DCHECK(gCodeObjects[i].code_info == nullptr); // Find out where the next entry should go. if (gCodeObjects[i].next_free == 0) { // if this is a fresh entry, use the next one. gNextCodeObject = i + 1; DCHECK(gNextCodeObject == gNumCodeObjects || (gCodeObjects[gNextCodeObject].code_info == nullptr && gCodeObjects[gNextCodeObject].next_free == 0)); } else { gNextCodeObject = gCodeObjects[i].next_free - 1; } if (i <= int_max) { gCodeObjects[i].code_info = data; return static_cast<int>(i); } else { return -1; } } void ReleaseHandlerData(int index) { // 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; // +1 because we reserve {next_entry == 0} to indicate a fresh list entry. gCodeObjects[index].next_free = gNextCodeObject + 1; gNextCodeObject = index; } // TODO(eholk): on debug builds, ensure there are no more copies in // the list. free(data); } bool RegisterDefaultSignalHandler() { #if V8_TRAP_HANDLER_SUPPORTED struct sigaction action; action.sa_sigaction = HandleSignal; action.sa_flags = SA_SIGINFO; sigemptyset(&action.sa_mask); // {sigaction} installs a new custom segfault handler. On success, it returns // 0. If we get a nonzero value, we report an error to the caller by returning // false. if (sigaction(SIGSEGV, &action, nullptr) != 0) { return false; } return true; #else return false; #endif } } // namespace trap_handler } // namespace internal } // namespace v8