| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "secagentd/secagent.h" | |
| #include <unistd.h> | |
| #include <cstdlib> | |
| #include <memory> | |
| #include <string> | |
| #include <sysexits.h> | |
| #include <utility> | |
| #include "absl/status/status.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/functional/callback_helpers.h" | |
| #include "base/logging.h" | |
| #include "base/memory/scoped_refptr.h" | |
| #include "brillo/files/file_util.h" | |
| #include "secagentd/bpf_skeleton_wrappers.h" | |
| #include "secagentd/device_user.h" | |
| #include "secagentd/message_sender.h" | |
| #include "secagentd/metrics_sender.h" | |
| #include "secagentd/plugins.h" | |
| #include "secagentd/policies_features_broker.h" | |
| #include "secagentd/process_cache.h" | |
| namespace secagentd { | |
| SecAgent::SecAgent( | |
| base::OnceCallback<void(int)> quit_daemon_cb, | |
| scoped_refptr<MessageSenderInterface> message_sender, | |
| scoped_refptr<ProcessCacheInterface> process_cache, | |
| scoped_refptr<DeviceUserInterface> device_user, | |
| std::unique_ptr<PluginFactoryInterface> plugin_factory, | |
| std::unique_ptr<org::chromium::AttestationProxyInterface> attestation_proxy, | |
| std::unique_ptr<org::chromium::TpmManagerProxyInterface> tpm_proxy, | |
| feature::PlatformFeaturesInterface* platform_features, | |
| bool bypass_policy_for_testing, | |
| bool bypass_enq_ok_wait_for_testing, | |
| bool stop_reporting_for_unaffiliated_users, | |
| uint32_t heartbeat_period_s, | |
| uint32_t plugin_batch_interval_s, | |
| uint32_t feature_poll_interval_s_for_testing, | |
| const base::FilePath& root_path) | |
| : message_sender_(message_sender), | |
| process_cache_(process_cache), | |
| device_user_(device_user), | |
| plugin_factory_(std::move(plugin_factory)), | |
| attestation_proxy_(std::move(attestation_proxy)), | |
| tpm_proxy_(std::move(tpm_proxy)), | |
| platform_features_(platform_features), | |
| bypass_policy_for_testing_(bypass_policy_for_testing), | |
| bypass_enq_ok_wait_for_testing_(bypass_enq_ok_wait_for_testing), | |
| stop_reporting_for_unaffiliated_users_( | |
| stop_reporting_for_unaffiliated_users), | |
| heartbeat_period_s_(heartbeat_period_s), | |
| plugin_batch_interval_s_(plugin_batch_interval_s), | |
| feature_poll_interval_s_(feature_poll_interval_s_for_testing), | |
| quit_daemon_cb_(std::move(quit_daemon_cb)), | |
| root_path_(root_path), | |
| weak_ptr_factory_(this) { | |
| policies_features_broker_ = base::MakeRefCounted<PoliciesFeaturesBroker>( | |
| std::make_unique<policy::PolicyProvider>(), platform_features_, | |
| base::BindRepeating(&SecAgent::CheckPolicyAndFeature, | |
| weak_ptr_factory_.GetWeakPtr())); | |
| } | |
| void SecAgent::Activate() { | |
| LOG(INFO) << absl::StrFormat( | |
| "BypassPolicyForTesting:%d," | |
| "BypassEnqOkWaitForTesting:%d,StopReportingForUnaffiliatedUsers:%d," | |
| "HeartbeatPeriodSeconds:%d," | |
| "PluginBatchIntervalSeconds:%d,FeaturePollIntervalSeconds:%d", | |
| bypass_policy_for_testing_, bypass_enq_ok_wait_for_testing_, | |
| stop_reporting_for_unaffiliated_users_, heartbeat_period_s_, | |
| plugin_batch_interval_s_, feature_poll_interval_s_); | |
| absl::Status result = message_sender_->Initialize(); | |
| if (result != absl::OkStatus()) { | |
| LOG(ERROR) << result.message(); | |
| std::move(quit_daemon_cb_).Run(EX_SOFTWARE); | |
| return; | |
| } | |
| if (stop_reporting_for_unaffiliated_users_) { | |
| device_user_->SetFlushCallback(base::BindRepeating( | |
| &SecAgent::FlushAllPluginEvents, weak_ptr_factory_.GetWeakPtr())); | |
| // The session change listener will indirectly call CheckPolicyAndFeature | |
| // to start polling. | |
| device_user_->RegisterSessionChangeListener(base::BindRepeating( | |
| &SecAgent::OnSessionStateChange, weak_ptr_factory_.GetWeakPtr())); | |
| device_user_->RegisterSessionChangeHandler(); | |
| } else { | |
| policies_features_broker_->StartAndBlockForSync( | |
| base::Seconds(feature_poll_interval_s_)); | |
| } | |
| // Some BPF maps are pinned for easy sharing between BPF programs. | |
| std::string_view relative_bpf_pin_path(kDefaultBpfPinDir); | |
| if (relative_bpf_pin_path.starts_with("/")) { | |
| relative_bpf_pin_path.remove_prefix(1); | |
| } | |
| base::FilePath absolute_bpf_pin_dir = | |
| root_path_.Append(relative_bpf_pin_path); | |
| const std::vector<base::FilePath> pinned_map_names{ | |
| base::FilePath("shared_process_info")}; | |
| for (const auto& map_path : pinned_map_names) { | |
| base::FilePath pinned_map = absolute_bpf_pin_dir.Append(map_path); | |
| if (base::PathExists(pinned_map)) { | |
| LOG(INFO) << "Cleaning up " << absolute_bpf_pin_dir.Append(map_path); | |
| brillo::DeleteFile(absolute_bpf_pin_dir.Append(map_path)); | |
| } | |
| } | |
| process_cache_->InitializeFilter(); | |
| } | |
| void SecAgent::CheckPolicyAndFeature() { | |
| static bool first_visit = true; | |
| bool xdr_reporting_policy = | |
| policies_features_broker_->GetDeviceReportXDREventsPolicy() || | |
| bypass_policy_for_testing_; | |
| bool xdr_reporting_feature = policies_features_broker_->GetFeature( | |
| PoliciesFeaturesBroker::Feature::kCrOSLateBootSecagentdXDRReporting); | |
| if (first_visit) { | |
| LOG(INFO) << "DeviceReportXDREventsPolicy: " << xdr_reporting_policy | |
| << (bypass_policy_for_testing_ ? " (set by flag)" : ""); | |
| LOG(INFO) << "CrOSLateBootSecagentdXDRReporting: " << xdr_reporting_feature; | |
| } | |
| stop_reporting_for_unaffiliated_users_ = | |
| policies_features_broker_->GetFeature( | |
| PoliciesFeaturesBroker::Feature:: | |
| kCrosLateBootSecagentdXDRStopReportingForUnaffiliated); | |
| if (stop_reporting_for_unaffiliated_users_ && | |
| device_user_->GetIsUnaffiliated() && !reporting_events_) { | |
| if (first_visit) { | |
| LOG(INFO) << "Not starting reporting because unaffiliated user signed in"; | |
| } | |
| return; | |
| } | |
| // If either policy is false do not report. | |
| if (reporting_events_ && !(xdr_reporting_feature && xdr_reporting_policy)) { | |
| LOG(INFO) << "Stopping event reporting and quitting. Policy: " | |
| << std::to_string(xdr_reporting_policy) | |
| << " Feature: " << std::to_string(xdr_reporting_feature); | |
| reporting_events_ = false; | |
| // Will exit and restart secagentd. | |
| std::move(quit_daemon_cb_).Run(EX_OK); | |
| return; | |
| } else if (!reporting_events_ && | |
| (xdr_reporting_feature && xdr_reporting_policy)) { | |
| LOG(INFO) << "Starting event reporting"; | |
| // This is emitted at most once per daemon lifetime. | |
| MetricsSender::GetInstance().SendEnumMetricToUMA(metrics::kPolicy, | |
| metrics::Policy::kEnabled); | |
| reporting_events_ = true; | |
| StartXDRReporting(); | |
| } else if (reporting_events_ && xdr_reporting_feature && | |
| xdr_reporting_policy) { | |
| // BPF plugins were activated in the past. Repoll features and | |
| // activate/deactivate relevant plugins. | |
| ActivateOrDeactivatePlugins(); | |
| } else if (first_visit) { | |
| LOG(INFO) << "Not reporting yet."; | |
| } | |
| // Else do nothing until the next poll. | |
| first_visit = false; | |
| } | |
| void SecAgent::StartXDRReporting() { | |
| if (!stop_reporting_for_unaffiliated_users_) { | |
| device_user_->RegisterSessionChangeHandler(); | |
| } | |
| MetricsSender::GetInstance().InitBatchedMetrics(); | |
| using CbType = base::OnceCallback<void()>; | |
| CbType cb_for_agent = base::BindOnce(&SecAgent::CreateAndActivatePlugins, | |
| weak_ptr_factory_.GetWeakPtr()); | |
| CbType cb_for_now = base::DoNothing(); | |
| if (bypass_enq_ok_wait_for_testing_) { | |
| std::swap(cb_for_agent, cb_for_now); | |
| } | |
| agent_plugin_ = plugin_factory_->CreateAgentPlugin( | |
| message_sender_, device_user_, std::move(attestation_proxy_), | |
| std::move(tpm_proxy_), std::move(cb_for_agent), heartbeat_period_s_); | |
| if (!agent_plugin_) { | |
| std::move(quit_daemon_cb_).Run(EX_SOFTWARE); | |
| return; | |
| } | |
| absl::Status result = agent_plugin_->Activate(); | |
| if (!result.ok()) { | |
| LOG(ERROR) << result.message(); | |
| std::move(quit_daemon_cb_).Run(EX_SOFTWARE); | |
| return; | |
| } | |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( | |
| FROM_HERE, std::move(cb_for_now)); | |
| } | |
| void SecAgent::ActivateOrDeactivatePlugins() { | |
| absl::Status result; | |
| std::string action; | |
| auto activate = [&action](PluginConfig& pc) { | |
| if (!pc.plugin->IsActive()) { | |
| action = "activated"; | |
| return pc.plugin->Activate(); | |
| } | |
| return absl::OkStatus(); | |
| }; | |
| auto deactivate = [&action](PluginConfig& pc) { | |
| if (pc.plugin->IsActive()) { | |
| action = "deactivated"; | |
| return pc.plugin->Deactivate(); | |
| } | |
| return absl::OkStatus(); | |
| }; | |
| for (auto& plugin_config : plugins_) { | |
| auto& feature = plugin_config.gated_by_feature; | |
| auto& plugin = plugin_config.plugin; | |
| action = ""; | |
| if (feature.has_value()) { | |
| // The plugin is gated by a feature. | |
| if (policies_features_broker_->GetFeature(feature.value())) { | |
| result = activate(plugin_config); | |
| } else { | |
| result = deactivate(plugin_config); | |
| } | |
| } else { | |
| result = activate(plugin_config); | |
| } | |
| if (!result.ok()) { | |
| LOG(ERROR) << result.message(); | |
| } else if (!action.empty()) { | |
| LOG(INFO) << plugin->GetName() << " plugin " << action; | |
| } | |
| } | |
| } | |
| void SecAgent::CreateAndActivatePlugins() { | |
| using Feature = PoliciesFeaturesBrokerInterface::Feature; | |
| using Plugin = Types::Plugin; | |
| static const std::vector<std::pair<Plugin, std::optional<Feature>>> plugins = | |
| {std::make_pair( | |
| Plugin::kAuthenticate, | |
| std::optional(Feature::kCrOSLateBootSecagentdXDRAuthenticateEvents)), | |
| std::make_pair( | |
| Plugin::kNetwork, | |
| std::optional(Feature::kCrOSLateBootSecagentdXDRNetworkEvents)), | |
| std::make_pair(Plugin::kProcess, std::nullopt)}; | |
| std::unique_ptr<PluginInterface> plugin; | |
| for (const auto& p : plugins) { | |
| plugin = plugin_factory_->Create(p.first, message_sender_, process_cache_, | |
| policies_features_broker_, device_user_, | |
| plugin_batch_interval_s_); | |
| if (!plugin) { | |
| std::move(quit_daemon_cb_).Run(EX_SOFTWARE); | |
| } | |
| plugins_.push_back({p.second, std::move(plugin)}); | |
| } | |
| ActivateOrDeactivatePlugins(); | |
| } | |
| void SecAgent::OnSessionStateChange(const std::string& state) { | |
| static bool started_polling = false; | |
| // Make sure device user is updated before starting reporting. | |
| if (!started_polling) { | |
| policies_features_broker_->StartAndBlockForSync( | |
| base::Seconds(feature_poll_interval_s_)); | |
| started_polling = true; | |
| } | |
| if (stop_reporting_for_unaffiliated_users_) { | |
| device_user_->GetDeviceUserAsync(base::BindOnce( | |
| &SecAgent::OnDeviceUserRetrieved, weak_ptr_factory_.GetWeakPtr())); | |
| } | |
| } | |
| void SecAgent::OnDeviceUserRetrieved(const std::string& user) { | |
| if (reporting_events_) { | |
| if (device_user_->GetIsUnaffiliated()) { | |
| LOG(INFO) << "Stopping reporting: Unaffiliated user signed in"; | |
| std::move(quit_daemon_cb_).Run(EX_OK); | |
| } | |
| } else { | |
| CheckPolicyAndFeature(); | |
| } | |
| } | |
| void SecAgent::FlushAllPluginEvents() { | |
| for (auto& plugin : plugins_) { | |
| plugin.plugin->Flush(); | |
| } | |
| } | |
| } // namespace secagentd |