blob: 6c80a68e0ed7a38d238f898767ddf0076bdc65c5 [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.
#ifndef SECAGENTD_PROCESS_CACHE_H_
#define SECAGENTD_PROCESS_CACHE_H_
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_map.h"
#include "absl/status/statusor.h"
#include "base/containers/lru_cache.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/synchronization/lock.h"
#include "secagentd/bpf/bpf_types.h"
#include "secagentd/device_user.h"
#include "secagentd/image_cache.h"
#include "secagentd/metrics_sender.h"
#include "secagentd/proto/security_xdr_events.pb.h"
namespace secagentd {
namespace testing {
class ProcessCacheTestFixture;
}
class ProcessCacheInterface
: public base::RefCountedThreadSafe<ProcessCacheInterface> {
public:
virtual ~ProcessCacheInterface() = default;
// Internalizes a process exec event from the BPF.
virtual void PutFromBpfExec(const bpf::cros_process_start& process_start) = 0;
// Evicts the given process from the cache if present.
virtual void EraseProcess(uint64_t pid, bpf::time_ns_t start_time_ns) = 0;
// Returns num_generations worth of processes in the process tree of the given
// pid; including pid itself. start_time_ns is used as a safety check against
// PID reuse.
virtual std::vector<std::unique_ptr<cros_xdr::reporting::Process>>
GetProcessHierarchy(uint64_t pid,
bpf::time_ns_t start_time_ns,
int num_generations) = 0;
// Is the event a noisy background event that should be dropped?
virtual bool IsEventFiltered(
const cros_xdr::reporting::Process* parent_process,
const cros_xdr::reporting::Process* process) = 0;
// Initializes the event filter for use.
// underscorify is used for testing and is defaulted to false. The fake root
// in the unit test uses underscores for subdirectories rather than creating
// real ones.
virtual void InitializeFilter(bool underscorify = false) = 0;
virtual void FillProcessFromBpf(
const bpf::cros_process_task_info& task_info,
const bpf::cros_image_info& image_info,
cros_xdr::reporting::Process* process_proto,
const std::list<std::string>& redacted_usernames) = 0;
};
class ProcessCache : public ProcessCacheInterface {
public:
struct InternalProcessKeyType {
uint64_t start_time_t;
uint64_t pid;
bool operator<(const InternalProcessKeyType& rhs) const {
return std::tie(start_time_t, pid) < std::tie(rhs.start_time_t, rhs.pid);
}
};
struct InternalProcessValueType {
std::unique_ptr<cros_xdr::reporting::Process> process_proto;
InternalProcessKeyType parent_key;
};
using InternalProcessCacheType =
base::LRUCache<InternalProcessKeyType, InternalProcessValueType>;
struct InternalFilterRule {
std::string image_pathname;
// Optionally match against the entire commandline.
std::vector<std::string> commandline;
};
using InternalFilterRuleSetType =
absl::flat_hash_map<std::string, InternalFilterRule>;
// Converts ns (from BPF) to clock_t for use in InternalProcessKeyType. It
// would be ideal to do this conversion in the BPF but we lack the required
// kernel constants there.
static uint64_t LossyNsecToClockT(bpf::time_ns_t ns);
// Converts clock_t to seconds.
static int64_t ClockTToSeconds(uint64_t clock_t);
static void PartiallyFillProcessFromBpfTaskInfo(
const bpf::cros_process_task_info& task_info,
cros_xdr::reporting::Process* process_proto,
const std::list<std::string>& redacted_usernames);
void FillProcessFromBpf(
const bpf::cros_process_task_info& task_info,
const bpf::cros_image_info& image_info,
cros_xdr::reporting::Process* process_proto,
const std::list<std::string>& redacted_usernames) override;
template <typename ProtoT>
static void FillProcessTree(
ProtoT proto,
const bpf::cros_process_start& process_start,
bool has_full_process_start,
scoped_refptr<ProcessCacheInterface> process_cache,
scoped_refptr<DeviceUserInterface> device_user);
explicit ProcessCache(scoped_refptr<DeviceUserInterface> device_user);
void PutFromBpfExec(const bpf::cros_process_start& process_start) override;
void EraseProcess(uint64_t pid, bpf::time_ns_t start_time_ns) override;
std::vector<std::unique_ptr<cros_xdr::reporting::Process>>
GetProcessHierarchy(uint64_t pid,
bpf::time_ns_t start_time_ns,
int num_generations) override;
bool IsEventFiltered(const cros_xdr::reporting::Process* parent_process,
const cros_xdr::reporting::Process* process) override;
void InitializeFilter(bool underscorify = false) override;
// Allow calling the private test-only constructor without befriending
// scoped_refptr.
template <typename... Args>
static scoped_refptr<ProcessCache> CreateForTesting(Args&&... args) {
return base::WrapRefCounted(new ProcessCache(std::forward<Args>(args)...));
}
ProcessCache(const ProcessCache&) = delete;
ProcessCache(ProcessCache&&) = delete;
ProcessCache& operator=(const ProcessCache&) = delete;
ProcessCache& operator=(ProcessCache&&) = delete;
private:
friend class testing::ProcessCacheTestFixture;
// Internal constructor used for testing.
ProcessCache(const base::FilePath& root_path,
scoped_refptr<DeviceUserInterface> device_user,
scoped_refptr<ImageCacheInterface> image_cache);
// Like LRUCache::Get, returns an internal iterator to the given key. Unlike
// LRUCache::Get, best-effort tries to fetch missing keys from procfs. Then
// inclusively Puts them in process_cache_ if successful and returns an
// iterator.
std::pair<ProcessCache::InternalProcessCacheType::iterator, metrics::Cache>
InclusiveGetProcess(const InternalProcessKeyType& key);
absl::StatusOr<InternalProcessValueType> MakeFromProcfs(
const InternalProcessKeyType& key);
// Fills in the image proto from the given procfs directory. Uses
// pid_for_setns to resolve any exe symlinks.
absl::Status FillImageFromProcfs(
const base::FilePath& proc_pid_dir,
uint64_t pid_for_setns,
cros_xdr::reporting::FileImage* file_image_proto);
// Sends what percentage of process cache is full.
void SendPolledMetrics();
base::WeakPtrFactory<ProcessCache> weak_ptr_factory_;
base::Lock process_cache_lock_;
std::unique_ptr<InternalProcessCacheType> process_cache_;
scoped_refptr<DeviceUserInterface> device_user_;
const base::FilePath root_path_;
InternalFilterRuleSetType filter_rules_parent_;
InternalFilterRuleSetType filter_rules_process_;
int64_t earliest_seen_exec_rel_s_;
scoped_refptr<ImageCacheInterface> image_cache_;
};
template <typename ProtoT>
void ProcessCache::FillProcessTree(
ProtoT proto,
const bpf::cros_process_start& process_start,
bool has_full_process_start,
scoped_refptr<ProcessCacheInterface> process_cache,
scoped_refptr<DeviceUserInterface> device_user) {
if (has_full_process_start) {
process_cache->FillProcessFromBpf(
process_start.task_info, process_start.image_info,
proto->mutable_process(), device_user->GetUsernamesForRedaction());
auto parent_process = process_cache->GetProcessHierarchy(
process_start.task_info.ppid, process_start.task_info.parent_start_time,
1);
if (parent_process.size() == 1) {
proto->set_allocated_parent_process(parent_process[0].release());
}
return;
}
// No full process info included, fallback to using cache.
auto hierarchy = process_cache->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 2);
if (hierarchy.empty()) {
VLOG(1) << absl::StrFormat(
"pid %d cmdline(%s) not in process cache. "
"Creating a degraded %s filled with information available from BPF"
"process map.",
process_start.task_info.pid, process_start.task_info.commandline,
proto->GetTypeName());
ProcessCache::PartiallyFillProcessFromBpfTaskInfo(
process_start.task_info, proto->mutable_process(),
device_user->GetUsernamesForRedaction());
auto parent_process = process_cache->GetProcessHierarchy(
process_start.task_info.ppid, process_start.task_info.parent_start_time,
1);
if (parent_process.size() == 1) {
proto->set_allocated_parent_process(parent_process[0].release());
}
}
if (hierarchy.size() >= 1) {
proto->set_allocated_process(hierarchy[0].release());
}
if (hierarchy.size() == 2) {
proto->set_allocated_parent_process(hierarchy[1].release());
}
}
} // namespace secagentd
#endif // SECAGENTD_PROCESS_CACHE_H_