blob: 952c0b8978d8280905889b11048361168b358004 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/enterprise_companion/crash_client.h"
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/base_paths.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/debug/dump_without_crashing.h"
#include "base/environment.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/sequence_checker.h"
#include "chrome/enterprise_companion/enterprise_companion.h"
#include "chrome/enterprise_companion/enterprise_companion_branding.h"
#include "chrome/enterprise_companion/enterprise_companion_client.h"
#include "chrome/enterprise_companion/enterprise_companion_version.h"
#include "chrome/enterprise_companion/installer_paths.h"
#include "third_party/crashpad/crashpad/client/crash_report_database.h"
#include "third_party/crashpad/crashpad/client/crashpad_client.h"
#include "third_party/crashpad/crashpad/client/crashpad_info.h"
#include "third_party/crashpad/crashpad/client/settings.h"
#include "third_party/crashpad/crashpad/client/simulate_crash.h"
#include "third_party/crashpad/crashpad/handler/handler_main.h"
#include "third_party/crashpad/crashpad/util/misc/tri_state.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include <iterator>
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/wrapped_window_proc.h"
namespace {
int __cdecl HandleWinProcException(EXCEPTION_POINTERS* exception_pointers) {
crashpad::CrashpadClient::DumpAndCrash(exception_pointers);
return EXCEPTION_CONTINUE_SEARCH;
}
} // namespace
#endif // BUILDFLAG(IS_WIN)
namespace enterprise_companion {
namespace {
constexpr char kNoRateLimitSwitch[] = "no-rate-limit";
constexpr char kUsageStatsEnabledEnvVar[] = "GOOGLE_USAGE_STATS_ENABLED";
constexpr char kUsageStatsEnabledEnvVarValueEnabled[] = "1";
// Determines if crash dump uploading should be enabled.
bool ShouldEnableCrashUploads() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
kEnableUsageStatsSwitch)) {
return true;
}
std::string env_usage_stats;
if (base::Environment::Create()->GetVar(kUsageStatsEnabledEnvVar,
&env_usage_stats) &&
env_usage_stats == kUsageStatsEnabledEnvVarValueEnabled) {
return true;
}
return false;
}
std::vector<std::string> MakeCrashHandlerArgs() {
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitch(kCrashHandlerSwitch);
// The first element in the command line arguments is the program name,
// which must be skipped.
#if BUILDFLAG(IS_WIN)
std::vector<std::string> args;
base::ranges::transform(
++command_line.argv().begin(), command_line.argv().end(),
std::back_inserter(args),
[](const auto& arg) { return base::WideToUTF8(arg); });
return args;
#else
return {++command_line.argv().begin(), command_line.argv().end()};
#endif
}
class CrashClient {
public:
static CrashClient* GetInstance() {
static base::NoDestructor<CrashClient> crash_client;
return crash_client.get();
}
bool InitializeCrashReporting(
std::optional<base::FilePath> crash_database_path) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static bool initialized = false;
CHECK(!initialized);
initialized = true;
if (!crash_database_path || !base::CreateDirectory(*crash_database_path)) {
LOG(ERROR) << "Failed to get the database path.";
return false;
}
database_ = crashpad::CrashReportDatabase::Initialize(*crash_database_path);
if (!database_) {
LOG(ERROR) << "Failed to initialize Crashpad database.";
return false;
}
base::debug::SetDumpWithoutCrashingFunction(
[] { CRASHPAD_SIMULATE_CRASH(); });
#if BUILDFLAG(IS_WIN)
// Catch exceptions thrown from a window procedure.
base::win::WinProcExceptionFilter exception_filter =
base::win::SetWinProcExceptionFilter(&HandleWinProcException);
LOG_IF(DFATAL, exception_filter) << "Exception filter already present";
#endif // BUILDFLAG(IS_WIN)
std::vector<crashpad::CrashReportDatabase::Report> reports_completed;
const crashpad::CrashReportDatabase::OperationStatus status_completed =
database_->GetCompletedReports(&reports_completed);
if (status_completed == crashpad::CrashReportDatabase::kNoError) {
VLOG(1) << "Found " << reports_completed.size()
<< " completed crash reports";
for (const auto& report : reports_completed) {
VLOG(3) << "Crash since last run: ID \"" << report.id
<< "\", created at " << report.creation_time << ", "
<< report.upload_attempts << " upload attempts, file path \""
<< report.file_path << "\", unique ID \""
<< report.uuid.ToString()
<< "\"; uploaded: " << (report.uploaded ? "yes" : "no");
}
} else {
LOG(ERROR) << "Failed to fetch completed crash reports: "
<< status_completed;
}
std::vector<crashpad::CrashReportDatabase::Report> reports_pending;
const crashpad::CrashReportDatabase::OperationStatus status_pending =
database_->GetPendingReports(&reports_pending);
if (status_pending == crashpad::CrashReportDatabase::kNoError) {
VLOG(1) << "Found " << reports_pending.size() << " pending crash reports";
for (const auto& report : reports_pending) {
VLOG(1) << "Crash since last run: (pending), created at "
<< report.creation_time << ", " << report.upload_attempts
<< " upload attempts, file path \"" << report.file_path
<< "\", unique ID \"" << report.uuid.ToString() << "\"";
}
} else {
LOG(ERROR) << "Failed to fetch pending crash reports: " << status_pending;
}
if (ShouldEnableCrashUploads()) {
VLOG(2) << "Crash uploading is enabled.";
crashpad::Settings* crashpad_settings = database_->GetSettings();
CHECK(crashpad_settings);
LOG_IF(ERROR, !crashpad_settings->SetUploadsEnabled(true))
<< "Failed to set crash upload opt-in.";
}
return StartCrashReporter(*crash_database_path);
}
private:
friend class base::NoDestructor<CrashClient>;
CrashClient() = default;
~CrashClient() = default;
SEQUENCE_CHECKER(sequence_checker_);
std::unique_ptr<crashpad::CrashReportDatabase> database_;
bool StartCrashReporter(base::FilePath database_path) {
static base::NoDestructor<crashpad::CrashpadClient> client;
static bool started = false;
CHECK(!started);
started = true;
std::map<std::string, std::string> annotations;
annotations["ver"] = kEnterpriseCompanionVersion;
annotations["prod"] = CRASH_PRODUCT_NAME;
// Save dereferenced memory from all registers on the crashing thread.
// Crashpad saves up to 512 bytes per CPU register, and in the worst case,
// ARM64 has 32 registers.
constexpr uint32_t kIndirectMemoryLimit = 32 * 512;
crashpad::CrashpadInfo::GetCrashpadInfo()
->set_gather_indirectly_referenced_memory(crashpad::TriState::kEnabled,
kIndirectMemoryLimit);
std::vector<base::FilePath> attachments;
#if !BUILDFLAG(IS_MAC) // Crashpad does not support attachments on macOS.
std::optional<base::FilePath> log_file = GetLogFilePath();
if (log_file) {
attachments.push_back(*log_file);
}
#endif
if (!client->StartHandler(
base::PathService::CheckedGet(base::FILE_EXE), database_path,
/*metrics_dir=*/base::FilePath(), CRASH_UPLOAD_URL, annotations,
MakeCrashHandlerArgs(),
/*restartable=*/true,
/*asynchronous_start=*/false)) {
VLOG(1) << "Failed to start handler.";
return false;
}
VLOG(1) << "Crash handler launched and ready";
return true;
}
};
} // namespace
std::optional<base::FilePath> GetDefaultCrashDatabasePath() {
std::optional<base::FilePath> path = GetInstallDirectory();
return path ? path->Append(FILE_PATH_LITERAL("Crashpad")) : path;
}
bool InitializeCrashReporting(
std::optional<base::FilePath> crash_database_path) {
return CrashClient::GetInstance()->InitializeCrashReporting(
crash_database_path);
}
int CrashReporterMain() {
base::CommandLine command_line = *base::CommandLine::ForCurrentProcess();
CHECK(command_line.HasSwitch(kCrashHandlerSwitch));
// Disable rate-limiting until this is fixed:
// https://bugs.chromium.org/p/crashpad/issues/detail?id=23
command_line.AppendSwitch(kNoRateLimitSwitch);
// Because of https://bugs.chromium.org/p/crashpad/issues/detail?id=82,
// Crashpad fails on the presence of flags it doesn't handle.
command_line.RemoveSwitch(kCrashHandlerSwitch);
command_line.RemoveSwitch(kLoggingModuleSwitch);
const std::vector<base::CommandLine::StringType> argv = command_line.argv();
// |storage| must be declared before |argv_as_utf8|, to ensure it outlives
// |argv_as_utf8|, which will hold pointers into |storage|.
std::vector<std::string> storage;
auto argv_as_utf8 = std::make_unique<char*[]>(argv.size() + 1);
storage.reserve(argv.size());
for (size_t i = 0; i < argv.size(); ++i) {
#if BUILDFLAG(IS_WIN)
storage.push_back(base::WideToUTF8(argv[i]));
#else
storage.push_back(argv[i]);
#endif
argv_as_utf8[i] = &storage[i][0];
}
argv_as_utf8[argv.size()] = nullptr;
return crashpad::HandlerMain(argv.size(), argv_as_utf8.get(),
/*user_stream_sources=*/nullptr);
}
} // namespace enterprise_companion