blob: 7e7c34c3779ff315e8f20a010cb634c83c25fadf [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 "remoting/base/breakpad_utils.h"
#include <utility>
#include "base/base_paths.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "remoting/base/version.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
namespace remoting {
// This class is allowlisted in thread_restrictions.h.
class ScopedAllowBlockingForCrashReporting : public base::ScopedAllowBlocking {
};
namespace {
const base::BasePathKey kBasePathKey =
#if BUILDFLAG(IS_WIN)
// We can't use %TEMP% for Windows because our processes run as SYSTEM,
// Local Service, and the user. SYSTEM processes will write to %WINDIR%\Temp
// which we don't want to do and if a crash occurs before login, there isn't
// a user temp env var to query. Because of these issues, we store the crash
// dumps, which are usually < 100KB, in the install folder so they will get
// cleaned up when the user uninstalls or we push an update.
base::BasePathKey::DIR_ASSETS;
#else
base::BasePathKey::DIR_TEMP;
#endif
const base::FilePath::CharType kMinidumpsPath[] =
#if BUILDFLAG(IS_WIN)
FILE_PATH_LITERAL("minidumps");
#else
FILE_PATH_LITERAL("chromoting/minidumps");
#endif
const base::FilePath::CharType kTempExtension[] = FILE_PATH_LITERAL("temp");
const base::FilePath::CharType kJsonExtension[] = FILE_PATH_LITERAL("json");
} // namespace
const char kBreakpadProductVersionKey[] = "product_version";
const char kBreakpadProcessStartTimeKey[] = "process_start_time";
const char kBreakpadProcessIdKey[] = "process_id";
const char kBreakpadProcessNameKey[] = "process_name";
const char kBreakpadProcessUptimeKey[] = "process_uptime";
#if BUILDFLAG(IS_WIN)
const wchar_t kCrashServerPipeName[] =
L"\\\\.\\pipe\\RemotingCrashService\\S-1-5-18";
base::win::ScopedHandle GetClientHandleForCrashServerPipe() {
const ACCESS_MASK kPipeAccessMask = FILE_READ_ATTRIBUTES | FILE_READ_DATA |
FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA |
SYNCHRONIZE;
const DWORD kPipeFlagsAndAttributes =
SECURITY_IDENTIFICATION | SECURITY_SQOS_PRESENT;
SECURITY_ATTRIBUTES security_attributes = {0};
security_attributes.bInheritHandle = true;
base::win::ScopedHandle handle(
CreateFile(kCrashServerPipeName, kPipeAccessMask,
/*dwShareMode=*/0, &security_attributes, OPEN_EXISTING,
kPipeFlagsAndAttributes,
/*hTemplateFile=*/nullptr));
if (!handle.get()) {
PLOG(ERROR) << "Failed to open named pipe to crash server.";
}
return handle;
}
#endif // BUILDFLAG(IS_WIN)
base::FilePath GetMinidumpDirectoryPath() {
base::FilePath base_path;
if (base::PathService::Get(kBasePathKey, &base_path)) {
return base_path.Append(kMinidumpsPath);
}
LOG(ERROR) << "Failed to retrieve a directory for crash reporting.";
return base::FilePath();
}
bool CreateMinidumpDirectoryIfNeeded(const base::FilePath& minidump_directory) {
if (!base::DirectoryExists(minidump_directory) &&
!base::CreateDirectory(minidump_directory)) {
LOG(ERROR) << "Failed to create minidump directory: " << minidump_directory;
return false;
}
return true;
}
bool WriteMetadataForMinidump(const base::FilePath& minidump_file_path,
base::Value::Dict metadata) {
auto metadata_file_contents = base::WriteJson(metadata);
if (!metadata_file_contents.has_value()) {
LOG(ERROR) << "Failed to convert metadata to JSON.";
return false;
}
ScopedAllowBlockingForCrashReporting scoped_allow_blocking;
auto temp_metadata_file_path =
base::FilePath(minidump_file_path).ReplaceExtension(kTempExtension);
if (!base::WriteFile(temp_metadata_file_path, *metadata_file_contents)) {
LOG(ERROR) << "Failed to write crash dump metadata to temp file.";
return false;
}
auto metadata_file_path =
temp_metadata_file_path.ReplaceExtension(kJsonExtension);
if (!base::Move(temp_metadata_file_path, metadata_file_path)) {
LOG(ERROR) << "Failed to rename temp metadata file.";
return false;
}
return true;
}
BreakpadHelper::BreakpadHelper() = default;
BreakpadHelper::~BreakpadHelper() = default;
bool BreakpadHelper::Initialize(const base::FilePath& minidump_directory) {
CHECK(!initialized_);
const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
// This includes both executable name and the path such as:
// /opt/google/chrome-remote-desktop/chrome-remote-desktop-host
// or
// C:\\Program Files (x86)\\Chromoting\\127.0.6498.0\\remoting_host.exe
process_name_ = cmd_line->GetProgram();
if (!CreateMinidumpDirectoryIfNeeded(minidump_directory)) {
return false;
}
initialized_ = true;
return initialized_;
}
void BreakpadHelper::OnException() {
// Shared by in-proc and out-of-proc exception handlers.
if (handling_exception_.exchange(true)) {
base::PlatformThread::Sleep(base::TimeDelta::Max());
}
}
bool BreakpadHelper::OnMinidumpGenerated(
const base::FilePath& minidump_file_path) {
CHECK(initialized_);
if (!handling_exception_.exchange(true)) {
// Log a warning that the caller should be using OnException() to indicate
// when exception processing has started. Don't block so the current
// exception is handled.
LOG(WARNING) << "OnException() not called in filter callback.";
}
auto process_uptime = base::Time::NowFromSystemTime() - process_start_time_;
auto metadata =
base::Value::Dict()
.Set(kBreakpadProcessIdKey, static_cast<int>(process_id_))
.Set(kBreakpadProcessNameKey, process_name_.MaybeAsASCII())
.Set(kBreakpadProcessStartTimeKey,
base::NumberToString(process_start_time_.ToTimeT()))
.Set(kBreakpadProcessUptimeKey,
base::NumberToString(process_uptime.InMilliseconds()))
.Set(kBreakpadProductVersionKey, REMOTING_VERSION_STRING);
bool metadata_written =
WriteMetadataForMinidump(minidump_file_path, std::move(metadata));
handling_exception_.exchange(false);
return metadata_written;
}
} // namespace remoting