blob: c05bd58ec9b8d78be5b7923fd849b7bdcbe77100 [file]
// 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.
#include "base/debug/asan_service.h"
#include "base/compiler_specific.h"
#if defined(ADDRESS_SANITIZER)
#include <sanitizer/asan_interface.h>
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/debug/task_trace.h"
#include "base/environment.h"
#include "base/no_destructor.h"
#include "base/process/process.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringize_macros.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* reason,
bool* should_exit_cleanly,
bool* should_abort) {
// 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 (const char* ptr = buffer; *ptr != 0;
UNSAFE_TODO(ptr += strlen(ptr) + 1)) {
AsanService::GetInstance()->Log(" #%i %s", frame_index++, ptr);
}
}
AsanService::GetInstance()->Log("");
}
constexpr std::string_view kKeyValuePairDelimiters = " ,:\n\t\r";
NO_SANITIZE("address")
void ParseAsanFlagFromString(std::string_view input,
flat_map<std::string, std::string>& flags) {
for (std::string_view token : base::SplitStringPiece(
input, kKeyValuePairDelimiters, base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (auto pair = base::SplitStringOnce(token, '=')) {
flags[std::string(pair->first)] = std::string(pair->second);
}
}
}
// Asan doesn't provide its current flags. We will emulate almost the same way
// as Asan initializes its flags.
NO_SANITIZE("address")
void ParseAsanFlags(flat_map<std::string, std::string>& flags) {
// Asan default value.
// Asan compile definition: ASAN_DEFAULT_OPTIONS.
#if defined(ASAN_DEFAULT_OPTIONS)
ParseAsanFlagFromString(STRINGIZE(ASAN_DEFAULT_OPTIONS), flags);
#endif
// Override from user-specified string.
ParseAsanFlagFromString(__asan_default_options(), flags);
// Override from command line: ASAN_OPTIONS.
std::unique_ptr<Environment> env(Environment::Create());
std::optional<std::string> asan_options = env->GetVar("ASAN_OPTIONS");
if (asan_options.has_value()) {
ParseAsanFlagFromString(asan_options.value(), flags);
}
}
int GetAsanIntFlag(const flat_map<std::string, std::string>& flags,
std::string_view flag_name,
int default_value) {
const auto& it = flags.find(flag_name);
if (it == flags.cend()) {
return default_value;
}
int value;
if (StringToInt(it->second, &value)) {
return value;
}
return default_value;
}
bool GetAsanBoolFlag(const flat_map<std::string, std::string>& flags,
std::string_view flag_name,
bool default_value) {
const auto& it = flags.find(flag_name);
if (it == flags.cend()) {
return default_value;
}
if (it->second == "true" || it->second == "1") {
return true;
}
if (it->second == "false" || it->second == "0") {
return false;
}
return default_value;
}
} // 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);
// Default values as per
// https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
flat_map<std::string, std::string> flags;
ParseAsanFlags(flags);
halt_on_error_ =
GetAsanBoolFlag(flags, "halt_on_error", /*default value=*/true);
detect_leak_ = GetAsanBoolFlag(flags, "detect_leaks",
#if BUILDFLAG(IS_APPLE)
/*default value=*/false
#else
/*default value=*/true
#endif
);
exitcode_ = GetAsanIntFlag(flags, "exitcode", /*default value=*/1);
is_initialized_ = true;
}
}
NO_SANITIZE("address")
void AsanService::Log(const char* format, ...) {
va_list ap;
va_start(ap, format);
auto formatted_message = UNSAFE_TODO(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::Abort() {
Process::TerminateCurrentProcessImmediately(exitcode_);
}
void AsanService::AddErrorCallback(ErrorCallback error_callback) {
AutoLock lock(lock_);
CHECK(is_initialized_);
error_callbacks_.push_back(error_callback);
}
void AsanService::ResetErrorCallbacksForTesting() {
AutoLock lock(lock_);
error_callbacks_.clear();
}
NO_SANITIZE("address")
void AsanService::RunErrorCallbacks(const char* reason) {
ProcessId process_id = GetCurrentProcId();
bool should_exit_cleanly = false;
bool should_abort = true;
// 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, &should_abort);
}
Log("\n==%i==END OF ADDITIONAL INFO", (int)process_id);
if (should_exit_cleanly) {
Log("\n==%i==EXITING", (int)process_id);
Process::TerminateCurrentProcessImmediately(0);
} else if (should_abort) {
Log("\n==%i==ABORTING", (int)process_id);
Abort();
} else if (halt_on_error()) {
Log("AsanService ErrorCallback has cleared should_abort, but ASAN_OPTIONS "
"does not contain halt_on_error=0, so AddressSanitizer will abort!");
}
}
// 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)