blob: 15109ebc43fcfabc74dff66d07310d0a8fd13f35 [file] [log] [blame]
// 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 <iterator>
#include <memory>
#include <optional>
#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 "google/protobuf/message_lite.h"
#include "missive/proto/record_constants.pb.h"
#include "secagentd/bpf/process.h"
#include "secagentd/message_sender.h"
#include "secagentd/plugins.h"
#include "secagentd/proto/security_xdr_events.pb.h"
namespace {
namespace pb = cros_xdr::reporting;
// Fills a Namespaces proto with contents from bpf namespace_info.
void FillNamespaces(const secagentd::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);
}
} // namespace
namespace secagentd {
ProcessPlugin::ProcessPlugin(
scoped_refptr<BpfSkeletonFactoryInterface> bpf_skeleton_factory,
scoped_refptr<MessageSenderInterface> message_sender,
scoped_refptr<ProcessCacheInterface> process_cache)
: weak_ptr_factory_(this),
message_sender_(message_sender),
process_cache_(process_cache) {
CHECK(message_sender != nullptr);
CHECK(process_cache != nullptr);
CHECK(bpf_skeleton_factory);
factory_ = std::move(bpf_skeleton_factory);
}
std::string ProcessPlugin::GetName() const {
return "ProcessPlugin";
}
void ProcessPlugin::HandleRingBufferEvent(const bpf::cros_event& bpf_event) {
std::unique_ptr<google::protobuf::MessageLite> message = nullptr;
pb::CommonEventDataFields* mutable_common = nullptr;
reporting::Destination destination =
reporting::Destination::CROS_SECURITY_PROCESS;
if (bpf_event.type == bpf::process_type) {
const bpf::cros_process_event& pe = bpf_event.data.process_event;
if (pe.type == bpf::process_start_type) {
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);
if (process_cache_->IsEventFiltered(*exec_event)) {
return;
}
// Save a pointer to the common field before downcasting.
mutable_common = exec_event->mutable_common();
message = std::move(exec_event);
} else if (pe.type == bpf::process_exit_type) {
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);
}
if (process_cache_->IsEventFiltered(*terminate_event)) {
return;
}
mutable_common = terminate_event->mutable_common();
message = std::move(terminate_event);
} else {
LOG(ERROR) << "ProcessBPF: unknown BPF process event type.";
return;
}
} else {
LOG(ERROR) << "ProcessBPF: unknown BPF event type.";
return;
}
if (message) {
message_sender_->SendMessage(destination, mutable_common,
std::move(message), std::nullopt);
} else {
LOG(ERROR) << "Message construction failed for event.";
}
}
void ProcessPlugin::HandleBpfRingBufferReadReady() const {
skeleton_wrapper_->ConsumeEvent();
}
absl::Status ProcessPlugin::Activate() {
struct BpfCallbacks callbacks;
callbacks.ring_buffer_event_callback = base::BindRepeating(
&ProcessPlugin::HandleRingBufferEvent, weak_ptr_factory_.GetWeakPtr());
callbacks.ring_buffer_read_ready_callback =
base::BindRepeating(&ProcessPlugin::HandleBpfRingBufferReadReady,
weak_ptr_factory_.GetWeakPtr());
skeleton_wrapper_ =
factory_->Create(Types::BpfSkeleton::kProcess, std::move(callbacks));
if (skeleton_wrapper_ == nullptr) {
return absl::InternalError("Process BPF program loading error.");
}
return absl::OkStatus();
}
std::unique_ptr<pb::XdrProcessEvent> ProcessPlugin::MakeExecEvent(
const secagentd::bpf::cros_process_start& process_start) {
auto process_event = std::make_unique<pb::XdrProcessEvent>();
auto process_exec_event = process_event->mutable_process_exec();
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());
}
return process_event;
}
std::unique_ptr<pb::XdrProcessEvent> ProcessPlugin::MakeTerminateEvent(
const secagentd::bpf::cros_process_exit& process_exit) {
auto process_event = std::make_unique<pb::XdrProcessEvent>();
auto process_terminate_event = process_event->mutable_process_terminate();
// 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 (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());
}
// If that fails, fill in the task info that we got from BPF.
if (hierarchy.size() == 0) {
ProcessCache::PartiallyFillProcessFromBpfTaskInfo(
process_exit.task_info, process_terminate_event->mutable_process());
// 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());
}
}
return process_event;
}
} // namespace secagentd