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