blob: 28a6e487c4b9656e513fb2cc5039fe83f13ce572 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// clang-format off
#include "secagentd/plugins.h"
// clang-format on
#include <sys/utsname.h>
#include <unistd.h>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#if __has_include(<asm/bootparam.h>)
#include <asm/bootparam.h>
#define HAVE_BOOTPARAM
#endif
#include "attestation-client/attestation/dbus-proxies.h"
#include "attestation/proto_bindings/interface.pb.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "missive/proto/record_constants.pb.h"
#include "secagentd/device_user.h"
#include "secagentd/message_sender.h"
#include "secagentd/proto/security_xdr_events.pb.h"
#include "tpm_manager/proto_bindings/tpm_manager.pb.h"
#include "vboot/crossystem.h"
namespace {
constexpr int kWaitForServicesTimeoutMs = 2000;
std::string TpmPropertyToStr(uint32_t value) {
std::string str;
for (int i = 0, shift = 24; i < 4; i++, shift -= 8) {
auto c = static_cast<char>((value >> shift) & 0xFF);
if (c == 0) {
break;
}
str.push_back((c >= 32 && c < 127) ? c : ' ');
}
return str;
}
} // namespace
namespace secagentd {
namespace pb = cros_xdr::reporting;
AgentPlugin::AgentPlugin(
scoped_refptr<MessageSenderInterface> message_sender,
scoped_refptr<DeviceUserInterface> device_user,
std::unique_ptr<org::chromium::AttestationProxyInterface> attestation_proxy,
std::unique_ptr<org::chromium::TpmManagerProxyInterface> tpm_manager_proxy,
base::OnceCallback<void()> cb,
uint32_t heartbeat_timer)
: AgentPlugin(message_sender,
device_user,
std::move(attestation_proxy),
std::move(tpm_manager_proxy),
std::move(cb),
base::FilePath("/"),
heartbeat_timer) {}
AgentPlugin::AgentPlugin(
scoped_refptr<MessageSenderInterface> message_sender,
scoped_refptr<DeviceUserInterface> device_user,
std::unique_ptr<org::chromium::AttestationProxyInterface> attestation_proxy,
std::unique_ptr<org::chromium::TpmManagerProxyInterface> tpm_manager_proxy,
base::OnceCallback<void()> cb,
const base::FilePath& root_path,
uint32_t heartbeat_timer)
: weak_ptr_factory_(this),
message_sender_(message_sender),
device_user_(device_user),
attestation_proxy_(std::move(attestation_proxy)),
tpm_manager_proxy_((std::move(tpm_manager_proxy))),
daemon_cb_(std::move(cb)),
root_path_(root_path),
heartbeat_timer_(base::Seconds(std::max(heartbeat_timer, uint32_t(1)))) {
CHECK(message_sender != nullptr);
}
std::string AgentPlugin::GetName() const {
return "Agent";
}
absl::Status AgentPlugin::Activate() {
if (is_active_) {
// Calling activate on an activated plugin does nothing.
return absl::OkStatus();
}
StartInitializingAgentProto();
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AgentPlugin::SendStartEvent,
weak_ptr_factory_.GetWeakPtr()),
// Add delay for tpm_manager and attestation to initialize.
base::Seconds(1));
is_active_ = true;
return absl::OkStatus();
}
absl::Status AgentPlugin::Deactivate() {
return absl::UnimplementedError(
"Deactivate is not implemented for Agent Plugin");
}
void AgentPlugin::StartInitializingAgentProto() {
attestation_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
base::BindOnce(&AgentPlugin::AttestationCb,
weak_ptr_factory_.GetWeakPtr()));
tpm_manager_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
base::BindOnce(&AgentPlugin::TpmCb, weak_ptr_factory_.GetWeakPtr()));
char buffer[VB_MAX_STRING_PROPERTY];
auto get_fwid_rv =
VbGetSystemPropertyString("fwid", buffer, std::size(buffer));
// Get linux version.
struct utsname buf;
int get_uname_rv = uname(&buf);
auto uefi_metric =
GetUefiSecureBootInformation(root_path_.Append(kBootDataFilepath));
MetricsSender::GetInstance().SendEnumMetricToUMA(metrics::kUefiBootmode,
uefi_metric);
base::AutoLock lock(tcb_attributes_lock_);
if (get_fwid_rv == 0) {
tcb_attributes_.set_system_firmware_version(buffer);
} else {
LOG(ERROR) << "Failed to retrieve fwid";
}
if (!get_uname_rv) {
tcb_attributes_.set_linux_kernel_version(buf.release);
} else {
LOG(ERROR) << "Failed to retrieve uname";
}
}
metrics::UefiBootmode AgentPlugin::GetUefiSecureBootInformation(
const base::FilePath& boot_params_filepath) {
#ifdef HAVE_BOOTPARAM
std::string content;
if (!base::ReadFileToStringWithMaxSize(boot_params_filepath, &content,
sizeof(boot_params))) {
LOG(ERROR) << "Failed to read file: " << boot_params_filepath.value();
return metrics::UefiBootmode::kFailedToReadBootParams;
}
if (content.size() != sizeof(boot_params)) {
LOG(ERROR) << boot_params_filepath.value()
<< " boot params invalid file size";
return metrics::UefiBootmode::kBootParamInvalidSize;
}
const boot_params* boot =
reinterpret_cast<const boot_params*>(content.c_str());
// defined in kernel's include/linux/efi.h
static constexpr int kEfiSecurebootModeEnabled = 3;
if (boot->secure_boot == kEfiSecurebootModeEnabled) {
base::AutoLock lock(tcb_attributes_lock_);
tcb_attributes_.set_firmware_secure_boot(
pb::TcbAttributes_FirmwareSecureBoot_CROS_FLEX_UEFI_SECURE_BOOT);
}
return metrics::UefiBootmode::kSuccess;
#else
LOG(WARNING)
<< "Header bootparam.h is not present. Assuming not uefi secure boot.";
return metrics::UefiBootmode::kFileNotFound;
#endif
}
void AgentPlugin::AttestationCb(bool available) {
auto metric = GetCrosSecureBootInformation(available);
MetricsSender::GetInstance().SendEnumMetricToUMA(metrics::kCrosBootmode,
metric);
}
metrics::CrosBootmode AgentPlugin::GetCrosSecureBootInformation(
bool available) {
if (!available) {
LOG(ERROR) << "Failed waiting for attestation to become available";
return metrics::CrosBootmode::kUnavailable;
}
// Get boot information.
attestation::GetStatusRequest request;
attestation::GetStatusReply out_reply;
brillo::ErrorPtr error;
if (!attestation_proxy_->GetStatus(request, &out_reply, &error,
kWaitForServicesTimeoutMs) ||
error.get()) {
LOG(ERROR) << "Failed to get boot information " << error->GetMessage();
return metrics::CrosBootmode::kFailedRetrieval;
}
base::AutoLock lock(tcb_attributes_lock_);
if (out_reply.verified_boot()) {
tcb_attributes_.set_firmware_secure_boot(
pb::TcbAttributes_FirmwareSecureBoot_CROS_VERIFIED_BOOT);
} else {
if (!tcb_attributes_.has_firmware_secure_boot()) {
tcb_attributes_.set_firmware_secure_boot(
pb::TcbAttributes_FirmwareSecureBoot_NONE);
}
}
return metrics::CrosBootmode::kSuccess;
}
void AgentPlugin::TpmCb(bool available) {
auto metric = GetTpmInformation(available);
MetricsSender::GetInstance().SendEnumMetricToUMA(metrics::kTpm, metric);
}
metrics::Tpm AgentPlugin::GetTpmInformation(bool available) {
if (!available) {
LOG(ERROR) << "Failed waiting for tpm_manager to become available";
return metrics::Tpm::kUnavailable;
}
// Check if TPM is enabled.
tpm_manager::GetTpmNonsensitiveStatusRequest status_request;
tpm_manager::GetTpmNonsensitiveStatusReply status_reply;
brillo::ErrorPtr error;
if (!tpm_manager_proxy_->GetTpmNonsensitiveStatus(
status_request, &status_reply, &error, kWaitForServicesTimeoutMs) ||
error.get()) {
LOG(ERROR) << "Failed to get TPM status " << error->GetMessage();
return metrics::Tpm::kFailedRetrieval;
}
if (!status_reply.is_enabled()) {
LOG(INFO) << "TPM is disabled on device";
return metrics::Tpm::kSuccess;
}
// Get TPM information.
tpm_manager::GetVersionInfoRequest version_request;
tpm_manager::GetVersionInfoReply version_reply;
if (!tpm_manager_proxy_->GetVersionInfo(version_request, &version_reply,
&error, kWaitForServicesTimeoutMs) ||
error.get()) {
LOG(ERROR) << "Failed to get TPM information " << error->GetMessage();
return metrics::Tpm::kFailedRetrieval;
}
base::AutoLock lock(tcb_attributes_lock_);
auto security_chip = tcb_attributes_.mutable_security_chip();
if (version_reply.has_gsc_device()) {
switch (version_reply.gsc_device()) {
case tpm_manager::GSC_DEVICE_NOT_GSC: {
security_chip->set_kind(pb::TcbAttributes_SecurityChip::Kind::
TcbAttributes_SecurityChip_Kind_TPM);
break;
}
case tpm_manager::GSC_DEVICE_H1:
case tpm_manager::GSC_DEVICE_DT:
case tpm_manager::GSC_DEVICE_NT:
security_chip->set_kind(
pb::TcbAttributes_SecurityChip::Kind::
TcbAttributes_SecurityChip_Kind_GOOGLE_SECURITY_CHIP);
}
auto family = TpmPropertyToStr(version_reply.family());
auto level =
std::to_string((version_reply.spec_level() >> 32) & 0xffffffff);
security_chip->set_chip_version(base::StringPrintf(
"%s.%s.%s", family.c_str(), level.c_str(),
std::to_string(version_reply.spec_level() & 0xffffffff).c_str()));
security_chip->set_spec_family(family);
security_chip->set_spec_level(level);
security_chip->set_manufacturer(
TpmPropertyToStr(version_reply.manufacturer()));
security_chip->set_vendor_id(version_reply.vendor_specific());
security_chip->set_tpm_model(std::to_string(version_reply.tpm_model()));
security_chip->set_firmware_version(
std::to_string(version_reply.firmware_version()));
} else {
security_chip->set_kind(pb::TcbAttributes_SecurityChip::Kind::
TcbAttributes_SecurityChip_Kind_NONE);
}
return metrics::Tpm::kSuccess;
}
void AgentPlugin::SendAgentEvent(bool is_agent_start) {
auto agent_event = std::make_unique<pb::AgentEventAtomicVariant>();
base::AutoLock lock(tcb_attributes_lock_);
if (is_agent_start) {
agent_event->mutable_agent_start()->mutable_tcb()->CopyFrom(
tcb_attributes_);
} else {
agent_event->mutable_agent_heartbeat()->mutable_tcb()->CopyFrom(
tcb_attributes_);
}
agent_event->mutable_common()->set_create_timestamp_us(
base::Time::Now().InMillisecondsSinceUnixEpoch() *
base::Time::kMicrosecondsPerMillisecond);
device_user_->GetDeviceUserAsync(
base::BindOnce(&AgentPlugin::OnDeviceUserRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(agent_event)));
}
void AgentPlugin::OnDeviceUserRetrieved(
std::unique_ptr<pb::AgentEventAtomicVariant> agent_event,
const std::string& device_user,
const std::string& device_userhash) {
agent_event->mutable_common()->set_device_user(device_user);
auto xdr_proto = std::make_unique<pb::XdrAgentEvent>();
auto batched_event = xdr_proto->add_batched_events();
base::OnceCallback<void(reporting::Status)> cb;
if (agent_event->has_agent_start()) {
batched_event->set_allocated_agent_start(
agent_event->release_agent_start());
cb = base::BindOnce(&AgentPlugin::StartEventStatusCallback,
weak_ptr_factory_.GetWeakPtr());
} else {
batched_event->set_allocated_agent_heartbeat(
agent_event->release_agent_heartbeat());
cb = base::DoNothingAs<void(reporting::Status)>();
}
batched_event->set_allocated_common(agent_event->release_common());
message_sender_->SendMessage(reporting::CROS_SECURITY_AGENT,
xdr_proto->mutable_common(),
std::move(xdr_proto), std::move(cb));
}
void AgentPlugin::StartEventStatusCallback(reporting::Status status) {
if (status.ok()) {
// Start heartbeat timer.
agent_heartbeat_timer_.Start(
FROM_HERE, heartbeat_timer_,
base::BindRepeating(&AgentPlugin::SendHeartbeatEvent,
weak_ptr_factory_.GetWeakPtr()));
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(daemon_cb_));
} else {
LOG(ERROR) << "Agent Start failed to send. Will retry in 3s.";
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&AgentPlugin::SendStartEvent,
weak_ptr_factory_.GetWeakPtr()),
base::Seconds(3));
}
}
} // namespace secagentd