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