| // 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 |