xref: /openbsd-src/gnu/llvm/compiler-rt/lib/gwp_asan/optional/segv_handler_posix.cpp (revision 810390e339a5425391477d5d41c78d7cab2424ac)
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 {
ScopedEndOfReportDecorator__anon647644720111::ScopedEndOfReportDecorator36   ScopedEndOfReportDecorator(gwp_asan::Printf_t Printf) : Printf(Printf) {}
~ScopedEndOfReportDecorator__anon647644720111::ScopedEndOfReportDecorator37   ~ScopedEndOfReportDecorator() { Printf("*** End GWP-ASan report ***\n"); }
38   gwp_asan::Printf_t Printf;
39 };
40 
41 // Prints the provided error and metadata information.
printHeader(Error E,uintptr_t AccessPtr,const gwp_asan::AllocationMetadata * Metadata,Printf_t Printf)42 void printHeader(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(Metadata);
54     size_t Size = __gwp_asan_get_allocation_size(Metadata);
55     if (AccessPtr < Address) {
56       snprintf(DescriptionBuffer, kDescriptionBufferLen,
57                "(%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(DescriptionBuffer, kDescriptionBufferLen,
62                "(%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(DescriptionBuffer, kDescriptionBufferLen,
67                "(a %zu-byte allocation) ", Size);
68     } else {
69       AccessWasInBounds = true;
70       snprintf(DescriptionBuffer, kDescriptionBufferLen,
71                "(%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(ThreadBuffer, kThreadBufferLen, "<unknown>");
84   else
85     snprintf(ThreadBuffer, kThreadBufferLen, "%" 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 
dumpReport(uintptr_t ErrorPtr,const gwp_asan::AllocatorState * State,const gwp_asan::AllocationMetadata * Metadata,SegvBacktrace_t SegvBacktrace,Printf_t Printf,PrintBacktrace_t PrintBacktrace,void * Context)102 void dumpReport(uintptr_t ErrorPtr, const gwp_asan::AllocatorState *State,
103                 const gwp_asan::AllocationMetadata *Metadata,
104                 SegvBacktrace_t SegvBacktrace, Printf_t Printf,
105                 PrintBacktrace_t PrintBacktrace, void *Context) {
106   assert(State && "dumpReport missing Allocator State.");
107   assert(Metadata && "dumpReport missing Metadata.");
108   assert(Printf && "dumpReport missing Printf.");
109   assert(__gwp_asan_error_is_mine(State, ErrorPtr) &&
110          "dumpReport() called on a non-GWP-ASan error.");
111 
112   uintptr_t InternalErrorPtr =
113       __gwp_asan_get_internal_crash_address(State, ErrorPtr);
114   if (InternalErrorPtr)
115     ErrorPtr = InternalErrorPtr;
116 
117   const gwp_asan::AllocationMetadata *AllocMeta =
118       __gwp_asan_get_metadata(State, Metadata, ErrorPtr);
119 
120   // It's unusual for a signal handler to be invoked multiple times for the same
121   // allocation, but it's possible in various scenarios, like:
122   //  1. A double-free or invalid-free was invoked in one thread at the same
123   //     time as a buffer-overflow or use-after-free in another thread, or
124   //  2. Two threads do a use-after-free or buffer-overflow at the same time.
125   // In these instances, we've already dumped a report for this allocation, so
126   // skip dumping this issue as well.
127   if (AllocMeta->HasCrashed)
128     return;
129 
130   Printf("*** GWP-ASan detected a memory error ***\n");
131   ScopedEndOfReportDecorator Decorator(Printf);
132 
133   Error E = __gwp_asan_diagnose_error(State, Metadata, ErrorPtr);
134   if (E == Error::UNKNOWN) {
135     Printf("GWP-ASan cannot provide any more information about this error. "
136            "This may occur due to a wild memory access into the GWP-ASan pool, "
137            "or an overflow/underflow that is > 512B in length.\n");
138     return;
139   }
140 
141   // Print the error header.
142   printHeader(E, ErrorPtr, AllocMeta, Printf);
143 
144   // Print the fault backtrace.
145   static constexpr unsigned kMaximumStackFramesForCrashTrace = 512;
146   uintptr_t Trace[kMaximumStackFramesForCrashTrace];
147   size_t TraceLength =
148       SegvBacktrace(Trace, kMaximumStackFramesForCrashTrace, Context);
149 
150   PrintBacktrace(Trace, TraceLength, Printf);
151 
152   if (AllocMeta == nullptr)
153     return;
154 
155   // Maybe print the deallocation trace.
156   if (__gwp_asan_is_deallocated(AllocMeta)) {
157     uint64_t ThreadID = __gwp_asan_get_deallocation_thread_id(AllocMeta);
158     if (ThreadID == gwp_asan::kInvalidThreadID)
159       Printf("0x%zx was deallocated by thread <unknown> here:\n", ErrorPtr);
160     else
161       Printf("0x%zx was deallocated by thread %zu here:\n", ErrorPtr, ThreadID);
162     TraceLength = __gwp_asan_get_deallocation_trace(
163         AllocMeta, Trace, kMaximumStackFramesForCrashTrace);
164     PrintBacktrace(Trace, TraceLength, Printf);
165   }
166 
167   // Print the allocation trace.
168   uint64_t ThreadID = __gwp_asan_get_allocation_thread_id(AllocMeta);
169   if (ThreadID == gwp_asan::kInvalidThreadID)
170     Printf("0x%zx was allocated by thread <unknown> here:\n", ErrorPtr);
171   else
172     Printf("0x%zx was allocated by thread %zu here:\n", ErrorPtr, ThreadID);
173   TraceLength = __gwp_asan_get_allocation_trace(
174       AllocMeta, Trace, kMaximumStackFramesForCrashTrace);
175   PrintBacktrace(Trace, TraceLength, Printf);
176 }
177 
178 struct sigaction PreviousHandler;
179 bool SignalHandlerInstalled;
180 bool RecoverableSignal;
181 gwp_asan::GuardedPoolAllocator *GPAForSignalHandler;
182 Printf_t PrintfForSignalHandler;
183 PrintBacktrace_t PrintBacktraceForSignalHandler;
184 SegvBacktrace_t BacktraceForSignalHandler;
185 
sigSegvHandler(int sig,siginfo_t * info,void * ucontext)186 static void sigSegvHandler(int sig, siginfo_t *info, void *ucontext) {
187   const gwp_asan::AllocatorState *State =
188       GPAForSignalHandler->getAllocatorState();
189   void *FaultAddr = info->si_addr;
190   uintptr_t FaultAddrUPtr = reinterpret_cast<uintptr_t>(FaultAddr);
191 
192   if (__gwp_asan_error_is_mine(State, FaultAddrUPtr)) {
193     GPAForSignalHandler->preCrashReport(FaultAddr);
194 
195     dumpReport(FaultAddrUPtr, State, GPAForSignalHandler->getMetadataRegion(),
196                BacktraceForSignalHandler, PrintfForSignalHandler,
197                PrintBacktraceForSignalHandler, ucontext);
198 
199     if (RecoverableSignal) {
200       GPAForSignalHandler->postCrashReportRecoverableOnly(FaultAddr);
201       return;
202     }
203   }
204 
205   // Process any previous handlers as long as the crash wasn't a GWP-ASan crash
206   // in recoverable mode.
207   if (PreviousHandler.sa_flags & SA_SIGINFO) {
208     PreviousHandler.sa_sigaction(sig, info, ucontext);
209   } else if (PreviousHandler.sa_handler == SIG_DFL) {
210     // If the previous handler was the default handler, cause a core dump.
211     signal(SIGSEGV, SIG_DFL);
212     raise(SIGSEGV);
213   } else if (PreviousHandler.sa_handler == SIG_IGN) {
214     // If the previous segv handler was SIGIGN, crash iff we were responsible
215     // for the crash.
216     if (__gwp_asan_error_is_mine(GPAForSignalHandler->getAllocatorState(),
217                                  reinterpret_cast<uintptr_t>(info->si_addr))) {
218       signal(SIGSEGV, SIG_DFL);
219       raise(SIGSEGV);
220     }
221   } else {
222     PreviousHandler.sa_handler(sig);
223   }
224 }
225 } // anonymous namespace
226 
227 namespace gwp_asan {
228 namespace segv_handler {
229 
installSignalHandlers(gwp_asan::GuardedPoolAllocator * GPA,Printf_t Printf,PrintBacktrace_t PrintBacktrace,SegvBacktrace_t SegvBacktrace,bool Recoverable)230 void installSignalHandlers(gwp_asan::GuardedPoolAllocator *GPA, Printf_t Printf,
231                            PrintBacktrace_t PrintBacktrace,
232                            SegvBacktrace_t SegvBacktrace, bool Recoverable) {
233   assert(GPA && "GPA wasn't provided to installSignalHandlers.");
234   assert(Printf && "Printf wasn't provided to installSignalHandlers.");
235   assert(PrintBacktrace &&
236          "PrintBacktrace wasn't provided to installSignalHandlers.");
237   assert(SegvBacktrace &&
238          "SegvBacktrace wasn't provided to installSignalHandlers.");
239   GPAForSignalHandler = GPA;
240   PrintfForSignalHandler = Printf;
241   PrintBacktraceForSignalHandler = PrintBacktrace;
242   BacktraceForSignalHandler = SegvBacktrace;
243   RecoverableSignal = Recoverable;
244 
245   struct sigaction Action = {};
246   Action.sa_sigaction = sigSegvHandler;
247   Action.sa_flags = SA_SIGINFO;
248   sigaction(SIGSEGV, &Action, &PreviousHandler);
249   SignalHandlerInstalled = true;
250 }
251 
uninstallSignalHandlers()252 void uninstallSignalHandlers() {
253   if (SignalHandlerInstalled) {
254     sigaction(SIGSEGV, &PreviousHandler, nullptr);
255     SignalHandlerInstalled = false;
256   }
257 }
258 } // namespace segv_handler
259 } // namespace gwp_asan
260