| // 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 |