blob: 4504d9a8f06f4ab1aa89b0f3ff1c6deb27a45eb8 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crash-reporter/vm_support_proper.h"
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <brillo/key_value_store.h>
#include <chromeos/constants/vm_tools.h>
#include <grpcpp/grpcpp.h>
#include <sys/socket.h>
#include <utility>
#include <linux/vm_sockets.h>
#include "crash-reporter/constants.h"
#include "crash-reporter/paths.h"
#include "crash-reporter/user_collector.h"
#include "crash-reporter/util.h"
namespace {
constexpr char kLsbBoardKey[] = "CHROMEOS_RELEASE_BOARD";
constexpr char kOsNameKey[] = "PRETTY_NAME";
constexpr char kContainerOsReleasePath[] =
"/mnt/stateful/lxd/storage-pools/default/containers/penguin/rootfs/etc/"
"os-release";
} // namespace
class ScopedFileDeleter {
public:
explicit ScopedFileDeleter(base::FilePath path) : path_(path) {}
~ScopedFileDeleter() {
if (!base::DeleteFile(path_)) {
LOG(ERROR) << "Failed to delete file " << path_.value();
}
}
private:
const base::FilePath path_;
};
VmSupportProper::VmSupportProper() {
std::string addr = base::StringPrintf("vsock:%u:%u", VMADDR_CID_HOST,
vm_tools::kCrashListenerPort);
// It's safe to use an unencrypted/authenticated channel here because the
// whole channel exists within a single machine, and so we can rely on the
// kernel to provide us with confidentiality and integrity. Our usage of a
// vsock address guarantees this.
auto channel =
grpc::CreateChannel(std::move(addr), grpc::InsecureChannelCredentials());
stub_ = std::make_unique<vm_tools::cicerone::CrashListener::Stub>(
std::move(channel));
}
void VmSupportProper::AddMetadata(UserCollector* collector) {
std::string value;
base::FilePath lsb_path =
base::FilePath(paths::kEtcDirectory).Append(paths::kLsbRelease);
util::GetCachedKeyValue(lsb_path.BaseName(), kLsbBoardKey,
{lsb_path.DirName()}, &value);
collector->AddCrashMetaData("board", value);
base::FilePath os_path = base::FilePath(kContainerOsReleasePath);
util::GetCachedKeyValue(os_path.BaseName(), kOsNameKey, {os_path.DirName()},
&value);
collector->AddCrashMetaData("upload_var_vm_os_release", value);
}
void VmSupportProper::ProcessFileData(
const base::FilePath& crash_meta_path,
const brillo::KeyValueStore& metadata,
const std::string& key,
vm_tools::cicerone::CrashReport* crash_report) {
std::string file_name;
metadata.GetString(key, &file_name);
base::FilePath path = crash_meta_path.DirName().Append(file_name);
ScopedFileDeleter file_deleter(path);
std::string* dest = nullptr;
if (key == "payload") {
dest = crash_report->mutable_minidump();
} else if (key ==
std::string(constants::kUploadTextPrefix) + "process_tree") {
dest = crash_report->mutable_process_tree();
}
if (dest && !base::ReadFileToString(path, dest)) {
LOG(ERROR) << "Failed to read file " << file_name;
}
}
void VmSupportProper::FinishCrash(const base::FilePath& crash_meta_path) {
// We send crash reports outside the VM via GRPC instead of storing them on
// disk, so we delete files as we finish processing them.
ScopedFileDeleter metadata_deleter(crash_meta_path);
brillo::KeyValueStore metadata;
if (!metadata.Load(crash_meta_path)) {
LOG(ERROR) << "Failed to read metadata file";
return;
}
grpc::ClientContext ctx;
vm_tools::cicerone::CrashReport crash_report;
vm_tools::EmptyMessage response;
for (const auto& key : metadata.GetKeys()) {
// These keys store file names, not raw values, which need to be read into
// the crash report protobuf and deleted.
if (base::StartsWith(key, constants::kUploadFilePrefix,
base::CompareCase::SENSITIVE) ||
base::StartsWith(key, constants::kUploadTextPrefix,
base::CompareCase::SENSITIVE) ||
key == "payload") {
ProcessFileData(crash_meta_path, metadata, key, &crash_report);
} else {
std::string value;
metadata.GetString(key, &value);
(*crash_report.mutable_metadata())[key] = value;
}
}
grpc::Status status = stub_->SendCrashReport(&ctx, crash_report, &response);
if (!status.ok()) {
LOG(ERROR) << "Failed to send crash report to cicerone: "
<< status.error_code() << ", " << status.error_message();
}
}
bool VmSupportProper::GetMetricsConsent() {
grpc::ClientContext ctx;
vm_tools::EmptyMessage request;
vm_tools::cicerone::MetricsConsentResponse response;
grpc::Status status = stub_->CheckMetricsConsent(&ctx, request, &response);
return status.ok() && response.consent_granted();
}
bool VmSupportProper::ShouldDump(pid_t pid, std::string* out_reason) {
// Namespaces are accessed via the /proc/*/ns/* set of paths. The kernel
// guarantees that if two processes share a namespace, their corresponding
// namespace files will have the same inode number, as reported by stat.
//
// For now, we are only interested in processes in the root PID
// namespace. When invoked by the kernel in response to a crash,
// crash_reporter will be run in the root of all the namespace hierarchies, so
// we can easily check this by comparing the crashed process PID namespace
// with our own.
struct stat st;
auto namespace_path = base::StringPrintf("/proc/%d/ns/pid", pid);
if (stat(namespace_path.c_str(), &st) < 0) {
*out_reason = "failed to get process PID namespace";
return false;
}
ino_t inode = st.st_ino;
if (stat("/proc/self/ns/pid", &st) < 0) {
*out_reason = "failed to get own PID namespace";
return false;
}
ino_t self_inode = st.st_ino;
if (inode != self_inode) {
*out_reason = "ignoring - process not in root namespace";
return false;
}
return true;
}