| 1 | //===-- segv_handler_posix.cpp ----------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "gwp_asan/common.h" |
| 10 | #include "gwp_asan/crash_handler.h" |
| 11 | #include "gwp_asan/guarded_pool_allocator.h" |
| 12 | #include "gwp_asan/optional/segv_handler.h" |
| 13 | #include "gwp_asan/options.h" |
| 14 | |
| 15 | // RHEL creates the PRIu64 format macro (for printing uint64_t's) only when this |
| 16 | // macro is defined before including <inttypes.h>. |
| 17 | #ifndef __STDC_FORMAT_MACROS |
| 18 | #define __STDC_FORMAT_MACROS 1 |
| 19 | #endif |
| 20 | |
| 21 | #include <assert.h> |
| 22 | #include <inttypes.h> |
| 23 | #include <signal.h> |
| 24 | #include <stdio.h> |
| 25 | |
| 26 | using gwp_asan::AllocationMetadata; |
| 27 | using gwp_asan::Error; |
| 28 | using gwp_asan::GuardedPoolAllocator; |
| 29 | using gwp_asan::Printf_t; |
| 30 | using gwp_asan::backtrace::PrintBacktrace_t; |
| 31 | using gwp_asan::backtrace::SegvBacktrace_t; |
| 32 | |
| 33 | namespace { |
| 34 | |
| 35 | struct ScopedEndOfReportDecorator { |
| 36 | ScopedEndOfReportDecorator(gwp_asan::Printf_t Printf) : Printf(Printf) {} |
| 37 | ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n" ); } |
| 38 | gwp_asan::Printf_t Printf; |
| 39 | }; |
| 40 | |
| 41 | // Prints the provided error and metadata information. |
| 42 | void (Error E, uintptr_t AccessPtr, |
| 43 | const gwp_asan::AllocationMetadata *Metadata, |
| 44 | Printf_t Printf) { |
| 45 | // Print using intermediate strings. Platforms like Android don't like when |
| 46 | // you print multiple times to the same line, as there may be a newline |
| 47 | // appended to a log file automatically per Printf() call. |
| 48 | constexpr size_t kDescriptionBufferLen = 128; |
| 49 | char DescriptionBuffer[kDescriptionBufferLen] = "" ; |
| 50 | |
| 51 | bool AccessWasInBounds = false; |
| 52 | if (E != Error::UNKNOWN && Metadata != nullptr) { |
| 53 | uintptr_t Address = __gwp_asan_get_allocation_address(AllocationMeta: Metadata); |
| 54 | size_t Size = __gwp_asan_get_allocation_size(AllocationMeta: Metadata); |
| 55 | if (AccessPtr < Address) { |
| 56 | snprintf(s: DescriptionBuffer, maxlen: kDescriptionBufferLen, |
| 57 | format: "(%zu byte%s to the left of a %zu-byte allocation at 0x%zx) " , |
| 58 | Address - AccessPtr, (Address - AccessPtr == 1) ? "" : "s" , Size, |
| 59 | Address); |
| 60 | } else if (AccessPtr > Address) { |
| 61 | snprintf(s: DescriptionBuffer, maxlen: kDescriptionBufferLen, |
| 62 | format: "(%zu byte%s to the right of a %zu-byte allocation at 0x%zx) " , |
| 63 | AccessPtr - Address, (AccessPtr - Address == 1) ? "" : "s" , Size, |
| 64 | Address); |
| 65 | } else if (E == Error::DOUBLE_FREE) { |
| 66 | snprintf(s: DescriptionBuffer, maxlen: kDescriptionBufferLen, |
| 67 | format: "(a %zu-byte allocation) " , Size); |
| 68 | } else { |
| 69 | AccessWasInBounds = true; |
| 70 | snprintf(s: DescriptionBuffer, maxlen: kDescriptionBufferLen, |
| 71 | format: "(%zu byte%s into a %zu-byte allocation at 0x%zx) " , |
| 72 | AccessPtr - Address, (AccessPtr - Address == 1) ? "" : "s" , Size, |
| 73 | Address); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // Possible number of digits of a 64-bit number: ceil(log10(2^64)) == 20. Add |
| 78 | // a null terminator, and round to the nearest 8-byte boundary. |
| 79 | uint64_t ThreadID = gwp_asan::getThreadID(); |
| 80 | constexpr size_t kThreadBufferLen = 24; |
| 81 | char ThreadBuffer[kThreadBufferLen]; |
| 82 | if (ThreadID == gwp_asan::kInvalidThreadID) |
| 83 | snprintf(s: ThreadBuffer, maxlen: kThreadBufferLen, format: "<unknown>" ); |
| 84 | else |
| 85 | snprintf(s: ThreadBuffer, maxlen: kThreadBufferLen, format: "%" PRIu64, ThreadID); |
| 86 | |
| 87 | const char *OutOfBoundsAndUseAfterFreeWarning = "" ; |
| 88 | if (E == Error::USE_AFTER_FREE && !AccessWasInBounds) { |
| 89 | OutOfBoundsAndUseAfterFreeWarning = |
| 90 | " (warning: buffer overflow/underflow detected on a free()'d " |
| 91 | "allocation. This either means you have a buffer-overflow and a " |
| 92 | "use-after-free at the same time, or you have a long-lived " |
| 93 | "use-after-free bug where the allocation/deallocation metadata below " |
| 94 | "has already been overwritten and is likely bogus)" ; |
| 95 | } |
| 96 | |
| 97 | Printf("%s%s at 0x%zx %sby thread %s here:\n" , gwp_asan::ErrorToString(E), |
| 98 | OutOfBoundsAndUseAfterFreeWarning, AccessPtr, DescriptionBuffer, |
| 99 | ThreadBuffer); |
| 100 | } |
| 101 | |
| 102 | static bool HasReportedBadPoolAccess = false; |
| 103 | static const char *kUnknownCrashText = |
| 104 | "GWP-ASan cannot provide any more information about this error. This may " |
| 105 | "occur due to a wild memory access into the GWP-ASan pool, or an " |
| 106 | "overflow/underflow that is > 512B in length.\n" ; |
| 107 | |
| 108 | void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State, |
| 109 | const gwp_asan::AllocationMetadata *Metadata, |
| 110 | SegvBacktrace_t SegvBacktrace, Printf_t Printf, |
| 111 | PrintBacktrace_t PrintBacktrace, void *Context) { |
| 112 | assert(State && "dumpReport missing Allocator State." ); |
| 113 | assert(Metadata && "dumpReport missing Metadata." ); |
| 114 | assert(Printf && "dumpReport missing Printf." ); |
| 115 | assert(__gwp_asan_error_is_mine(State, ErrorPtr) && |
| 116 | "dumpReport() called on a non-GWP-ASan error." ); |
| 117 | |
| 118 | uintptr_t InternalErrorPtr = |
| 119 | __gwp_asan_get_internal_crash_address(State, ErrorPtr); |
| 120 | if (InternalErrorPtr) |
| 121 | ErrorPtr = InternalErrorPtr; |
| 122 | |
| 123 | const gwp_asan::AllocationMetadata *AllocMeta = |
| 124 | __gwp_asan_get_metadata(State, Metadata, ErrorPtr); |
| 125 | |
| 126 | if (AllocMeta == nullptr) { |
| 127 | if (HasReportedBadPoolAccess) return; |
| 128 | HasReportedBadPoolAccess = true; |
| 129 | Printf("*** GWP-ASan detected a memory error ***\n" ); |
| 130 | ScopedEndOfReportDecorator Decorator(Printf); |
| 131 | Printf(kUnknownCrashText); |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | // It's unusual for a signal handler to be invoked multiple times for the same |
| 136 | // allocation, but it's possible in various scenarios, like: |
| 137 | // 1. A double-free or invalid-free was invoked in one thread at the same |
| 138 | // time as a buffer-overflow or use-after-free in another thread, or |
| 139 | // 2. Two threads do a use-after-free or buffer-overflow at the same time. |
| 140 | // In these instances, we've already dumped a report for this allocation, so |
| 141 | // skip dumping this issue as well. |
| 142 | if (AllocMeta->HasCrashed) |
| 143 | return; |
| 144 | |
| 145 | Printf("*** GWP-ASan detected a memory error ***\n" ); |
| 146 | ScopedEndOfReportDecorator Decorator(Printf); |
| 147 | |
| 148 | Error E = __gwp_asan_diagnose_error(State, Metadata, ErrorPtr); |
| 149 | if (E == Error::UNKNOWN) { |
| 150 | Printf(kUnknownCrashText); |
| 151 | return; |
| 152 | } |
| 153 | |
| 154 | // Print the error header. |
| 155 | printHeader(E, AccessPtr: ErrorPtr, Metadata: AllocMeta, Printf); |
| 156 | |
| 157 | // Print the fault backtrace. |
| 158 | static constexpr unsigned kMaximumStackFramesForCrashTrace = 512; |
| 159 | uintptr_t Trace[kMaximumStackFramesForCrashTrace]; |
| 160 | size_t TraceLength = |
| 161 | SegvBacktrace(Trace, kMaximumStackFramesForCrashTrace, Context); |
| 162 | |
| 163 | PrintBacktrace(Trace, TraceLength, Printf); |
| 164 | |
| 165 | // Maybe print the deallocation trace. |
| 166 | if (__gwp_asan_is_deallocated(AllocationMeta: AllocMeta)) { |
| 167 | uint64_t ThreadID = __gwp_asan_get_deallocation_thread_id(AllocationMeta: AllocMeta); |
| 168 | if (ThreadID == gwp_asan::kInvalidThreadID) |
| 169 | Printf("0x%zx was deallocated by thread <unknown> here:\n" , ErrorPtr); |
| 170 | else |
| 171 | Printf("0x%zx was deallocated by thread %zu here:\n" , ErrorPtr, ThreadID); |
| 172 | TraceLength = __gwp_asan_get_deallocation_trace( |
| 173 | AllocationMeta: AllocMeta, Buffer: Trace, BufferLen: kMaximumStackFramesForCrashTrace); |
| 174 | PrintBacktrace(Trace, TraceLength, Printf); |
| 175 | } |
| 176 | |
| 177 | // Print the allocation trace. |
| 178 | uint64_t ThreadID = __gwp_asan_get_allocation_thread_id(AllocationMeta: AllocMeta); |
| 179 | if (ThreadID == gwp_asan::kInvalidThreadID) |
| 180 | Printf("0x%zx was allocated by thread <unknown> here:\n" , ErrorPtr); |
| 181 | else |
| 182 | Printf("0x%zx was allocated by thread %zu here:\n" , ErrorPtr, ThreadID); |
| 183 | TraceLength = __gwp_asan_get_allocation_trace( |
| 184 | AllocationMeta: AllocMeta, Buffer: Trace, BufferLen: kMaximumStackFramesForCrashTrace); |
| 185 | PrintBacktrace(Trace, TraceLength, Printf); |
| 186 | } |
| 187 | |
| 188 | struct sigaction PreviousHandler; |
| 189 | bool SignalHandlerInstalled; |
| 190 | bool RecoverableSignal; |
| 191 | gwp_asan::GuardedPoolAllocator *GPAForSignalHandler; |
| 192 | Printf_t PrintfForSignalHandler; |
| 193 | PrintBacktrace_t PrintBacktraceForSignalHandler; |
| 194 | SegvBacktrace_t BacktraceForSignalHandler; |
| 195 | |
| 196 | static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) { |
| 197 | const gwp_asan::AllocatorState *State = |
| 198 | GPAForSignalHandler->getAllocatorState(); |
| 199 | void *FaultAddr = info->si_addr; |
| 200 | uintptr_t FaultAddrUPtr = reinterpret_cast<uintptr_t>(FaultAddr); |
| 201 | |
| 202 | if (__gwp_asan_error_is_mine(State, ErrorPtr: FaultAddrUPtr)) { |
| 203 | GPAForSignalHandler->preCrashReport(Ptr: FaultAddr); |
| 204 | |
| 205 | dumpReport(ErrorPtr: FaultAddrUPtr, State, Metadata: GPAForSignalHandler->getMetadataRegion(), |
| 206 | SegvBacktrace: BacktraceForSignalHandler, Printf: PrintfForSignalHandler, |
| 207 | PrintBacktrace: PrintBacktraceForSignalHandler, Context: ucontext); |
| 208 | |
| 209 | if (RecoverableSignal) { |
| 210 | GPAForSignalHandler->postCrashReportRecoverableOnly(Ptr: FaultAddr); |
| 211 | return; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | // Process any previous handlers as long as the crash wasn't a GWP-ASan crash |
| 216 | // in recoverable mode. |
| 217 | if (PreviousHandler.sa_flags & SA_SIGINFO) { |
| 218 | PreviousHandler.sa_sigaction(sig, info, ucontext); |
| 219 | } else if (PreviousHandler.sa_handler == SIG_DFL) { |
| 220 | // If the previous handler was the default handler, cause a core dump. |
| 221 | signal(SIGSEGV, SIG_DFL); |
| 222 | raise(SIGSEGV); |
| 223 | } else if (PreviousHandler.sa_handler == SIG_IGN) { |
| 224 | // If the previous segv handler was SIGIGN, crash iff we were responsible |
| 225 | // for the crash. |
| 226 | if (__gwp_asan_error_is_mine(State: GPAForSignalHandler->getAllocatorState(), |
| 227 | ErrorPtr: reinterpret_cast<uintptr_t>(info->si_addr))) { |
| 228 | signal(SIGSEGV, SIG_DFL); |
| 229 | raise(SIGSEGV); |
| 230 | } |
| 231 | } else { |
| 232 | PreviousHandler.sa_handler(sig); |
| 233 | } |
| 234 | } |
| 235 | } // anonymous namespace |
| 236 | |
| 237 | namespace gwp_asan { |
| 238 | namespace segv_handler { |
| 239 | |
| 240 | void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf, |
| 241 | PrintBacktrace_t PrintBacktrace, |
| 242 | SegvBacktrace_t SegvBacktrace, bool Recoverable) { |
| 243 | assert(GPA && "GPA wasn't provided to installSignalHandlers." ); |
| 244 | assert(Printf && "Printf wasn't provided to installSignalHandlers." ); |
| 245 | assert(PrintBacktrace && |
| 246 | "PrintBacktrace wasn't provided to installSignalHandlers." ); |
| 247 | assert(SegvBacktrace && |
| 248 | "SegvBacktrace wasn't provided to installSignalHandlers." ); |
| 249 | GPAForSignalHandler = GPA; |
| 250 | PrintfForSignalHandler = Printf; |
| 251 | PrintBacktraceForSignalHandler = PrintBacktrace; |
| 252 | BacktraceForSignalHandler = SegvBacktrace; |
| 253 | RecoverableSignal = Recoverable; |
| 254 | |
| 255 | struct sigaction Action = {}; |
| 256 | Action.sa_sigaction = sigSegvHandler; |
| 257 | Action.sa_flags = SA_SIGINFO; |
| 258 | sigaction(SIGSEGV, act: &Action, oact: &PreviousHandler); |
| 259 | SignalHandlerInstalled = true; |
| 260 | HasReportedBadPoolAccess = false; |
| 261 | } |
| 262 | |
| 263 | void uninstallSignalHandlers() { |
| 264 | if (SignalHandlerInstalled) { |
| 265 | sigaction(SIGSEGV, act: &PreviousHandler, oact: nullptr); |
| 266 | SignalHandlerInstalled = false; |
| 267 | } |
| 268 | } |
| 269 | } // namespace segv_handler |
| 270 | } // namespace gwp_asan |
| 271 | |