| // 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. |
| |
| #include <unistd.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <sys/utsname.h> |
| |
| #if __has_include(<asm/bootparam.h>) |
| #include <asm/bootparam.h> |
| #define HAVE_BOOTPARAM |
| #endif |
| #include "absl/status/status.h" |
| #include "attestation/proto_bindings/interface.pb.h" |
| #include "attestation-client/attestation/dbus-proxies.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "missive/proto/record_constants.pb.h" |
| #include "secagentd/message_sender.h" |
| #include "secagentd/plugins.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; |
| constexpr char kBootDataFilepath[] = "/sys/kernel/boot_params/data"; |
| |
| // Converts a brillo::Error* to string for printing. |
| std::string BrilloErrorToString(brillo::Error* err) { |
| std::string result; |
| if (err) { |
| result = base::StrCat({"(", err->GetDomain(), ", ", err->GetCode(), ", ", |
| err->GetMessage(), ")"}); |
| } else { |
| result = "(null)"; |
| } |
| return result; |
| } |
| |
| 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, |
| 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) |
| : weak_ptr_factory_(this), |
| message_sender_(message_sender), |
| heartbeat_timer_(base::Seconds(std::max(heartbeat_timer, uint32_t(1)))) { |
| CHECK(message_sender != nullptr); |
| attestation_proxy_ = std::move(attestation_proxy); |
| tpm_manager_proxy_ = std::move(tpm_manager_proxy); |
| daemon_cb_ = std::move(cb); |
| } |
| |
| std::string AgentPlugin::GetName() const { |
| return "AgentPlugin"; |
| } |
| |
| absl::Status AgentPlugin::Activate() { |
| StartInitializingAgentProto(); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&AgentPlugin::SendAgentStartEvent, |
| weak_ptr_factory_.GetWeakPtr()), |
| // Add delay for tpm_manager and attestation to initialize. |
| base::Seconds(1)); |
| |
| return absl::OkStatus(); |
| } |
| |
| void AgentPlugin::StartInitializingAgentProto() { |
| attestation_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable( |
| base::BindOnce(&AgentPlugin::GetCrosSecureBootInformation, |
| weak_ptr_factory_.GetWeakPtr())); |
| tpm_manager_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable( |
| base::BindOnce(&AgentPlugin::GetTpmInformation, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| char buffer[VB_MAX_STRING_PROPERTY]; |
| auto get_fwid_rv = |
| VbGetSystemPropertyString("fwid", buffer, std::size(buffer)); |
| if (!get_fwid_rv) { |
| LOG(ERROR) << "Failed to retrieve fwid"; |
| } |
| |
| // Get linux version. |
| struct utsname buf; |
| int get_uname_rv = uname(&buf); |
| |
| GetUefiSecureBootInformation(base::FilePath(kBootDataFilepath)); |
| |
| base::AutoLock lock(tcb_attributes_lock_); |
| if (get_fwid_rv) { |
| tcb_attributes_.set_system_firmware_version(get_fwid_rv); |
| } 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"; |
| } |
| } |
| |
| void 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; |
| } |
| if (content.size() != sizeof(boot_params)) { |
| LOG(ERROR) << boot_params_filepath.value() |
| << " boot params invalid file size"; |
| return; |
| } |
| 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); |
| } |
| #else |
| LOG(WARNING) |
| << "Header bootparam.h is not present. Assuming not uefi secure boot."; |
| #endif |
| } |
| |
| void AgentPlugin::GetCrosSecureBootInformation(bool available) { |
| if (!available) { |
| LOG(ERROR) << "Failed waiting for attestation to become available"; |
| return; |
| } |
| |
| // 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 " |
| << BrilloErrorToString(error.get()); |
| return; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| void AgentPlugin::GetTpmInformation(bool available) { |
| if (!available) { |
| LOG(ERROR) << "Failed waiting for tpm_manager to become available"; |
| return; |
| } |
| |
| // Get TPM information. |
| tpm_manager::GetVersionInfoRequest request; |
| tpm_manager::GetVersionInfoReply out_reply; |
| brillo::ErrorPtr error; |
| |
| if (!tpm_manager_proxy_->GetVersionInfo(request, &out_reply, &error, |
| kWaitForServicesTimeoutMs) || |
| error.get()) { |
| LOG(ERROR) << "Failed to get TPM information " |
| << BrilloErrorToString(error.get()); |
| return; |
| } |
| base::AutoLock lock(tcb_attributes_lock_); |
| auto security_chip = tcb_attributes_.mutable_security_chip(); |
| if (out_reply.has_gsc_version()) { |
| switch (out_reply.gsc_version()) { |
| case tpm_manager::GSC_VERSION_NOT_GSC: { |
| security_chip->set_kind(pb::TcbAttributes_SecurityChip::Kind:: |
| TcbAttributes_SecurityChip_Kind_TPM); |
| break; |
| } |
| case tpm_manager::GSC_VERSION_CR50: |
| case tpm_manager::GSC_VERSION_TI50: |
| security_chip->set_kind( |
| pb::TcbAttributes_SecurityChip::Kind:: |
| TcbAttributes_SecurityChip_Kind_GOOGLE_SECURITY_CHIP); |
| } |
| auto family = TpmPropertyToStr(out_reply.family()); |
| auto level = std::to_string((out_reply.spec_level() >> 32) & 0xffffffff); |
| security_chip->set_chip_version(base::StringPrintf( |
| "%s.%s.%s", family.c_str(), level.c_str(), |
| std::to_string(out_reply.spec_level() & 0xffffffff).c_str())); |
| security_chip->set_spec_family(family); |
| security_chip->set_spec_level(level); |
| security_chip->set_manufacturer(TpmPropertyToStr(out_reply.manufacturer())); |
| security_chip->set_vendor_id(out_reply.vendor_specific()); |
| security_chip->set_tpm_model(std::to_string(out_reply.tpm_model())); |
| security_chip->set_firmware_version( |
| std::to_string(out_reply.firmware_version())); |
| } else { |
| security_chip->set_kind(pb::TcbAttributes_SecurityChip::Kind:: |
| TcbAttributes_SecurityChip_Kind_NONE); |
| } |
| } |
| |
| void AgentPlugin::SendAgentStartEvent() { |
| auto agent_event = std::make_unique<pb::XdrAgentEvent>(); |
| base::AutoLock lock(tcb_attributes_lock_); |
| agent_event->mutable_agent_start()->mutable_tcb()->CopyFrom(tcb_attributes_); |
| message_sender_->SendMessage( |
| reporting::CROS_SECURITY_AGENT, agent_event->mutable_common(), |
| std::move(agent_event), |
| base::BindOnce(&AgentPlugin::StartEventStatusCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void AgentPlugin::SendAgentHeartbeatEvent() { |
| // Create agent heartbeat event. |
| auto agent_event = std::make_unique<pb::XdrAgentEvent>(); |
| base::AutoLock lock(tcb_attributes_lock_); |
| agent_event->mutable_agent_heartbeat()->mutable_tcb()->CopyFrom( |
| tcb_attributes_); |
| message_sender_->SendMessage(reporting::CROS_SECURITY_AGENT, |
| agent_event->mutable_common(), |
| std::move(agent_event), std::nullopt); |
| } |
| |
| void AgentPlugin::StartEventStatusCallback(reporting::Status status) { |
| if (status.ok()) { |
| // Start heartbeat timer. |
| agent_heartbeat_timer_.Start( |
| FROM_HERE, heartbeat_timer_, |
| base::BindRepeating(&AgentPlugin::SendAgentHeartbeatEvent, |
| 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::SendAgentStartEvent, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Seconds(3)); |
| } |
| } |
| |
| } // namespace secagentd |