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