454 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// Diagnostic reporting for the UBSan runtime.10//11//===----------------------------------------------------------------------===//12 13#include "ubsan_platform.h"14#if CAN_SANITIZE_UB15#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 27using namespace __ubsan;28 29// UBSan is combined with runtimes that already provide this functionality30// (e.g., ASan) as well as runtimes that lack it (e.g., scudo). Tried to use31// weak linkage to resolve this issue which is not portable and breaks on32// Windows.33// TODO(yln): This is a temporary workaround. GetStackTrace functions will be34// removed in the future.35void 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(false, &top, &bottom);40 bool fast = StackTrace::WillUseFastUnwind(request_fast);41 stack->Unwind(max_depth, pc, bp, context, top, bottom, fast);42}43 44static void MaybePrintStackTrace(uptr pc, uptr bp) {45 // We assume that flags are already parsed, as UBSan runtime46 // 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, kStackTraceMax, pc, bp, nullptr,52 common_flags()->fast_unwind_on_fatal);53 stack.Print();54}55 56static 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_CHECK63 }64 UNREACHABLE("unknown ErrorType!");65}66 67static 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_CHECK74 }75 UNREACHABLE("unknown ErrorType!");76}77 78static 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(SLoc.getFilename());89 AI.line = SLoc.getLine();90 AI.column = SLoc.getColumn();91 AI.function = nullptr;92 ReportErrorSummary(ErrorKind, AI, GetSanititizerToolName());93 AI.Clear();94 return;95 }96 } else if (Loc.isSymbolizedStack()) {97 const AddressInfo &AI = Loc.getSymbolizedStack()->info;98 ReportErrorSummary(ErrorKind, AI, GetSanititizerToolName());99 return;100 }101 ReportErrorSummary(ErrorKind, GetSanititizerToolName());102}103 104namespace {105class 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 113SymbolizedStack *__ubsan::getSymbolizedLocation(uptr PC) {114 InitAsStandaloneIfNecessary();115 return Symbolizer::GetOrInit()->SymbolizePC(PC);116}117 118Diag &Diag::operator<<(const TypeDescriptor &V) {119 return AddArg(V.getTypeName());120}121 122Diag &Diag::operator<<(const Value &V) {123 if (V.getType().isSignedIntegerTy())124 AddArg(V.getSIntValue());125 else if (V.getType().isUnsignedIntegerTy())126 AddArg(V.getUIntValue());127 else if (V.getType().isFloatTy())128 AddArg(V.getFloatValue());129 else130 AddArg("<unknown>");131 return *this;132}133 134/// Hexadecimal printing for numbers too large for Printf to handle directly.135static void RenderHex(InternalScopedString *Buffer, UIntMax Val) {136#if HAVE_INT128_T137 Buffer->AppendF("0x%08x%08x%08x%08x", (unsigned int)(Val >> 96),138 (unsigned int)(Val >> 64), (unsigned int)(Val >> 32),139 (unsigned int)(Val));140#else141 UNREACHABLE("long long smaller than 64 bits?");142#endif143}144 145static 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("<unknown>");151 else152 StackTracePrinter::GetOrInit()->RenderSourceLocation(153 Buffer, SLoc.getFilename(), SLoc.getLine(), SLoc.getColumn(),154 common_flags()->symbolize_vs_style,155 common_flags()->strip_path_prefix);156 return;157 }158 case Location::LK_Memory:159 Buffer->AppendF("%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, Info.file, Info.line, Info.column,166 common_flags()->symbolize_vs_style,167 common_flags()->strip_path_prefix);168 else if (Info.module)169 StackTracePrinter::GetOrInit()->RenderModuleLocation(170 Buffer, Info.module, Info.module_offset, Info.module_arch,171 common_flags()->strip_path_prefix);172 else173 Buffer->AppendF("%p", reinterpret_cast<void *>(Info.address));174 return;175 }176 case Location::LK_Null:177 Buffer->AppendF("<unknown>");178 return;179 }180}181 182static 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("%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("%s", A.String);193 break;194 case Diag::AK_TypeName: {195 if (SANITIZER_WINDOWS)196 // The Windows implementation demangles names early.197 Buffer->AppendF("'%s'", A.String);198 else199 Buffer->AppendF("'%s'", Symbolizer::GetOrInit()->Demangle(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("%lld", (long long)A.SInt);206 else207 RenderHex(Buffer, A.SInt);208 break;209 case Diag::AK_UInt:210 if (A.UInt <= UINT64_MAX)211 Buffer->AppendF("%llu", (unsigned long long)A.UInt);212 else213 RenderHex(Buffer, A.UInt);214 break;215 case Diag::AK_Float: {216 // FIXME: Support floating-point formatting in sanitizer_common's217 // printf, and stop using snprintf here.218 char FloatBuffer[32];219#if SANITIZER_WINDOWS220 // 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 considers223 // long doubles to be 64 bit. Just cast the float value to a regular224 // double to avoid the potential ambiguity in MinGW mode.225 sprintf_s(FloatBuffer, sizeof(FloatBuffer), "%g", (double)A.Float);226#else227 snprintf(FloatBuffer, sizeof(FloatBuffer), "%Lg", (long double)A.Float);228#endif229 Buffer->Append(FloatBuffer);230 break;231 }232 case Diag::AK_Pointer:233 Buffer->AppendF("%p", A.Pointer);234 break;235 }236 }237}238 239/// Find the earliest-starting range in Ranges which ends after Loc.240static 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 252static inline uptr subtractNoOverflow(uptr LHS, uptr RHS) {253 return (LHS < RHS) ? 0 : LHS - RHS;254}255 256static 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.262static 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(Loc, MinBytesNearLoc);268 MemoryLocation Max = addNoOverflow(Loc, MinBytesNearLoc);269 MemoryLocation OrigMin = Min;270 for (unsigned I = 0; I < NumRanges; ++I) {271 Min = __sanitizer::Min(Ranges[I].getStart().getMemoryLocation(), Min);272 Max = __sanitizer::Max(Ranges[I].getEnd().getMemoryLocation(), 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(Max - BytesToShow, OrigMin);279 Max = addNoOverflow(Min, BytesToShow);280 281 if (!IsAccessibleMemoryRange(Min, Max - Min)) {282 Printf("<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("%s%02x", (P % 8 == 0) ? " " : " ", C);291 }292 Buffer.AppendF("\n");293 294 // Emit highlights.295 Buffer.Append(Decor.Highlight());296 Range *InRange = upperBound(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(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("%c", Pad);309 Buffer.AppendF("%c", Pad);310 Buffer.AppendF("%c", P == Loc ? '^' : Byte);311 Buffer.AppendF("%c", Byte);312 }313 Buffer.AppendF("%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(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(" ");329 RenderText(&Buffer, InRange->getText(), Args);330 Buffer.AppendF("\n");331 // FIXME: We only support naming one range for now!332 break;333 }334 335 Spaces += 2;336 }337 338 Printf("%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 particular342 // 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 an346 // entity which we can name, and if so, print that name.347 //348 // This needs an external symbolizer, or (preferably) ASan instrumentation.349}350 351Diag::~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, Message, Args);360 UndefinedBehaviorReport UBR{ConvertTypeToString(ET), Loc, Buffer};361 Buffer.clear();362 }363 364 Buffer.Append(Decor.Bold());365 RenderLocation(&Buffer, Loc);366 Buffer.AppendF(":");367 368 switch (Level) {369 case DL_Error:370 Buffer.AppendF("%s runtime error: %s%s", Decor.Warning(), Decor.Default(),371 Decor.Bold());372 break;373 374 case DL_Note:375 Buffer.AppendF("%s note: %s", Decor.Note(), Decor.Default());376 break;377 }378 379 RenderText(&Buffer, Message, Args);380 381 Buffer.AppendF("%s\n", Decor.Default());382 Printf("%s", Buffer.data());383 384 if (Loc.isMemoryLocation())385 PrintMemorySnippet(Decor, Loc.getMemoryLocation(), Ranges, NumRanges, Args);386}387 388ScopedReport::Initializer::Initializer() { InitAsStandaloneIfNecessary(); }389 390ScopedReport::ScopedReport(ReportOptions Opts, Location SummaryLoc,391 ErrorType Type)392 : Opts(Opts), SummaryLoc(SummaryLoc), Type(Type) {}393 394ScopedReport::~ScopedReport() {395 MaybePrintStackTrace(Opts.pc, Opts.bp);396 MaybeReportErrorSummary(SummaryLoc, Type);397 398 if (common_flags()->print_module_map >= 2)399 DumpProcessMap();400 401 if (flags()->halt_on_error)402 Die();403}404 405alignas(64) static char suppression_placeholder[sizeof(SuppressionContext)];406static SuppressionContext *suppression_ctx = nullptr;407static const char kVptrCheck[] = "vptr_check";408static const char *kSuppressionTypes[] = {409#define UBSAN_CHECK(Name, SummaryKind, FSanitizeFlagName) FSanitizeFlagName,410#include "ubsan_checks.inc"411#undef UBSAN_CHECK412 kVptrCheck,413};414 415void __ubsan::InitializeSuppressions() {416 CHECK_EQ(nullptr, suppression_ctx);417 suppression_ctx = new (suppression_placeholder)418 SuppressionContext(kSuppressionTypes, ARRAY_SIZE(kSuppressionTypes));419 suppression_ctx->ParseFromFile(flags()->suppressions);420}421 422bool __ubsan::IsVptrCheckSuppressed(const char *TypeName) {423 InitAsStandaloneIfNecessary();424 CHECK(suppression_ctx);425 Suppression *s;426 return suppression_ctx->Match(TypeName, kVptrCheck, &s);427}428 429bool __ubsan::IsPCSuppressed(ErrorType ET, uptr PC, const char *Filename) {430 InitAsStandaloneIfNecessary();431 CHECK(suppression_ctx);432 const char *SuppType = ConvertTypeToFlagName(ET);433 // Fast path: don't symbolize PC if there is no suppressions for given UB434 // type.435 if (!suppression_ctx->HasSuppressionType(SuppType))436 return false;437 Suppression *s = nullptr;438 // Suppress by file name known to runtime.439 if (Filename != nullptr && suppression_ctx->Match(Filename, SuppType, &s))440 return true;441 // Suppress by module name.442 if (const char *Module = Symbolizer::GetOrInit()->GetModuleNameForPc(PC)) {443 if (suppression_ctx->Match(Module, SuppType, &s))444 return true;445 }446 // Suppress by function or source file name from debug info.447 SymbolizedStackHolder Stack(Symbolizer::GetOrInit()->SymbolizePC(PC));448 const AddressInfo &AI = Stack.get()->info;449 return suppression_ctx->Match(AI.function, SuppType, &s) ||450 suppression_ctx->Match(AI.file, SuppType, &s);451}452 453#endif // CAN_SANITIZE_UB454