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