| //===-- asan_report.cpp ---------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file is a part of AddressSanitizer, an address sanity checker. |
| // |
| // This file contains error reporting code. |
| //===----------------------------------------------------------------------===// |
| |
| #include "asan_report.h" |
| |
| #include "asan_descriptions.h" |
| #include "asan_errors.h" |
| #include "asan_flags.h" |
| #include "asan_internal.h" |
| #include "asan_mapping.h" |
| #include "asan_scariness_score.h" |
| #include "asan_stack.h" |
| #include "asan_thread.h" |
| #include "sanitizer_common/sanitizer_common.h" |
| #include "sanitizer_common/sanitizer_flags.h" |
| #include "sanitizer_common/sanitizer_interface_internal.h" |
| #include "sanitizer_common/sanitizer_report_decorator.h" |
| #include "sanitizer_common/sanitizer_stackdepot.h" |
| #include "sanitizer_common/sanitizer_symbolizer.h" |
| |
| namespace __asan { |
| |
| // -------------------- User-specified callbacks ----------------- {{{1 |
| static void (*error_report_callback)(const char*); |
| static char *error_message_buffer = nullptr; |
| static uptr error_message_buffer_pos = 0; |
| static Mutex error_message_buf_mutex; |
| static const unsigned kAsanBuggyPcPoolSize = 25; |
| static __sanitizer::atomic_uintptr_t AsanBuggyPcPool[kAsanBuggyPcPoolSize]; |
| |
| void AppendToErrorMessageBuffer(const char *buffer) { |
| Lock l(&error_message_buf_mutex); |
| if (!error_message_buffer) { |
| error_message_buffer = |
| (char*)MmapOrDieQuietly(kErrorMessageBufferSize, __func__); |
| error_message_buffer_pos = 0; |
| } |
| uptr length = internal_strlen(buffer); |
| RAW_CHECK(kErrorMessageBufferSize >= error_message_buffer_pos); |
| uptr remaining = kErrorMessageBufferSize - error_message_buffer_pos; |
| internal_strncpy(error_message_buffer + error_message_buffer_pos, |
| buffer, remaining); |
| error_message_buffer[kErrorMessageBufferSize - 1] = '\0'; |
| // FIXME: reallocate the buffer instead of truncating the message. |
| error_message_buffer_pos += Min(remaining, length); |
| } |
| |
| // ---------------------- Helper functions ----------------------- {{{1 |
| |
| void PrintMemoryByte(InternalScopedString *str, const char *before, u8 byte, |
| bool in_shadow, const char *after) { |
| Decorator d; |
| str->append("%s%s%x%x%s%s", before, |
| in_shadow ? d.ShadowByte(byte) : d.MemoryByte(), byte >> 4, |
| byte & 15, d.Default(), after); |
| } |
| |
| static void PrintZoneForPointer(uptr ptr, uptr zone_ptr, |
| const char *zone_name) { |
| if (zone_ptr) { |
| if (zone_name) { |
| Printf("malloc_zone_from_ptr(%p) = %p, which is %s\n", (void *)ptr, |
| (void *)zone_ptr, zone_name); |
| } else { |
| Printf("malloc_zone_from_ptr(%p) = %p, which doesn't have a name\n", |
| (void *)ptr, (void *)zone_ptr); |
| } |
| } else { |
| Printf("malloc_zone_from_ptr(%p) = 0\n", (void *)ptr); |
| } |
| } |
| |
| // ---------------------- Address Descriptions ------------------- {{{1 |
| |
| bool ParseFrameDescription(const char *frame_descr, |
| InternalMmapVector<StackVarDescr> *vars) { |
| CHECK(frame_descr); |
| const char *p; |
| // This string is created by the compiler and has the following form: |
| // "n alloc_1 alloc_2 ... alloc_n" |
| // where alloc_i looks like "offset size len ObjectName" |
| // or "offset size len ObjectName:line". |
| uptr n_objects = (uptr)internal_simple_strtoll(frame_descr, &p, 10); |
| if (n_objects == 0) |
| return false; |
| |
| for (uptr i = 0; i < n_objects; i++) { |
| uptr beg = (uptr)internal_simple_strtoll(p, &p, 10); |
| uptr size = (uptr)internal_simple_strtoll(p, &p, 10); |
| uptr len = (uptr)internal_simple_strtoll(p, &p, 10); |
| if (beg == 0 || size == 0 || *p != ' ') { |
| return false; |
| } |
| p++; |
| char *colon_pos = internal_strchr(p, ':'); |
| uptr line = 0; |
| uptr name_len = len; |
| if (colon_pos != nullptr && colon_pos < p + len) { |
| name_len = colon_pos - p; |
| line = (uptr)internal_simple_strtoll(colon_pos + 1, nullptr, 10); |
| } |
| StackVarDescr var = {beg, size, p, name_len, line}; |
| vars->push_back(var); |
| p += len; |
| } |
| |
| return true; |
| } |
| |
| // -------------------- Different kinds of reports ----------------- {{{1 |
| |
| // Use ScopedInErrorReport to run common actions just before and |
| // immediately after printing error report. |
| class ScopedInErrorReport { |
| public: |
| explicit ScopedInErrorReport(bool fatal = false) |
| : halt_on_error_(fatal || flags()->halt_on_error) { |
| // Make sure the registry and sanitizer report mutexes are locked while |
| // we're printing an error report. |
| // We can lock them only here to avoid self-deadlock in case of |
| // recursive reports. |
| asanThreadRegistry().Lock(); |
| Printf( |
| "=================================================================\n"); |
| } |
| |
| ~ScopedInErrorReport() { |
| if (halt_on_error_ && !__sanitizer_acquire_crash_state()) { |
| asanThreadRegistry().Unlock(); |
| return; |
| } |
| ASAN_ON_ERROR(); |
| if (current_error_.IsValid()) current_error_.Print(); |
| |
| // Make sure the current thread is announced. |
| DescribeThread(GetCurrentThread()); |
| // We may want to grab this lock again when printing stats. |
| asanThreadRegistry().Unlock(); |
| // Print memory stats. |
| if (flags()->print_stats) |
| __asan_print_accumulated_stats(); |
| |
| if (common_flags()->print_cmdline) |
| PrintCmdline(); |
| |
| if (common_flags()->print_module_map == 2) |
| DumpProcessMap(); |
| |
| // Copy the message buffer so that we could start logging without holding a |
| // lock that gets acquired during printing. |
| InternalMmapVector<char> buffer_copy(kErrorMessageBufferSize); |
| { |
| Lock l(&error_message_buf_mutex); |
| internal_memcpy(buffer_copy.data(), |
| error_message_buffer, kErrorMessageBufferSize); |
| // Clear error_message_buffer so that if we find other errors |
| // we don't re-log this error. |
| error_message_buffer_pos = 0; |
| } |
| |
| LogFullErrorReport(buffer_copy.data()); |
| |
| if (error_report_callback) { |
| error_report_callback(buffer_copy.data()); |
| } |
| |
| if (halt_on_error_ && common_flags()->abort_on_error) { |
| // On Android the message is truncated to 512 characters. |
| // FIXME: implement "compact" error format, possibly without, or with |
| // highly compressed stack traces? |
| // FIXME: or just use the summary line as abort message? |
| SetAbortMessage(buffer_copy.data()); |
| } |
| |
| // In halt_on_error = false mode, reset the current error object (before |
| // unlocking). |
| if (!halt_on_error_) |
| internal_memset(¤t_error_, 0, sizeof(current_error_)); |
| |
| if (halt_on_error_) { |
| Report("ABORTING\n"); |
| Die(); |
| } |
| } |
| |
| void ReportError(const ErrorDescription &description) { |
| // Can only report one error per ScopedInErrorReport. |
| CHECK_EQ(current_error_.kind, kErrorKindInvalid); |
| internal_memcpy(¤t_error_, &description, sizeof(current_error_)); |
| } |
| |
| static ErrorDescription &CurrentError() { |
| return current_error_; |
| } |
| |
| private: |
| ScopedErrorReportLock error_report_lock_; |
| // Error currently being reported. This enables the destructor to interact |
| // with the debugger and point it to an error description. |
| static ErrorDescription current_error_; |
| bool halt_on_error_; |
| }; |
| |
| ErrorDescription ScopedInErrorReport::current_error_(LINKER_INITIALIZED); |
| |
| void ReportDeadlySignal(const SignalContext &sig) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorDeadlySignal error(GetCurrentTidOrInvalid(), sig); |
| in_report.ReportError(error); |
| } |
| |
| void ReportDoubleFree(uptr addr, BufferedStackTrace *free_stack) { |
| ScopedInErrorReport in_report; |
| ErrorDoubleFree error(GetCurrentTidOrInvalid(), free_stack, addr); |
| in_report.ReportError(error); |
| } |
| |
| void ReportNewDeleteTypeMismatch(uptr addr, uptr delete_size, |
| uptr delete_alignment, |
| BufferedStackTrace *free_stack) { |
| ScopedInErrorReport in_report; |
| ErrorNewDeleteTypeMismatch error(GetCurrentTidOrInvalid(), free_stack, addr, |
| delete_size, delete_alignment); |
| in_report.ReportError(error); |
| } |
| |
| void ReportFreeNotMalloced(uptr addr, BufferedStackTrace *free_stack) { |
| ScopedInErrorReport in_report; |
| ErrorFreeNotMalloced error(GetCurrentTidOrInvalid(), free_stack, addr); |
| in_report.ReportError(error); |
| } |
| |
| void ReportAllocTypeMismatch(uptr addr, BufferedStackTrace *free_stack, |
| AllocType alloc_type, |
| AllocType dealloc_type) { |
| ScopedInErrorReport in_report; |
| ErrorAllocTypeMismatch error(GetCurrentTidOrInvalid(), free_stack, addr, |
| alloc_type, dealloc_type); |
| in_report.ReportError(error); |
| } |
| |
| void ReportMallocUsableSizeNotOwned(uptr addr, BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| ErrorMallocUsableSizeNotOwned error(GetCurrentTidOrInvalid(), stack, addr); |
| in_report.ReportError(error); |
| } |
| |
| void ReportSanitizerGetAllocatedSizeNotOwned(uptr addr, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| ErrorSanitizerGetAllocatedSizeNotOwned error(GetCurrentTidOrInvalid(), stack, |
| addr); |
| in_report.ReportError(error); |
| } |
| |
| void ReportCallocOverflow(uptr count, uptr size, BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorCallocOverflow error(GetCurrentTidOrInvalid(), stack, count, size); |
| in_report.ReportError(error); |
| } |
| |
| void ReportReallocArrayOverflow(uptr count, uptr size, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorReallocArrayOverflow error(GetCurrentTidOrInvalid(), stack, count, size); |
| in_report.ReportError(error); |
| } |
| |
| void ReportPvallocOverflow(uptr size, BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorPvallocOverflow error(GetCurrentTidOrInvalid(), stack, size); |
| in_report.ReportError(error); |
| } |
| |
| void ReportInvalidAllocationAlignment(uptr alignment, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorInvalidAllocationAlignment error(GetCurrentTidOrInvalid(), stack, |
| alignment); |
| in_report.ReportError(error); |
| } |
| |
| void ReportInvalidAlignedAllocAlignment(uptr size, uptr alignment, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorInvalidAlignedAllocAlignment error(GetCurrentTidOrInvalid(), stack, |
| size, alignment); |
| in_report.ReportError(error); |
| } |
| |
| void ReportInvalidPosixMemalignAlignment(uptr alignment, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorInvalidPosixMemalignAlignment error(GetCurrentTidOrInvalid(), stack, |
| alignment); |
| in_report.ReportError(error); |
| } |
| |
| void ReportAllocationSizeTooBig(uptr user_size, uptr total_size, uptr max_size, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorAllocationSizeTooBig error(GetCurrentTidOrInvalid(), stack, user_size, |
| total_size, max_size); |
| in_report.ReportError(error); |
| } |
| |
| void ReportRssLimitExceeded(BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorRssLimitExceeded error(GetCurrentTidOrInvalid(), stack); |
| in_report.ReportError(error); |
| } |
| |
| void ReportOutOfMemory(uptr requested_size, BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report(/*fatal*/ true); |
| ErrorOutOfMemory error(GetCurrentTidOrInvalid(), stack, requested_size); |
| in_report.ReportError(error); |
| } |
| |
| void ReportStringFunctionMemoryRangesOverlap(const char *function, |
| const char *offset1, uptr length1, |
| const char *offset2, uptr length2, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| ErrorStringFunctionMemoryRangesOverlap error( |
| GetCurrentTidOrInvalid(), stack, (uptr)offset1, length1, (uptr)offset2, |
| length2, function); |
| in_report.ReportError(error); |
| } |
| |
| void ReportStringFunctionSizeOverflow(uptr offset, uptr size, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| ErrorStringFunctionSizeOverflow error(GetCurrentTidOrInvalid(), stack, offset, |
| size); |
| in_report.ReportError(error); |
| } |
| |
| void ReportBadParamsToAnnotateContiguousContainer(uptr beg, uptr end, |
| uptr old_mid, uptr new_mid, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| ErrorBadParamsToAnnotateContiguousContainer error( |
| GetCurrentTidOrInvalid(), stack, beg, end, old_mid, new_mid); |
| in_report.ReportError(error); |
| } |
| |
| void ReportBadParamsToAnnotateDoubleEndedContiguousContainer( |
| uptr storage_beg, uptr storage_end, uptr old_container_beg, |
| uptr old_container_end, uptr new_container_beg, uptr new_container_end, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| ErrorBadParamsToAnnotateDoubleEndedContiguousContainer error( |
| GetCurrentTidOrInvalid(), stack, storage_beg, storage_end, |
| old_container_beg, old_container_end, new_container_beg, |
| new_container_end); |
| in_report.ReportError(error); |
| } |
| |
| void ReportODRViolation(const __asan_global *g1, u32 stack_id1, |
| const __asan_global *g2, u32 stack_id2) { |
| ScopedInErrorReport in_report; |
| ErrorODRViolation error(GetCurrentTidOrInvalid(), g1, stack_id1, g2, |
| stack_id2); |
| in_report.ReportError(error); |
| } |
| |
| // ----------------------- CheckForInvalidPointerPair ----------- {{{1 |
| static NOINLINE void ReportInvalidPointerPair(uptr pc, uptr bp, uptr sp, |
| uptr a1, uptr a2) { |
| ScopedInErrorReport in_report; |
| ErrorInvalidPointerPair error(GetCurrentTidOrInvalid(), pc, bp, sp, a1, a2); |
| in_report.ReportError(error); |
| } |
| |
| static bool IsInvalidPointerPair(uptr a1, uptr a2) { |
| if (a1 == a2) |
| return false; |
| |
| // 256B in shadow memory can be iterated quite fast |
| static const uptr kMaxOffset = 2048; |
| |
| uptr left = a1 < a2 ? a1 : a2; |
| uptr right = a1 < a2 ? a2 : a1; |
| uptr offset = right - left; |
| if (offset <= kMaxOffset) |
| return __asan_region_is_poisoned(left, offset); |
| |
| AsanThread *t = GetCurrentThread(); |
| |
| // check whether left is a stack memory pointer |
| if (uptr shadow_offset1 = t->GetStackVariableShadowStart(left)) { |
| uptr shadow_offset2 = t->GetStackVariableShadowStart(right); |
| return shadow_offset2 == 0 || shadow_offset1 != shadow_offset2; |
| } |
| |
| // check whether left is a heap memory address |
| HeapAddressDescription hdesc1, hdesc2; |
| if (GetHeapAddressInformation(left, 0, &hdesc1) && |
| hdesc1.chunk_access.access_type == kAccessTypeInside) |
| return !GetHeapAddressInformation(right, 0, &hdesc2) || |
| hdesc2.chunk_access.access_type != kAccessTypeInside || |
| hdesc1.chunk_access.chunk_begin != hdesc2.chunk_access.chunk_begin; |
| |
| // check whether left is an address of a global variable |
| GlobalAddressDescription gdesc1, gdesc2; |
| if (GetGlobalAddressInformation(left, 0, &gdesc1)) |
| return !GetGlobalAddressInformation(right - 1, 0, &gdesc2) || |
| !gdesc1.PointsInsideTheSameVariable(gdesc2); |
| |
| if (t->GetStackVariableShadowStart(right) || |
| GetHeapAddressInformation(right, 0, &hdesc2) || |
| GetGlobalAddressInformation(right - 1, 0, &gdesc2)) |
| return true; |
| |
| // At this point we know nothing about both a1 and a2 addresses. |
| return false; |
| } |
| |
| static inline void CheckForInvalidPointerPair(void *p1, void *p2) { |
| switch (flags()->detect_invalid_pointer_pairs) { |
| case 0: |
| return; |
| case 1: |
| if (p1 == nullptr || p2 == nullptr) |
| return; |
| break; |
| } |
| |
| uptr a1 = reinterpret_cast<uptr>(p1); |
| uptr a2 = reinterpret_cast<uptr>(p2); |
| |
| if (IsInvalidPointerPair(a1, a2)) { |
| GET_CALLER_PC_BP_SP; |
| ReportInvalidPointerPair(pc, bp, sp, a1, a2); |
| } |
| } |
| // ----------------------- Mac-specific reports ----------------- {{{1 |
| |
| void ReportMacMzReallocUnknown(uptr addr, uptr zone_ptr, const char *zone_name, |
| BufferedStackTrace *stack) { |
| ScopedInErrorReport in_report; |
| Printf( |
| "mz_realloc(%p) -- attempting to realloc unallocated memory.\n" |
| "This is an unrecoverable problem, exiting now.\n", |
| (void *)addr); |
| PrintZoneForPointer(addr, zone_ptr, zone_name); |
| stack->Print(); |
| DescribeAddressIfHeap(addr); |
| } |
| |
| // -------------- SuppressErrorReport -------------- {{{1 |
| // Avoid error reports duplicating for ASan recover mode. |
| static bool SuppressErrorReport(uptr pc) { |
| if (!common_flags()->suppress_equal_pcs) return false; |
| for (unsigned i = 0; i < kAsanBuggyPcPoolSize; i++) { |
| uptr cmp = atomic_load_relaxed(&AsanBuggyPcPool[i]); |
| if (cmp == 0 && atomic_compare_exchange_strong(&AsanBuggyPcPool[i], &cmp, |
| pc, memory_order_relaxed)) |
| return false; |
| if (cmp == pc) return true; |
| } |
| Die(); |
| } |
| |
| void ReportGenericError(uptr pc, uptr bp, uptr sp, uptr addr, bool is_write, |
| uptr access_size, u32 exp, bool fatal) { |
| if (__asan_test_only_reported_buggy_pointer) { |
| *__asan_test_only_reported_buggy_pointer = addr; |
| return; |
| } |
| if (!fatal && SuppressErrorReport(pc)) return; |
| ENABLE_FRAME_POINTER; |
| |
| // Optimization experiments. |
| // The experiments can be used to evaluate potential optimizations that remove |
| // instrumentation (assess false negatives). Instead of completely removing |
| // some instrumentation, compiler can emit special calls into runtime |
| // (e.g. __asan_report_exp_load1 instead of __asan_report_load1) and pass |
| // mask of experiments (exp). |
| // The reaction to a non-zero value of exp is to be defined. |
| (void)exp; |
| |
| ScopedInErrorReport in_report(fatal); |
| ErrorGeneric error(GetCurrentTidOrInvalid(), pc, bp, sp, addr, is_write, |
| access_size); |
| in_report.ReportError(error); |
| } |
| |
| } // namespace __asan |
| |
| // --------------------------- Interface --------------------- {{{1 |
| using namespace __asan; |
| |
| void __asan_report_error(uptr pc, uptr bp, uptr sp, uptr addr, int is_write, |
| uptr access_size, u32 exp) { |
| ENABLE_FRAME_POINTER; |
| bool fatal = flags()->halt_on_error; |
| ReportGenericError(pc, bp, sp, addr, is_write, access_size, exp, fatal); |
| } |
| |
| void NOINLINE __asan_set_error_report_callback(void (*callback)(const char*)) { |
| Lock l(&error_message_buf_mutex); |
| error_report_callback = callback; |
| } |
| |
| void __asan_describe_address(uptr addr) { |
| // Thread registry must be locked while we're describing an address. |
| asanThreadRegistry().Lock(); |
| PrintAddressDescription(addr, 1, ""); |
| asanThreadRegistry().Unlock(); |
| } |
| |
| int __asan_report_present() { |
| return ScopedInErrorReport::CurrentError().kind != kErrorKindInvalid; |
| } |
| |
| uptr __asan_get_report_pc() { |
| if (ScopedInErrorReport::CurrentError().kind == kErrorKindGeneric) |
| return ScopedInErrorReport::CurrentError().Generic.pc; |
| return 0; |
| } |
| |
| uptr __asan_get_report_bp() { |
| if (ScopedInErrorReport::CurrentError().kind == kErrorKindGeneric) |
| return ScopedInErrorReport::CurrentError().Generic.bp; |
| return 0; |
| } |
| |
| uptr __asan_get_report_sp() { |
| if (ScopedInErrorReport::CurrentError().kind == kErrorKindGeneric) |
| return ScopedInErrorReport::CurrentError().Generic.sp; |
| return 0; |
| } |
| |
| uptr __asan_get_report_address() { |
| ErrorDescription &err = ScopedInErrorReport::CurrentError(); |
| if (err.kind == kErrorKindGeneric) |
| return err.Generic.addr_description.Address(); |
| else if (err.kind == kErrorKindDoubleFree) |
| return err.DoubleFree.addr_description.addr; |
| return 0; |
| } |
| |
| int __asan_get_report_access_type() { |
| if (ScopedInErrorReport::CurrentError().kind == kErrorKindGeneric) |
| return ScopedInErrorReport::CurrentError().Generic.is_write; |
| return 0; |
| } |
| |
| uptr __asan_get_report_access_size() { |
| if (ScopedInErrorReport::CurrentError().kind == kErrorKindGeneric) |
| return ScopedInErrorReport::CurrentError().Generic.access_size; |
| return 0; |
| } |
| |
| const char *__asan_get_report_description() { |
| if (ScopedInErrorReport::CurrentError().kind == kErrorKindGeneric) |
| return ScopedInErrorReport::CurrentError().Generic.bug_descr; |
| return ScopedInErrorReport::CurrentError().Base.scariness.GetDescription(); |
| } |
| |
| extern "C" { |
| SANITIZER_INTERFACE_ATTRIBUTE |
| void __sanitizer_ptr_sub(void *a, void *b) { |
| CheckForInvalidPointerPair(a, b); |
| } |
| SANITIZER_INTERFACE_ATTRIBUTE |
| void __sanitizer_ptr_cmp(void *a, void *b) { |
| CheckForInvalidPointerPair(a, b); |
| } |
| } // extern "C" |
| |
| // Provide default implementation of __asan_on_error that does nothing |
| // and may be overriden by user. |
| SANITIZER_INTERFACE_WEAK_DEF(void, __asan_on_error, void) {} |