blob: 4dd10cd8c52f1a66fc3c84f4bec8fc7c69b51628 [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.
#include <cstdint>
#include <memory>
#include <utility>
#include "absl/status/status.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "missive/proto/record_constants.pb.h"
#include "secagentd/bpf/bpf_types.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/proto/security_xdr_events.pb.h"
namespace secagentd {
namespace pb = cros_xdr::reporting;
namespace {
// Fills a Namespaces proto with contents from bpf namespace_info.
void FillNamespaces(const bpf::cros_namespace_info& ns,
pb::Namespaces* ns_proto) {
ns_proto->set_cgroup_ns(ns.cgroup_ns);
ns_proto->set_pid_ns(ns.pid_ns);
ns_proto->set_user_ns(ns.user_ns);
ns_proto->set_uts_ns(ns.uts_ns);
ns_proto->set_mnt_ns(ns.mnt_ns);
ns_proto->set_net_ns(ns.net_ns);
ns_proto->set_ipc_ns(ns.ipc_ns);
}
std::string GetBatchedEventKey(
const pb::ProcessEventAtomicVariant& process_event) {
switch (process_event.variant_type_case()) {
case cros_xdr::reporting::ProcessEventAtomicVariant::kProcessExec:
return process_event.process_exec().spawn_process().process_uuid();
case cros_xdr::reporting::ProcessEventAtomicVariant::kProcessTerminate:
return process_event.process_terminate().process().process_uuid();
case cros_xdr::reporting::ProcessEventAtomicVariant::VARIANT_TYPE_NOT_SET:
return "";
}
}
bool SetTerminateTimestamp(pb::ProcessEventAtomicVariant* exec) {
if (exec->has_process_exec()) {
exec->mutable_process_exec()->set_terminate_timestamp_us(
base::Time::Now().InMillisecondsSinceUnixEpoch() *
base::Time::kMicrosecondsPerMillisecond);
return true;
}
return false;
}
} // namespace
ProcessPlugin::ProcessPlugin(
scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory,
scoped_refptr<MessageSenderInterface> message_sender,
scoped_refptr<ProcessCacheInterface> process_cache,
scoped_refptr<PoliciesFeaturesBrokerInterface> policies_features_broker,
scoped_refptr<DeviceUserInterface> device_user,
uint32_t batch_interval_s)
: weak_ptr_factory_(this),
process_cache_(process_cache),
policies_features_broker_(policies_features_broker),
device_user_(device_user),
batch_sender_(
std::make_unique<BatchSender<std::string,
pb::XdrProcessEvent,
pb::ProcessEventAtomicVariant>>(
base::BindRepeating(&GetBatchedEventKey),
message_sender,
reporting::Destination::CROS_SECURITY_PROCESS,
batch_interval_s)),
bpf_skeleton_helper_(
std::make_unique<BpfSkeletonHelper<Types::BpfSkeleton::kProcess>>(
bpf_skeleton_factory, batch_interval_s)) {
CHECK(message_sender != nullptr);
CHECK(process_cache != nullptr);
CHECK(bpf_skeleton_factory);
}
std::string ProcessPlugin::GetName() const {
return "Process";
}
void ProcessPlugin::HandleRingBufferEvent(const bpf::cros_event& bpf_event) {
auto atomic_event = std::make_unique<pb::ProcessEventAtomicVariant>();
if (bpf_event.type != bpf::kProcessEvent) {
LOG(ERROR) << "ProcessBPF: unknown BPF event type.";
return;
}
const bpf::cros_process_event& pe = bpf_event.data.process_event;
if (pe.type == bpf::kProcessStartEvent) {
const bpf::cros_process_start& process_start = pe.data.process_start;
// Record the newly spawned process into our cache.
process_cache_->PutFromBpfExec(process_start);
auto exec_event = MakeExecEvent(process_start);
const pb::Process* parent_process =
exec_event->has_process() ? exec_event->mutable_process() : nullptr;
const pb::Process* process = exec_event->has_spawn_process()
? exec_event->mutable_spawn_process()
: nullptr;
if (process_cache_->IsEventFiltered(parent_process, process)) {
return;
}
atomic_event->set_allocated_process_exec(exec_event.release());
} else if (pe.type == bpf::kProcessExitEvent) {
const bpf::cros_process_exit& process_exit = pe.data.process_exit;
auto terminate_event = MakeTerminateEvent(process_exit);
if (process_exit.is_leaf) {
process_cache_->EraseProcess(process_exit.task_info.pid,
process_exit.task_info.start_time);
}
const pb::Process* parent_process =
terminate_event->has_parent_process()
? terminate_event->mutable_parent_process()
: nullptr;
const pb::Process* process = terminate_event->has_process()
? terminate_event->mutable_process()
: nullptr;
if (process_cache_->IsEventFiltered(parent_process, process)) {
return;
}
atomic_event->set_allocated_process_terminate(terminate_event.release());
} else {
LOG(ERROR) << "ProcessBPF: unknown BPF process event type.";
return;
}
device_user_->GetDeviceUserAsync(
base::BindOnce(&ProcessPlugin::OnDeviceUserRetrieved,
weak_ptr_factory_.GetWeakPtr(), std::move(atomic_event)));
}
void ProcessPlugin::OnDeviceUserRetrieved(
std::unique_ptr<pb::ProcessEventAtomicVariant> atomic_event,
const std::string& device_user,
const std::string& device_userhash) {
atomic_event->mutable_common()->set_device_user(device_user);
EnqueueBatchedEvent(std::move(atomic_event));
}
absl::Status ProcessPlugin::Activate() {
struct BpfCallbacks callbacks;
callbacks.ring_buffer_event_callback = base::BindRepeating(
&ProcessPlugin::HandleRingBufferEvent, weak_ptr_factory_.GetWeakPtr());
absl::Status status = bpf_skeleton_helper_->LoadAndAttach(callbacks);
if (status == absl::OkStatus()) {
batch_sender_->Start();
}
return status;
}
absl::Status ProcessPlugin::Deactivate() {
return absl::UnimplementedError(
"Deactivate not implemented for ProcessPlugin.");
}
void ProcessPlugin::EnqueueBatchedEvent(
std::unique_ptr<pb::ProcessEventAtomicVariant> atomic_event) {
if (atomic_event->has_process_terminate() &&
policies_features_broker_->GetFeature(
PoliciesFeaturesBroker::Feature::
kCrOSLateBootSecagentdCoalesceTerminates)) {
if (batch_sender_->Visit(pb::ProcessEventAtomicVariant::kProcessExec,
GetBatchedEventKey(*atomic_event),
base::BindOnce(&SetTerminateTimestamp))) {
// Successfully visited and presumably also coalesced.
return;
}
}
batch_sender_->Enqueue(std::move(atomic_event));
}
bool ProcessPlugin::IsActive() const {
return bpf_skeleton_helper_->IsAttached();
}
std::unique_ptr<pb::ProcessExecEvent> ProcessPlugin::MakeExecEvent(
const bpf::cros_process_start& process_start) {
auto process_exec_event = std::make_unique<pb::ProcessExecEvent>();
FillNamespaces(process_start.spawn_namespace,
process_exec_event->mutable_spawn_namespaces());
// Fetch information on process that was just spawned, the parent process
// that spawned that process, and its parent process. I.e a total of
// three.
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
if (hierarchy.empty()) {
LOG(ERROR) << "PID:" << process_start.task_info.pid
<< " not found in the process cache.";
}
if (hierarchy.size() > 0) {
process_exec_event->set_allocated_spawn_process(hierarchy[0].release());
}
if (hierarchy.size() > 1) {
process_exec_event->set_allocated_process(hierarchy[1].release());
}
if (hierarchy.size() > 2) {
process_exec_event->set_allocated_parent_process(hierarchy[2].release());
}
// Exec event metrics.
metrics::ProcessEvent exec_event_metric = metrics::ProcessEvent::kFullEvent;
if (hierarchy.empty()) {
exec_event_metric = metrics::ProcessEvent::kSpawnPidNotInCache;
} else if (hierarchy.size() == 1) {
exec_event_metric = metrics::ProcessEvent::kProcessPidNotInCache;
} else if (hierarchy.size() == 2 && process_exec_event->has_process() &&
process_exec_event->process().canonical_pid() > 1) {
exec_event_metric = metrics::ProcessEvent::kParentPidNotInCache;
}
MetricsSender::GetInstance().IncrementCountMetric(
metrics::kCommandLineSize, process_start.task_info.real_commandline_len);
MetricsSender::GetInstance().IncrementBatchedMetric(metrics::kExecEvent,
exec_event_metric);
return process_exec_event;
}
std::unique_ptr<pb::ProcessTerminateEvent> ProcessPlugin::MakeTerminateEvent(
const bpf::cros_process_exit& process_exit) {
auto process_terminate_event = std::make_unique<pb::ProcessTerminateEvent>();
// Try to fetch from the process cache if possible. The cache has more
// complete information.
auto hierarchy = process_cache_->GetProcessHierarchy(
process_exit.task_info.pid, process_exit.task_info.start_time, 2);
// If that fails, fill in the task info that we got from BPF.
if (hierarchy.empty()) {
process_cache_->FillProcessFromBpf(
process_exit.task_info, process_exit.image_info,
process_terminate_event->mutable_process(),
device_user_->GetUsernamesForRedaction());
if (!process_exit.has_full_info) {
LOG(WARNING) << absl::StrFormat(
"process_exit for pid=%d reltime=%d uuid=%s filled with partial "
"process info.",
process_terminate_event->process().canonical_pid(),
process_terminate_event->process().rel_start_time_s(),
process_terminate_event->process().process_uuid());
}
// Maybe the parent is still alive and in procfs.
auto parent = process_cache_->GetProcessHierarchy(
process_exit.task_info.ppid, process_exit.task_info.parent_start_time,
1);
if (parent.size() != 0) {
process_terminate_event->set_allocated_parent_process(
parent[0].release());
}
}
if (hierarchy.size() > 0) {
process_terminate_event->set_allocated_process(hierarchy[0].release());
}
if (hierarchy.size() > 1) {
process_terminate_event->set_allocated_parent_process(
hierarchy[1].release());
}
// Terminate event metrics.
metrics::ProcessEvent terminate_event_metric =
metrics::ProcessEvent::kFullEvent;
if (hierarchy.empty()) {
if (process_terminate_event->has_process()) {
terminate_event_metric = metrics::ProcessEvent::kParentStillAlive;
} else {
terminate_event_metric = metrics::ProcessEvent::kProcessPidNotInCache;
}
} else if (hierarchy.size() == 1 && process_terminate_event->has_process() &&
process_terminate_event->process().canonical_pid() > 1) {
terminate_event_metric = metrics::ProcessEvent::kParentPidNotInCache;
}
MetricsSender::GetInstance().IncrementBatchedMetric(metrics::kTerminateEvent,
terminate_event_metric);
return process_terminate_event;
}
} // namespace secagentd