1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147

base / debug / asan_service.cc [blame]

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif

#include "base/debug/asan_service.h"

#if defined(ADDRESS_SANITIZER)
#include <sanitizer/asan_interface.h>

#include "base/debug/task_trace.h"
#include "base/no_destructor.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"

#if BUILDFLAG(IS_WIN)
#include "base/logging.h"
#include "base/win/windows_types.h"
#endif  // BUILDFLAG(IS_WIN)

#if defined(COMPONENT_BUILD) && BUILDFLAG(IS_WIN)
// In component builds on Windows, weak function exported by ASan have the
// `__dll` suffix. ASan itself uses the `alternatename` directive to account for
// that.
#pragma comment(linker,                                                \
                    "/alternatename:__sanitizer_report_error_summary=" \
                    "__sanitizer_report_error_summary__dll")
#pragma comment(linker,                                     \
                "/alternatename:__sanitizer_set_report_fd=" \
                "__sanitizer_set_report_fd__dll")
#endif  // defined(COMPONENT_BUILD) && BUILDFLAG(IS_WIN)

namespace base {
namespace debug {

namespace {
NO_SANITIZE("address")
void TaskTraceErrorCallback(const char* error, bool*) {
  // Use the sanitizer api to symbolize the task trace, which otherwise might
  // not symbolize properly. This also lets us format the task trace in the
  // same way as the address sanitizer backtraces, which also means that we can
  // get the stack trace symbolized with asan_symbolize.py in the cases where
  // symbolization at runtime fails.
  std::array<const void*, 4> addresses;
  size_t address_count = TaskTrace().GetAddresses(addresses);

  AsanService::GetInstance()->Log("Task trace:");
  size_t frame_index = 0;
  for (size_t i = 0; i < std::min(address_count, addresses.size()); ++i) {
    char buffer[4096] = {};
    void* address = const_cast<void*>(addresses[i]);
    __sanitizer_symbolize_pc(address, "%p %F %L", buffer, sizeof(buffer));
    for (char* ptr = buffer; *ptr != 0; ptr += strlen(ptr)) {
      AsanService::GetInstance()->Log("    #%i %s", frame_index++, ptr);
    }
  }
  AsanService::GetInstance()->Log("");
}
}  // namespace

// static
NO_SANITIZE("address")
AsanService* AsanService::GetInstance() {
  static NoDestructor<AsanService> instance;
  return instance.get();
}

void AsanService::Initialize() {
  AutoLock lock(lock_);
  if (!is_initialized_) {
#if BUILDFLAG(IS_WIN)
    if (logging::IsLoggingToFileEnabled()) {
      // Sandboxed processes cannot open files but are provided a HANDLE.
      HANDLE log_handle = logging::DuplicateLogFileHandle();
      if (log_handle) {
        // Sanitizer APIs need a HANDLE cast to void*.
        __sanitizer_set_report_fd(reinterpret_cast<void*>(log_handle));
      }
    }
#endif  // BUILDFLAG(IS_WIN)
    __asan_set_error_report_callback(ErrorReportCallback);
    error_callbacks_.push_back(TaskTraceErrorCallback);
    is_initialized_ = true;
  }
}

NO_SANITIZE("address")
void AsanService::Log(const char* format, ...) {
  va_list ap;
  va_start(ap, format);
  auto formatted_message = StringPrintV(format, ap);
  va_end(ap);

  // Despite its name, the function just prints the input to the destination
  // configured by ASan.
  __sanitizer_report_error_summary(formatted_message.c_str());
}

void AsanService::AddErrorCallback(ErrorCallback error_callback) {
  AutoLock lock(lock_);
  CHECK(is_initialized_);
  error_callbacks_.push_back(error_callback);
}

NO_SANITIZE("address")
void AsanService::RunErrorCallbacks(const char* reason) {
  ProcessId process_id = GetCurrentProcId();
  bool should_exit_cleanly = false;

  {
    // We can hold `lock_` throughout the error callbacks, since ASan doesn't
    // re-enter when handling nested errors on the same thread.
    AutoLock lock(lock_);

    Log("\n==%i==ADDITIONAL INFO", (int)process_id);
    Log("\n==%i==Note: Please include this section with the ASan report.",
        (int)process_id);
    for (const auto& error_callback : error_callbacks_) {
      error_callback(reason, &should_exit_cleanly);
    }
    Log("\n==%i==END OF ADDITIONAL INFO", (int)process_id);
  }

  if (should_exit_cleanly) {
    Log("\n==%i==EXITING", (int)process_id);
    Process::TerminateCurrentProcessImmediately(0);
  }
}

// static
NO_SANITIZE("address")
void AsanService::ErrorReportCallback(const char* reason) {
  AsanService::GetInstance()->RunErrorCallbacks(reason);
}

AsanService::AsanService() = default;

}  // namespace debug
}  // namespace base

#endif  // defined(ADDRESS_SANITIZER)