1//===-- ubsan_diag.cpp ----------------------------------------------------===//
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// Diagnostic reporting for the UBSan runtime.
10//
11//===----------------------------------------------------------------------===//
12
13#include "ubsan_platform.h"
14#if CAN_SANITIZE_UB
15#include "ubsan_diag.h"
16#include "ubsan_flags.h"
17#include "ubsan_init.h"
18#include "ubsan_monitor.h"
19
20#include "sanitizer_common/sanitizer_internal_defs.h"
21#include "sanitizer_common/sanitizer_placement_new.h"
22#include "sanitizer_common/sanitizer_report_decorator.h"
23#include "sanitizer_common/sanitizer_stacktrace.h"
24#include "sanitizer_common/sanitizer_stacktrace_printer.h"
25#include "sanitizer_common/sanitizer_suppressions.h"
26#include "sanitizer_common/sanitizer_symbolizer.h"
27
28#include <stdio.h>
29
30using namespace __ubsan;
31
32// Can be overriden in frontend.
33SANITIZER_INTERFACE_WEAK_DEF(const char *, __ubsan_default_suppressions, void) {
34 return "";
35}
36
37// UBSan is combined with runtimes that already provide this functionality
38// (e.g., ASan) as well as runtimes that lack it (e.g., scudo). Tried to use
39// weak linkage to resolve this issue which is not portable and breaks on
40// Windows.
41// TODO(yln): This is a temporary workaround. GetStackTrace functions will be
42// removed in the future.
43void ubsan_GetStackTrace(BufferedStackTrace *stack, uptr max_depth, uptr pc,
44 uptr bp, void *context, bool request_fast) {
45 uptr top = 0;
46 uptr bottom = 0;
47 GetThreadStackTopAndBottom(at_initialization: false, stack_top: &top, stack_bottom: &bottom);
48 bool fast = StackTrace::WillUseFastUnwind(request_fast_unwind: request_fast);
49 stack->Unwind(max_depth, pc, bp, context, stack_top: top, stack_bottom: bottom, request_fast_unwind: fast);
50}
51
52static void MaybePrintStackTrace(uptr pc, uptr bp) {
53 // We assume that flags are already parsed, as UBSan runtime
54 // will definitely be called when we print the first diagnostics message.
55 if (!flags()->print_stacktrace)
56 return;
57
58 UNINITIALIZED BufferedStackTrace stack;
59 ubsan_GetStackTrace(stack: &stack, max_depth: kStackTraceMax, pc, bp, context: nullptr,
60 request_fast: common_flags()->fast_unwind_on_fatal);
61 stack.Print();
62}
63
64static const char *ConvertTypeToString(ErrorType Type) {
65 switch (Type) {
66#define UBSAN_CHECK(Name, SummaryKind, FSanitizeFlagName) \
67 case ErrorType::Name: \
68 return SummaryKind;
69#include "ubsan_checks.inc"
70#undef UBSAN_CHECK
71 }
72 UNREACHABLE("unknown ErrorType!");
73}
74
75static const char *ConvertTypeToFlagName(ErrorType Type) {
76 switch (Type) {
77#define UBSAN_CHECK(Name, SummaryKind, FSanitizeFlagName) \
78 case ErrorType::Name: \
79 return FSanitizeFlagName;
80#include "ubsan_checks.inc"
81#undef UBSAN_CHECK
82 }
83 UNREACHABLE("unknown ErrorType!");
84}
85
86static void MaybeReportErrorSummary(Location Loc, ErrorType Type) {
87 if (!common_flags()->print_summary)
88 return;
89 if (!flags()->report_error_type)
90 Type = ErrorType::GenericUB;
91 const char *ErrorKind = ConvertTypeToString(Type);
92 if (Loc.isSourceLocation()) {
93 SourceLocation SLoc = Loc.getSourceLocation();
94 if (!SLoc.isInvalid()) {
95 AddressInfo AI;
96 AI.file = internal_strdup(s: SLoc.getFilename());
97 AI.line = SLoc.getLine();
98 AI.column = SLoc.getColumn();
99 AI.function = nullptr;
100 ReportErrorSummary(error_type: ErrorKind, info: AI, alt_tool_name: GetSanititizerToolName());
101 AI.Clear();
102 return;
103 }
104 } else if (Loc.isSymbolizedStack()) {
105 const AddressInfo &AI = Loc.getSymbolizedStack()->info;
106 ReportErrorSummary(error_type: ErrorKind, info: AI, alt_tool_name: GetSanititizerToolName());
107 return;
108 }
109 ReportErrorSummary(error_message: ErrorKind, alt_tool_name: GetSanititizerToolName());
110}
111
112namespace {
113class Decorator : public SanitizerCommonDecorator {
114 public:
115 Decorator() : SanitizerCommonDecorator() {}
116 const char *Highlight() const { return Green(); }
117 const char *Note() const { return Black(); }
118};
119}
120
121SymbolizedStack *__ubsan::getSymbolizedLocation(uptr PC) {
122 InitAsStandaloneIfNecessary();
123 return Symbolizer::GetOrInit()->SymbolizePC(address: PC);
124}
125
126Diag &Diag::operator<<(const TypeDescriptor &V) {
127 return AddArg(A: V.getTypeName());
128}
129
130Diag &Diag::operator<<(const Value &V) {
131 if (V.getType().isSignedIntegerTy())
132 AddArg(A: V.getSIntValue());
133 else if (V.getType().isUnsignedIntegerTy())
134 AddArg(A: V.getUIntValue());
135 else if (V.getType().isFloatTy())
136 AddArg(A: V.getFloatValue());
137 else
138 AddArg(A: "<unknown>");
139 return *this;
140}
141
142/// Hexadecimal printing for numbers too large for Printf to handle directly.
143static void RenderHex(InternalScopedString *Buffer, UIntMax Val) {
144#if HAVE_INT128_T
145 Buffer->AppendF(format: "0x%08x%08x%08x%08x", (unsigned int)(Val >> 96),
146 (unsigned int)(Val >> 64), (unsigned int)(Val >> 32),
147 (unsigned int)(Val));
148#else
149 UNREACHABLE("long long smaller than 64 bits?");
150#endif
151}
152
153static void RenderLocation(InternalScopedString *Buffer, Location Loc) {
154 switch (Loc.getKind()) {
155 case Location::LK_Source: {
156 SourceLocation SLoc = Loc.getSourceLocation();
157 if (SLoc.isInvalid())
158 Buffer->AppendF(format: "<unknown>");
159 else
160 StackTracePrinter::GetOrInit()->RenderSourceLocation(
161 buffer: Buffer, file: SLoc.getFilename(), line: SLoc.getLine(), column: SLoc.getColumn(),
162 vs_style: common_flags()->symbolize_vs_style,
163 strip_path_prefix: common_flags()->strip_path_prefix);
164 return;
165 }
166 case Location::LK_Memory:
167 Buffer->AppendF(format: "%p", reinterpret_cast<void *>(Loc.getMemoryLocation()));
168 return;
169 case Location::LK_Symbolized: {
170 const AddressInfo &Info = Loc.getSymbolizedStack()->info;
171 if (Info.file)
172 StackTracePrinter::GetOrInit()->RenderSourceLocation(
173 buffer: Buffer, file: Info.file, line: Info.line, column: Info.column,
174 vs_style: common_flags()->symbolize_vs_style,
175 strip_path_prefix: common_flags()->strip_path_prefix);
176 else if (Info.module)
177 StackTracePrinter::GetOrInit()->RenderModuleLocation(
178 buffer: Buffer, module: Info.module, offset: Info.module_offset, arch: Info.module_arch,
179 strip_path_prefix: common_flags()->strip_path_prefix);
180 else
181 Buffer->AppendF(format: "%p", reinterpret_cast<void *>(Info.address));
182 return;
183 }
184 case Location::LK_Null:
185 Buffer->AppendF(format: "<unknown>");
186 return;
187 }
188}
189
190static void RenderText(InternalScopedString *Buffer, const char *Message,
191 const Diag::Arg *Args) {
192 for (const char *Msg = Message; *Msg; ++Msg) {
193 if (*Msg != '%') {
194 Buffer->AppendF(format: "%c", *Msg);
195 continue;
196 }
197 const Diag::Arg &A = Args[*++Msg - '0'];
198 switch (A.Kind) {
199 case Diag::AK_String:
200 Buffer->AppendF(format: "%s", A.String);
201 break;
202 case Diag::AK_TypeName: {
203 if (SANITIZER_WINDOWS)
204 // The Windows implementation demangles names early.
205 Buffer->AppendF(format: "'%s'", A.String);
206 else
207 Buffer->AppendF(format: "'%s'", Symbolizer::GetOrInit()->Demangle(name: A.String));
208 break;
209 }
210 case Diag::AK_SInt:
211 // 'long long' is guaranteed to be at least 64 bits wide.
212 if (A.SInt >= INT64_MIN && A.SInt <= INT64_MAX)
213 Buffer->AppendF(format: "%lld", (long long)A.SInt);
214 else
215 RenderHex(Buffer, Val: A.SInt);
216 break;
217 case Diag::AK_UInt:
218 if (A.UInt <= UINT64_MAX)
219 Buffer->AppendF(format: "%llu", (unsigned long long)A.UInt);
220 else
221 RenderHex(Buffer, Val: A.UInt);
222 break;
223 case Diag::AK_Float: {
224 // FIXME: Support floating-point formatting in sanitizer_common's
225 // printf, and stop using snprintf here.
226 char FloatBuffer[32];
227#if SANITIZER_WINDOWS
228 // On MSVC platforms, long doubles are equal to regular doubles.
229 // In MinGW environments on x86, long doubles are 80 bit, but here,
230 // we're calling an MS CRT provided printf function which considers
231 // long doubles to be 64 bit. Just cast the float value to a regular
232 // double to avoid the potential ambiguity in MinGW mode.
233 sprintf_s(FloatBuffer, sizeof(FloatBuffer), "%g", (double)A.Float);
234#else
235 snprintf(s: FloatBuffer, maxlen: sizeof(FloatBuffer), format: "%Lg", (long double)A.Float);
236#endif
237 Buffer->Append(str: FloatBuffer);
238 break;
239 }
240 case Diag::AK_Pointer:
241 Buffer->AppendF(format: "%p", A.Pointer);
242 break;
243 }
244 }
245}
246
247/// Find the earliest-starting range in Ranges which ends after Loc.
248static Range *upperBound(MemoryLocation Loc, Range *Ranges,
249 unsigned NumRanges) {
250 Range *Best = 0;
251 for (unsigned I = 0; I != NumRanges; ++I)
252 if (Ranges[I].getEnd().getMemoryLocation() > Loc &&
253 (!Best ||
254 Best->getStart().getMemoryLocation() >
255 Ranges[I].getStart().getMemoryLocation()))
256 Best = &Ranges[I];
257 return Best;
258}
259
260static inline uptr subtractNoOverflow(uptr LHS, uptr RHS) {
261 return (LHS < RHS) ? 0 : LHS - RHS;
262}
263
264static inline uptr addNoOverflow(uptr LHS, uptr RHS) {
265 const uptr Limit = (uptr)-1;
266 return (LHS > Limit - RHS) ? Limit : LHS + RHS;
267}
268
269/// Render a snippet of the address space near a location.
270static void PrintMemorySnippet(const Decorator &Decor, MemoryLocation Loc,
271 Range *Ranges, unsigned NumRanges,
272 const Diag::Arg *Args) {
273 // Show at least the 8 bytes surrounding Loc.
274 const unsigned MinBytesNearLoc = 4;
275 MemoryLocation Min = subtractNoOverflow(LHS: Loc, RHS: MinBytesNearLoc);
276 MemoryLocation Max = addNoOverflow(LHS: Loc, RHS: MinBytesNearLoc);
277 MemoryLocation OrigMin = Min;
278 for (unsigned I = 0; I < NumRanges; ++I) {
279 Min = __sanitizer::Min(a: Ranges[I].getStart().getMemoryLocation(), b: Min);
280 Max = __sanitizer::Max(a: Ranges[I].getEnd().getMemoryLocation(), b: Max);
281 }
282
283 // If we have too many interesting bytes, prefer to show bytes after Loc.
284 const unsigned BytesToShow = 32;
285 if (Max - Min > BytesToShow)
286 Min = __sanitizer::Min(a: Max - BytesToShow, b: OrigMin);
287 Max = addNoOverflow(LHS: Min, RHS: BytesToShow);
288
289 if (!IsAccessibleMemoryRange(beg: Min, size: Max - Min)) {
290 Printf(format: "<memory cannot be printed>\n");
291 return;
292 }
293
294 // Emit data.
295 InternalScopedString Buffer;
296 for (uptr P = Min; P != Max; ++P) {
297 unsigned char C = *reinterpret_cast<const unsigned char*>(P);
298 Buffer.AppendF(format: "%s%02x", (P % 8 == 0) ? " " : " ", C);
299 }
300 Buffer.AppendF(format: "\n");
301
302 // Emit highlights.
303 Buffer.Append(str: Decor.Highlight());
304 Range *InRange = upperBound(Loc: Min, Ranges, NumRanges);
305 for (uptr P = Min; P != Max; ++P) {
306 char Pad = ' ', Byte = ' ';
307 if (InRange && InRange->getEnd().getMemoryLocation() == P)
308 InRange = upperBound(Loc: P, Ranges, NumRanges);
309 if (!InRange && P > Loc)
310 break;
311 if (InRange && InRange->getStart().getMemoryLocation() < P)
312 Pad = '~';
313 if (InRange && InRange->getStart().getMemoryLocation() <= P)
314 Byte = '~';
315 if (P % 8 == 0)
316 Buffer.AppendF(format: "%c", Pad);
317 Buffer.AppendF(format: "%c", Pad);
318 Buffer.AppendF(format: "%c", P == Loc ? '^' : Byte);
319 Buffer.AppendF(format: "%c", Byte);
320 }
321 Buffer.AppendF(format: "%s\n", Decor.Default());
322
323 // Go over the line again, and print names for the ranges.
324 InRange = 0;
325 unsigned Spaces = 0;
326 for (uptr P = Min; P != Max; ++P) {
327 if (!InRange || InRange->getEnd().getMemoryLocation() == P)
328 InRange = upperBound(Loc: P, Ranges, NumRanges);
329 if (!InRange)
330 break;
331
332 Spaces += (P % 8) == 0 ? 2 : 1;
333
334 if (InRange && InRange->getStart().getMemoryLocation() == P) {
335 while (Spaces--)
336 Buffer.AppendF(format: " ");
337 RenderText(Buffer: &Buffer, Message: InRange->getText(), Args);
338 Buffer.AppendF(format: "\n");
339 // FIXME: We only support naming one range for now!
340 break;
341 }
342
343 Spaces += 2;
344 }
345
346 Printf(format: "%s", Buffer.data());
347 // FIXME: Print names for anything we can identify within the line:
348 //
349 // * If we can identify the memory itself as belonging to a particular
350 // global, stack variable, or dynamic allocation, then do so.
351 //
352 // * If we have a pointer-size, pointer-aligned range highlighted,
353 // determine whether the value of that range is a pointer to an
354 // entity which we can name, and if so, print that name.
355 //
356 // This needs an external symbolizer, or (preferably) ASan instrumentation.
357}
358
359Diag::~Diag() {
360 // All diagnostics should be printed under report mutex.
361 ScopedReport::CheckLocked();
362 Decorator Decor;
363 InternalScopedString Buffer;
364
365 // Prepare a report that a monitor process can inspect.
366 if (Level == DL_Error) {
367 RenderText(Buffer: &Buffer, Message, Args);
368 UndefinedBehaviorReport UBR{ConvertTypeToString(Type: ET), Loc, Buffer};
369 Buffer.clear();
370 }
371
372 Buffer.Append(str: Decor.Bold());
373 RenderLocation(Buffer: &Buffer, Loc);
374 Buffer.AppendF(format: ":");
375
376 switch (Level) {
377 case DL_Error:
378 Buffer.AppendF(format: "%s runtime error: %s%s", Decor.Warning(), Decor.Default(),
379 Decor.Bold());
380 break;
381
382 case DL_Note:
383 Buffer.AppendF(format: "%s note: %s", Decor.Note(), Decor.Default());
384 break;
385 }
386
387 RenderText(Buffer: &Buffer, Message, Args);
388
389 Buffer.AppendF(format: "%s\n", Decor.Default());
390 Printf(format: "%s", Buffer.data());
391
392 if (Loc.isMemoryLocation())
393 PrintMemorySnippet(Decor, Loc: Loc.getMemoryLocation(), Ranges, NumRanges, Args);
394}
395
396ScopedReport::Initializer::Initializer() { InitAsStandaloneIfNecessary(); }
397
398ScopedReport::ScopedReport(ReportOptions Opts, Location SummaryLoc,
399 ErrorType Type)
400 : Opts(Opts), SummaryLoc(SummaryLoc), Type(Type) {}
401
402ScopedReport::~ScopedReport() {
403 MaybePrintStackTrace(pc: Opts.pc, bp: Opts.bp);
404 MaybeReportErrorSummary(Loc: SummaryLoc, Type);
405
406 if (common_flags()->print_module_map >= 2)
407 DumpProcessMap();
408
409 if (flags()->halt_on_error)
410 Die();
411}
412
413alignas(64) static char suppression_placeholder[sizeof(SuppressionContext)];
414static SuppressionContext *suppression_ctx = nullptr;
415static const char kVptrCheck[] = "vptr_check";
416static const char *kSuppressionTypes[] = {
417#define UBSAN_CHECK(Name, SummaryKind, FSanitizeFlagName) FSanitizeFlagName,
418#include "ubsan_checks.inc"
419#undef UBSAN_CHECK
420 kVptrCheck,
421};
422
423void __ubsan::InitializeSuppressions() {
424 CHECK_EQ(nullptr, suppression_ctx);
425 suppression_ctx = new (suppression_placeholder)
426 SuppressionContext(kSuppressionTypes, ARRAY_SIZE(kSuppressionTypes));
427 suppression_ctx->ParseFromFile(filename: flags()->suppressions);
428 suppression_ctx->Parse(str: __ubsan_default_suppressions());
429}
430
431bool __ubsan::IsVptrCheckSuppressed(const char *TypeName) {
432 InitAsStandaloneIfNecessary();
433 CHECK(suppression_ctx);
434 Suppression *s;
435 return suppression_ctx->Match(str: TypeName, type: kVptrCheck, s: &s);
436}
437
438bool __ubsan::IsPCSuppressed(ErrorType ET, uptr PC, const char *Filename) {
439 InitAsStandaloneIfNecessary();
440 CHECK(suppression_ctx);
441 const char *SuppType = ConvertTypeToFlagName(Type: ET);
442 // Fast path: don't symbolize PC if there is no suppressions for given UB
443 // type.
444 if (!suppression_ctx->HasSuppressionType(type: SuppType))
445 return false;
446 Suppression *s = nullptr;
447 // Suppress by file name known to runtime.
448 if (Filename != nullptr && suppression_ctx->Match(str: Filename, type: SuppType, s: &s))
449 return true;
450 // Suppress by module name.
451 if (const char *Module = Symbolizer::GetOrInit()->GetModuleNameForPc(pc: PC)) {
452 if (suppression_ctx->Match(str: Module, type: SuppType, s: &s))
453 return true;
454 }
455 // Suppress by function or source file name from debug info.
456 SymbolizedStackHolder Stack(Symbolizer::GetOrInit()->SymbolizePC(address: PC));
457 const AddressInfo &AI = Stack.get()->info;
458 return suppression_ctx->Match(str: AI.function, type: SuppType, s: &s) ||
459 suppression_ctx->Match(str: AI.file, type: SuppType, s: &s);
460}
461
462#endif // CAN_SANITIZE_UB
463