blob: 2e7c0b3233a0998627049995f2f5117fa883b502 [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 "secagentd/process_cache.h"
#include <unistd.h>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/task_environment.h"
#include "brillo/files/file_util.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "secagentd/bpf/bpf_types.h"
#include "secagentd/proto/security_xdr_events.pb.h"
#include "secagentd/test/mock_device_user.h"
#include "secagentd/test/test_utils.h"
namespace {
namespace pb = cros_xdr::reporting;
// Partially() protobuf matcher isn't available and importing it is more
// involved than copy-pasting a single macro. So improvise.
void ExpectPartialMatch(pb::Process& expected, pb::Process& actual) {
EXPECT_EQ(expected.canonical_pid(), actual.canonical_pid());
EXPECT_EQ(expected.commandline(), actual.commandline());
if (expected.has_image()) {
EXPECT_EQ(expected.image().pathname(), actual.image().pathname());
EXPECT_EQ(expected.image().mnt_ns(), actual.image().mnt_ns());
EXPECT_STRCASEEQ(expected.image().sha256().c_str(),
actual.image().sha256().c_str());
}
}
} // namespace
namespace secagentd::testing {
using ::testing::Return;
class ProcessCacheTestFixture : public ::testing::Test {
protected:
ProcessCacheTestFixture()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
struct MockProcFsFile {
std::string procstat;
uint64_t starttime_ns;
std::string cmdline;
base::FilePath mnt_ns_root;
base::FilePath exe_path;
std::string exe_contents;
std::string exe_sha256;
base::FilePath mnt_ns_symlink;
pb::Process expected_proto;
};
struct MockBpfSpawnEvent {
bpf::cros_process_start process_start;
std::string exe_contents;
std::string exe_sha256;
pb::Process expected_proto;
};
static constexpr uint64_t kPidInit = 1;
static constexpr uint64_t kPidKthreadd = 2;
static constexpr uint64_t kPidChildOfInit = 962;
static constexpr uint64_t kPidChildOfChild = 23888;
static constexpr uint64_t kPidSiblingOfChildOfChild = 1234;
static constexpr uint64_t kPidTrickyComm = 8934;
static constexpr uint64_t kPidThermalProcess = 9843;
static constexpr uint64_t kPidChildOfThermalProcess = 9024;
static constexpr uint64_t kPidRecoverDutProcess = 9168;
static constexpr uint64_t kPidChildOfRecoverDutProcess = 9114;
static constexpr uint64_t kPidContainerInit = 3997;
static constexpr uint64_t kPidChildOfContainerInit = 3949;
constexpr static const char kUsername[] = "username@email.com";
base::FilePath GetPathInCurrentMountNsOrDie(uint64_t pid_for_setns,
const base::FilePath& path) {
auto setns_proc = mock_procfs_.find(pid_for_setns);
// This is a limitation of the test because BPF spawns don't get a mock
// procfs entry. We use /proc/pid_for_setns/root as the mount namespace
// root which needs to exist for the test to work.
EXPECT_NE(mock_procfs_.end(), setns_proc)
<< "Test error. No procfs entry exists for pid " << pid_for_setns;
auto statusorpath = ProcessCache::SafeAppendAbsolutePath(
setns_proc->second.mnt_ns_root, path);
EXPECT_TRUE(statusorpath.ok());
return *statusorpath;
}
void CreateFakeFs(const base::FilePath& root) {
const base::FilePath proc_dir = root.Append("proc");
ASSERT_TRUE(base::CreateDirectory(proc_dir));
for (auto& p : mock_procfs_) {
const base::FilePath pid_dir = proc_dir.Append(std::to_string(p.first));
ASSERT_TRUE(base::CreateDirectory(pid_dir));
ASSERT_TRUE(base::WriteFile(pid_dir.Append("stat"), p.second.procstat));
ASSERT_TRUE(base::WriteFile(pid_dir.Append("cmdline"), p.second.cmdline));
if (!p.second.exe_path.empty() && !p.second.mnt_ns_root.empty()) {
base::FilePath exe_path_in_current_ns =
p.second.mnt_ns_root.Append(p.second.exe_path);
ASSERT_TRUE(base::CreateSymbolicLink(p.second.mnt_ns_root,
pid_dir.Append("root")));
ASSERT_TRUE(
base::WriteFile(exe_path_in_current_ns, p.second.exe_contents));
// Procfs exe_path is always absolute. As in starts with a '/' where the
// '/' is to be interpreted as the root of the mount namespace.
ASSERT_TRUE(base::CreateSymbolicLink(
base::FilePath("/").Append(p.second.exe_path),
pid_dir.Append("exe")));
}
const base::FilePath ns_dir = pid_dir.Append("ns");
ASSERT_TRUE(base::CreateDirectory(ns_dir));
ASSERT_TRUE(base::CreateSymbolicLink(p.second.mnt_ns_symlink,
ns_dir.Append("mnt")));
}
for (auto& p : mock_spawns_) {
ASSERT_TRUE(base::WriteFile(
GetPathInCurrentMountNsOrDie(
p.second.process_start.image_info.pid_for_setns,
base::FilePath(p.second.process_start.image_info.pathname)),
p.second.exe_contents));
}
}
void FillDynamicImageInfoFromMockFs(
secagentd::bpf::cros_image_info* image_info) {
ASSERT_NE(nullptr, image_info->pathname);
// See mock_spawns_ loop in CreateFakeFs for explanation.
auto setns_proc = mock_procfs_.find(image_info->pid_for_setns);
ASSERT_NE(mock_procfs_.end(), setns_proc);
auto pathname_in_current_ns = GetPathInCurrentMountNsOrDie(
image_info->pid_for_setns, base::FilePath(image_info->pathname));
base::stat_wrapper_t stat;
ASSERT_EQ(0, base::File::Stat(pathname_in_current_ns, &stat));
image_info->inode_device_id = stat.st_dev;
image_info->inode = stat.st_ino;
image_info->mtime.tv_sec = stat.st_mtim.tv_sec;
image_info->mtime.tv_nsec = stat.st_mtim.tv_nsec;
image_info->ctime.tv_sec = stat.st_ctim.tv_sec;
image_info->ctime.tv_nsec = stat.st_ctim.tv_nsec;
}
void ClearInternalCache() { process_cache_->process_cache_->Clear(); }
void SetUp() override {
device_user_ = base::MakeRefCounted<MockDeviceUser>();
ASSERT_TRUE(fake_root_.CreateUniqueTempDir());
const base::FilePath& root = fake_root_.GetPath();
ASSERT_TRUE(fake_container_root_.CreateUniqueTempDir());
const base::FilePath& container_root = fake_container_root_.GetPath();
process_cache_ = ProcessCache::CreateForTesting(root, device_user_);
mock_procfs_ = {
{kPidInit,
{.procstat =
"1 (init) S 0 1 1 0 -1 4194560 52789 185694 61 508 25 147 624 "
"595 20 0 1 0 2 5705728 1114 184 46744073709551615 "
"93986791456768 93986791580992 140721417359440 0 0 0 0 4096 "
"536946211 1 0 0 17 4 0 0 2 0 0 93986791594336 939867915 95104 "
"93986819518464 140721417363254 140721417363304 140721417363304 "
"140721417363437 0 ",
.starttime_ns = 20000000,
.cmdline = "/sbin/init",
.mnt_ns_root = root,
.exe_path = base::FilePath("sbin_init"),
.exe_contents = "This is the init binary",
// echo -ne "This is the init binary" | sha256sum -
// 4d4328fb2f25759a7bd95772f2caf19af15ad7722c4105dd403a391a6e795b88 -
.exe_sha256 = "4D4328FB2F25759A7BD95772F2CAF19AF15AD7722C4105DD403A39"
"1A6E795B88",
.mnt_ns_symlink = base::FilePath("mnt:[402653184]"),
.expected_proto = pb::Process()}},
{kPidKthreadd,
{.procstat = "2 (kthreadd) S 0 0 0 0 -1 2129984 0 0 0 0 0 22 0 0 20 0 "
"1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 "
"0 1 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0",
.starttime_ns = 20000001,
.cmdline = "",
.mnt_ns_root = base::FilePath(),
.exe_path = base::FilePath(),
.exe_contents = "",
.mnt_ns_symlink = base::FilePath("mnt:[402653184]"),
.expected_proto = pb::Process()}},
{kPidChildOfInit,
{.procstat =
"962 (cryptohomed) S 1 962 962 0 -1 1077936192 131232 1548267 2 "
"0 111 322 2065 1451 20 0 5 0 378 408432640 3365 "
"18446744073709551615 97070014267392 97070015746192 "
"140737338593200 0 0 0 16387 0 0 0 0 0 17 7 0 0 0 0 0 97070015 "
"$ 98896 97070015799432 97070032941056 140737338596688 "
"140737338596750 140737338596750 140737338597346 0 ",
.starttime_ns = 3780000000,
.cmdline = std::string("cryptohomed\0--noclose\0--direncryption\0--"
"fscrypt_v2\0--vmodule=",
61),
.mnt_ns_root = root,
.exe_path = base::FilePath("usr_sbin_cryptohomed"),
.exe_contents = "This is the cryptohome binary",
// # echo -ne "This is the cryptohome binary" | sha256sum -
// 6923461afaed79a0ecd65048f47524fd7b873d7ff9e164b09b5d9a1d4b5e54f2 -
.exe_sha256 = "6923461AFAED79A0ECD65048F47524FD7B873D7FF9E164B09B5D9A"
"1D4B5E54F2",
.mnt_ns_symlink = base::FilePath("mnt:[402653184]"),
.expected_proto = pb::Process()}},
{kPidTrickyComm,
{.procstat =
"962 (crypto (home) d) S 1 962 962 0 -1 1077936192 131232 "
"1548267 2 "
"0 111 322 2065 1451 20 0 5 0 978 408432640 3365 "
"18446744073709551615 97070014267392 97070015746192 "
"140737338593200 0 0 0 16387 0 0 0 0 0 17 7 0 0 0 0 0 97070015 "
"$ 98896 97070015799432 97070032941056 140737338596688 "
"140737338596750 140737338596750 140737338597346 0 ",
.starttime_ns = 9780000000,
.cmdline = "commspoofer",
.mnt_ns_root = root,
.exe_path = base::FilePath("tmp_commspoofer"),
.exe_contents = "unused",
.exe_sha256 = "unused",
.mnt_ns_symlink = base::FilePath("mnt:[402653184]"),
.expected_proto = pb::Process()}},
{kPidThermalProcess,
{
.procstat =
"9843 (temp_logger.sh) S 9842 9841 9841 0 -1 4194560 118 142 "
"0 0 0 0 0 0 20 0 1 0 978 2838528 306 "
"18446744073709551615 97600878653440 97600878726528 "
"140731096167264 0 0 0 0 0 65538 1 0 0 17 2 0 0 0 0 0 "
"97600878738944 97600878739424 97600907300864 140731096170227 "
"140731096170271 140731096170271 140731096170452 0",
.starttime_ns = 9780000000,
.cmdline = std::string(
"/bin/sh\0/usr/share/cros/init/temp_logger.sh", 43),
.mnt_ns_root = root,
.exe_path = base::FilePath("bin_sh"),
.exe_contents = "This is the shell binary",
// echo -ne "This is the shell binary" | sha256sum
// 9DF8B99E5B9F67AAD3F2382F7633BDE35EE032881F7FFE4037550F831392FF81
.exe_sha256 = "9DF8B99E5B9F67AAD3F2382F7633BDE35EE032881F7FFE40375"
"50F831392FF81",
.mnt_ns_symlink = base::FilePath("mnt:[4026532856]"),
.expected_proto = pb::Process(),
}},
{kPidRecoverDutProcess,
{
.procstat =
"24498 (recover_duts) T 5038 24498 5038 34816 24639 "
"1077936128 118 234 0 0 0 0 0 0 20 0 1 0 0 2654208 226 "
"18446744073709551615 94131781091328 94131781165952 "
"140735961348096 0 0 0 0 0 65538 1 0 0 17 0 0 0 0 0 0 "
"94131781178368 94131781178848 94131798298624 140735961351745 "
"140735961351798 140735961351798 140735961354187 0",
.starttime_ns = 0,
.cmdline = std::string(
"/bin/sh\0/usr/local/libexec/recover-duts/recover_duts", 52),
.mnt_ns_root = root,
.exe_path = base::FilePath("bin_sh"),
.exe_contents = "This is the shell binary",
// echo -ne "This is the shell binary" | sha256sum
// 9DF8B99E5B9F67AAD3F2382F7633BDE35EE032881F7FFE4037550F831392FF81
.exe_sha256 = "9DF8B99E5B9F67AAD3F2382F7633BDE35EE032881F7FFE40375"
"50F831392FF81",
.mnt_ns_symlink = base::FilePath("mnt:[4026531840]"),
.expected_proto = pb::Process(),
}},
{kPidContainerInit,
{.procstat =
"3997 (main) S 3941 3997 3669 0 -1 4194560 31765 707181 468 8540 "
"73 127 9920 2653 20 0 5 0 5812 3846950912 41147 "
"18446744073709551615 94131135979520 94131135996864 "
"140722056860464 0 0 0 4612 1 1073841400 0 0 0 17 1 0 0 44 0 0 "
"94131136002680 94131136004096 94131168919552 140722056866380 "
"140722056866479 140722056866479 140722056867806 0",
.starttime_ns = 58120000000,
.cmdline = "init",
.mnt_ns_root = container_root,
.exe_path = base::FilePath("system_bin_init"),
.exe_contents = "This is the init binary",
// echo -ne "This is the init binary" | sha256sum -
// 4d4328fb2f25759a7bd95772f2caf19af15ad7722c4105dd403a391a6e795b88
// -
.exe_sha256 = "4D4328FB2F25759A7BD95772F2CAF19AF15AD7722C4105DD403A39"
"1A6E795B88",
.mnt_ns_symlink = base::FilePath("mnt:[4026534311]"),
.expected_proto = pb::Process()}},
};
// ParseFromString unfortunately doesn't work with Lite protos.
mock_procfs_[kPidInit].expected_proto.set_canonical_pid(kPidInit);
mock_procfs_[kPidInit].expected_proto.set_commandline("'/sbin/init'");
mock_procfs_[kPidInit].expected_proto.mutable_image()->set_pathname(
"/sbin_init");
mock_procfs_[kPidInit].expected_proto.mutable_image()->set_mnt_ns(
402653184);
mock_procfs_[kPidInit].expected_proto.mutable_image()->set_sha256(
mock_procfs_[kPidInit].exe_sha256);
mock_procfs_[kPidKthreadd].expected_proto.set_canonical_pid(kPidKthreadd);
mock_procfs_[kPidKthreadd].expected_proto.set_commandline("[kthreadd]");
mock_procfs_[kPidChildOfInit].expected_proto.set_canonical_pid(
kPidChildOfInit);
mock_procfs_[kPidChildOfInit].expected_proto.set_commandline(
"'cryptohomed' '--noclose' '--direncryption' '--fscrypt_v2' "
"'--vmodule='");
mock_procfs_[kPidChildOfInit].expected_proto.mutable_image()->set_pathname(
"/usr_sbin_cryptohomed");
mock_procfs_[kPidChildOfInit].expected_proto.mutable_image()->set_mnt_ns(
402653184);
mock_procfs_[kPidChildOfInit].expected_proto.mutable_image()->set_sha256(
mock_procfs_[kPidChildOfInit].exe_sha256);
mock_procfs_[kPidTrickyComm].expected_proto.set_canonical_pid(
kPidTrickyComm);
mock_procfs_[kPidTrickyComm].expected_proto.set_commandline(
"'commspoofer'");
mock_procfs_[kPidTrickyComm].expected_proto.mutable_image()->set_pathname(
"/tmp_comspoofer");
mock_procfs_[kPidTrickyComm].expected_proto.mutable_image()->set_mnt_ns(
402653184);
mock_procfs_[kPidTrickyComm].expected_proto.mutable_image()->set_sha256(
mock_procfs_[kPidTrickyComm].exe_sha256);
mock_procfs_[kPidContainerInit].expected_proto.set_canonical_pid(
kPidContainerInit);
mock_procfs_[kPidContainerInit].expected_proto.set_commandline("'init'");
mock_procfs_[kPidContainerInit]
.expected_proto.mutable_image()
->set_pathname("/system_bin_init");
mock_procfs_[kPidContainerInit].expected_proto.mutable_image()->set_mnt_ns(
4026534311);
mock_procfs_[kPidContainerInit].expected_proto.mutable_image()->set_sha256(
mock_procfs_[kPidContainerInit].exe_sha256);
mock_spawns_ = {
{kPidChildOfRecoverDutProcess,
{.process_start =
{.task_info =
{.pid = kPidChildOfRecoverDutProcess,
.ppid = kPidRecoverDutProcess,
.start_time = 5029384029,
.parent_start_time =
mock_procfs_[kPidRecoverDutProcess].starttime_ns,
.commandline =
"/bin/sh\0/usr/local/libexec/recover-duts/recover_duts",
.commandline_len = 52,
.uid = 0,
.gid = 0},
.image_info =
{
.pathname = "/bin_sh",
.mnt_ns = 4026531840,
.inode_device_id = 0,
.inode = 0,
.uid = 0,
.gid = 0,
.pid_for_setns = kPidInit,
.mode = 0100755,
},
.spawn_namespace =
{
.cgroup_ns = 4026532932,
.pid_ns = 4026532856,
.user_ns = 4026531837,
.uts_ns = 4026532858,
.mnt_ns = 4026532857,
.net_ns = 4026532859,
.ipc_ns = 4026533674,
}},
.exe_contents = "This is the recover dut binary",
// # echo -ne "This is the recover dut binary" | sha256sum -
// 370EF140032B15E038FC673568221074D40153DB7EF61297B63276107714A6B8
.exe_sha256 = "370EF140032B15E038FC673568221074D40153DB7EF61297B63276"
"107714A6B8",
.expected_proto = pb::Process()}},
{kPidChildOfThermalProcess,
{.process_start =
{.task_info =
{.pid = kPidChildOfThermalProcess,
.ppid = kPidThermalProcess,
.start_time = 5029384029,
.parent_start_time =
mock_procfs_[kPidThermalProcess].starttime_ns,
.commandline =
"/usr/bin/logger\0-t\0temp_logger\0\"Exiting "
"temp_logger, system does not have any temp sensor.\"",
.commandline_len = 58,
.uid = 0,
.gid = 0},
.image_info =
{
.pathname = "/usr_bin_logger",
.mnt_ns = 4026531840,
.inode_device_id = 0,
.inode = 0,
.uid = 0,
.gid = 0,
.pid_for_setns = kPidInit,
.mode = 0100755,
},
.spawn_namespace =
{
.cgroup_ns = 4026532932,
.pid_ns = 4026532856,
.user_ns = 4026531837,
.uts_ns = 4026532858,
.mnt_ns = 4026532857,
.net_ns = 4026532859,
.ipc_ns = 4026533674,
}},
.exe_contents = "This is the logger binary",
// # echo -ne "This is the logger binary" | sha256sum -
// D1F76C43FB64CDCB35DE37F518C4AD1EE8EE247D540B6F2C07358657E4AA2F59
.exe_sha256 = "D1F76C43FB64CDCB35DE37F518C4AD1EE8EE247D540B6F2C073586"
"57E4AA2F59",
.expected_proto = pb::Process()}},
{kPidSiblingOfChildOfChild,
{.process_start =
{.task_info = {.pid = kPidSiblingOfChildOfChild,
.ppid = kPidChildOfInit,
.start_time = 5029384029,
.parent_start_time =
mock_procfs_[kPidChildOfInit].starttime_ns,
.commandline =
"/bin/sh\0/usr/share/cros/init/temp_logger.sh",
.commandline_len = 43,
.uid = 0,
.gid = 0},
.image_info =
{
.pathname = "/bin_sh",
.mnt_ns = 4026531840,
.inode_device_id = 0,
.inode = 0,
.uid = 0,
.gid = 0,
.pid_for_setns = kPidChildOfInit,
.mode = 0100755,
},
.spawn_namespace =
{
.cgroup_ns = 4026531835,
.pid_ns = 4026531836,
.user_ns = 4026531837,
.uts_ns = 4026531838,
.mnt_ns = 4026531840,
.net_ns = 4026531999,
.ipc_ns = 4026531839,
}},
.exe_contents = "This is the shell binary",
// echo -ne "This is the shell binary" | sha256sum
// 9DF8B99E5B9F67AAD3F2382F7633BDE35EE032881F7FFE4037550F831392FF81
.exe_sha256 = "9DF8B99E5B9F67AAD3F2382F7633BDE35EE032881F7FFE40375"
"50F831392FF81",
.expected_proto = pb::Process()}},
{kPidChildOfChild,
{.process_start =
{.task_info = {.pid = kPidChildOfChild,
.ppid = kPidChildOfInit,
.start_time = 5029384029,
.parent_start_time =
mock_procfs_[kPidChildOfInit].starttime_ns,
.commandline =
"/usr/sbin/spaced_cli\0"
"--get_free_disk_space=/home/.shadow",
.commandline_len = 57,
.uid = 0,
.gid = 0},
.image_info =
{
.pathname = "/usr_sbin_spaced_cli",
.mnt_ns = 4026531840,
.inode_device_id = 0,
.inode = 0,
.uid = 0,
.gid = 0,
.pid_for_setns = kPidChildOfInit,
.mode = 0100755,
},
.spawn_namespace =
{
.cgroup_ns = 4026531835,
.pid_ns = 4026531836,
.user_ns = 4026531837,
.uts_ns = 4026531838,
.mnt_ns = 4026531840,
.net_ns = 4026531999,
.ipc_ns = 4026531839,
}},
.exe_contents = "This is the spaced_cli binary",
// # echo -ne "This is the spaced_cli binary" | sha256sum -
// 7c3ad304a78de0191f3c682d84f22787ad1085ae1cf1c158544b097556dcf408 -
.exe_sha256 = "7C3AD304A78DE0191F3C682D84F22787AD1085AE1CF1C158544B09"
"7556DCF408",
.expected_proto = pb::Process()}},
{kPidChildOfContainerInit,
{.process_start =
{.task_info = {.pid = kPidChildOfContainerInit,
.ppid = kPidContainerInit,
.start_time = 58200000000,
.parent_start_time =
mock_procfs_[kPidContainerInit].starttime_ns,
.commandline = "/sbin/udevadm",
.commandline_len = 14,
.uid = 655360,
.gid = 655360},
.image_info =
{
.pathname = "/sbin_udevadm",
.mnt_ns = 4026534311,
.inode_device_id = 0,
.inode = 0,
.uid = 0,
.gid = 0,
.pid_for_setns = kPidContainerInit,
.mode = 0100755,
},
.spawn_namespace =
{
.cgroup_ns = 4026534072,
.pid_ns = 4026533764,
.user_ns = 4026532803,
.uts_ns = 4026531838,
.mnt_ns = 4026534311,
.net_ns = 4026533768,
.ipc_ns = 4026533766,
}},
.exe_contents = "This is the container udevadm binary",
// # echo -ne "This is the container udevadm binary" | sha256sum -
// 8db47df5ef67311335c2d2d464491d08a1c84030cf16031ea22b3c5eb82abb27 -
.exe_sha256 = "8DB47DF5EF67311335C2D2D464491D08A1C84030CF16031EA22B3C"
"5EB82ABB27",
.expected_proto = pb::Process()}},
};
CreateFakeFs(root);
ASSERT_NO_FATAL_FAILURE(FillDynamicImageInfoFromMockFs(
&mock_spawns_[kPidChildOfChild].process_start.image_info));
mock_spawns_[kPidChildOfChild].expected_proto.set_canonical_pid(
kPidChildOfChild);
mock_spawns_[kPidChildOfChild].expected_proto.set_canonical_uid(0);
mock_spawns_[kPidChildOfChild].expected_proto.set_rel_start_time_s(
ProcessCache::ClockTToSeconds(
ProcessCache::LossyNsecToClockT(5029384029)));
mock_spawns_[kPidChildOfChild].expected_proto.set_commandline(
"'/usr/sbin/spaced_cli' '--get_free_disk_space=/home/.shadow'");
mock_spawns_[kPidChildOfChild].expected_proto.mutable_image()->set_pathname(
"/usr_sbin_spaced_cli");
mock_spawns_[kPidChildOfChild].expected_proto.mutable_image()->set_mnt_ns(
4026531840);
mock_spawns_[kPidChildOfChild]
.expected_proto.mutable_image()
->set_inode_device_id(mock_spawns_[kPidChildOfChild]
.process_start.image_info.inode_device_id);
mock_spawns_[kPidChildOfChild].expected_proto.mutable_image()->set_inode(
mock_spawns_[kPidChildOfChild].process_start.image_info.inode);
mock_spawns_[kPidChildOfChild]
.expected_proto.mutable_image()
->set_canonical_uid(0);
mock_spawns_[kPidChildOfChild]
.expected_proto.mutable_image()
->set_canonical_gid(0);
mock_spawns_[kPidChildOfChild].expected_proto.mutable_image()->set_mode(
0100755);
mock_spawns_[kPidChildOfChild].expected_proto.mutable_image()->set_sha256(
mock_spawns_[kPidChildOfChild].exe_sha256);
ASSERT_NO_FATAL_FAILURE(FillDynamicImageInfoFromMockFs(
&mock_spawns_[kPidChildOfContainerInit].process_start.image_info));
mock_spawns_[kPidChildOfContainerInit].expected_proto.set_canonical_pid(
kPidChildOfContainerInit);
mock_spawns_[kPidChildOfContainerInit].expected_proto.set_canonical_uid(
655360);
mock_spawns_[kPidChildOfContainerInit].expected_proto.set_rel_start_time_s(
ProcessCache::ClockTToSeconds(
ProcessCache::LossyNsecToClockT(58200000000)));
mock_spawns_[kPidChildOfContainerInit].expected_proto.set_commandline(
"'/sbin/udevadm'");
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_pathname("/sbin_udevadm");
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_mnt_ns(4026534311);
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_inode_device_id(mock_spawns_[kPidChildOfContainerInit]
.process_start.image_info.inode_device_id);
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_inode(mock_spawns_[kPidChildOfContainerInit]
.process_start.image_info.inode);
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_canonical_uid(0);
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_canonical_gid(0);
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_mode(0100755);
mock_spawns_[kPidChildOfContainerInit]
.expected_proto.mutable_image()
->set_sha256(mock_spawns_[kPidChildOfContainerInit].exe_sha256);
}
base::test::TaskEnvironment task_environment_;
scoped_refptr<ProcessCache> process_cache_;
scoped_refptr<MockDeviceUser> device_user_;
base::ScopedTempDir fake_root_;
base::ScopedTempDir fake_container_root_;
std::map<uint64_t, MockProcFsFile> mock_procfs_;
std::map<uint64_t, MockBpfSpawnEvent> mock_spawns_;
};
TEST_F(ProcessCacheTestFixture, TestStableUuid) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(mock_spawns_[kPidChildOfChild].process_start);
auto before = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 2);
ClearInternalCache();
process_cache_->PutFromBpfExec(process_start);
auto after = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 2);
EXPECT_EQ(before[0]->process_uuid(), after[0]->process_uuid());
EXPECT_EQ(before[1]->process_uuid(), after[1]->process_uuid());
// Might as well check that the UUIDs are somewhat unique.
EXPECT_NE(before[0]->process_uuid(), before[1]->process_uuid());
}
TEST_F(ProcessCacheTestFixture, TestUuidBpfVsProcfs) {
const bpf::cros_process_task_info task_info = {
.pid = kPidChildOfInit,
.start_time = mock_procfs_[kPidChildOfInit].starttime_ns,
};
cros_xdr::reporting::Process bpf_process_proto;
ProcessCache::PartiallyFillProcessFromBpfTaskInfo(
task_info, &bpf_process_proto, std::list<std::string>());
EXPECT_TRUE(bpf_process_proto.has_process_uuid());
auto procfs_process_proto = process_cache_->GetProcessHierarchy(
kPidChildOfInit, mock_procfs_[kPidChildOfInit].starttime_ns, 1);
EXPECT_EQ(1, procfs_process_proto.size());
EXPECT_TRUE(procfs_process_proto[0]->has_process_uuid());
EXPECT_EQ(bpf_process_proto.process_uuid(),
procfs_process_proto[0]->process_uuid());
}
TEST_F(ProcessCacheTestFixture, ProcfsCacheHit) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(process_start);
auto before = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
EXPECT_EQ(3, before.size());
// Verify and unset this metadata separately since it's expected to change
// between calls.
for (auto& proc : before) {
// Verify that start_times set in the mock procfs spawns are earlier than
// the first BPF exec here. This bypasses the heuristic which is covered in
// a separate test case.
ASSERT_LE(proc->rel_start_time_s(), process_start.task_info.start_time);
EXPECT_TRUE(proc->has_meta_first_appearance());
EXPECT_TRUE(proc->meta_first_appearance());
proc->clear_meta_first_appearance();
}
ASSERT_TRUE(fake_root_.Delete());
bpf::cros_process_start process_start_sibling = process_start;
process_start_sibling.task_info.pid = process_start.task_info.pid + 1;
process_start_sibling.task_info.start_time =
process_start.task_info.start_time + 1;
process_cache_->PutFromBpfExec(process_start_sibling);
auto after = process_cache_->GetProcessHierarchy(
process_start_sibling.task_info.pid,
process_start_sibling.task_info.start_time, 3);
EXPECT_EQ(3, after.size());
// We've only seen after[1] and after[2] earlier as before[1] and before[2]
// respectively.
for (int i = 0; i < 3; ++i) {
EXPECT_TRUE(after[i]->has_meta_first_appearance());
if (i == 0) {
EXPECT_TRUE(after[i]->meta_first_appearance());
} else {
// Verify and clear this volatile metadata as done earlier with before.
EXPECT_FALSE(after[i]->meta_first_appearance());
after[i]->clear_meta_first_appearance();
EXPECT_THAT(*before[i], EqualsProto(*after[i]));
}
}
ExpectPartialMatch(mock_procfs_[kPidChildOfInit].expected_proto, *before[1]);
ExpectPartialMatch(mock_procfs_[kPidInit].expected_proto, *before[2]);
}
TEST_F(ProcessCacheTestFixture, ProcfsScrapeButSeenBefore) {
// Heuristic uses earliest_seen_exec_rel_s_. Set that to a very low value
// first.
const bpf::cros_process_start earliest_seen_exec = {
.task_info = {
.pid = 9999,
.ppid = kPidInit,
.start_time =
mock_spawns_[kPidInit].process_start.task_info.start_time + 1,
.parent_start_time =
mock_spawns_[kPidInit].process_start.task_info.start_time}};
process_cache_->PutFromBpfExec(earliest_seen_exec);
// Spawn a second BPF process with parent that's younger than
// earliest_seen_exec.
const bpf::cros_process_start& exec_with_young_ancestors =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(exec_with_young_ancestors);
auto actual = process_cache_->GetProcessHierarchy(
exec_with_young_ancestors.task_info.pid,
exec_with_young_ancestors.task_info.start_time, 2);
EXPECT_EQ(2, actual.size());
ASSERT_GT(actual[1]->rel_start_time_s(),
earliest_seen_exec.task_info.start_time);
EXPECT_TRUE(actual[1]->has_meta_first_appearance());
EXPECT_FALSE(actual[1]->meta_first_appearance());
}
TEST_F(ProcessCacheTestFixture, ThermalLoggerChildrenExecEventsAreFiltered) {
// underscorify the filter paths.
process_cache_->InitializeFilter(true);
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfThermalProcess].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_TRUE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture,
ThermalLoggerChildrenTerminateEventsAreFiltered) {
// underscorify the filter paths.
process_cache_->InitializeFilter(true);
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfThermalProcess].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_TRUE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture, RecoverDutsChildrenExecEventsAreFiltered) {
// underscorify the filter paths.
process_cache_->InitializeFilter(true);
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfRecoverDutProcess].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_TRUE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture, RecoverDutsChildrenTerminateEventsAreFiltered) {
// underscorify the filter paths.
process_cache_->InitializeFilter(true);
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfRecoverDutProcess].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_TRUE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture, SpacedCliExecEventsAreFiltered) {
// underscorify the filter paths.
process_cache_->InitializeFilter(true);
// this is spaced_cli as called by cryptohome.
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 2);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_TRUE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture, SpacedCliTerminateEventsAreFiltered) {
// underscorify the filter paths.
process_cache_->InitializeFilter(true);
// this is spaced_cli as called by cryptohome.
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 2);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_TRUE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture, NotEverythingIsFiltered) {
process_cache_->InitializeFilter(true);
// this is cryptohom
const bpf::cros_process_start& process_start =
mock_spawns_[kPidSiblingOfChildOfChild].process_start;
process_cache_->PutFromBpfExec(process_start);
auto hierarchy = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 2);
EXPECT_GE(hierarchy.size(), 2);
EXPECT_FALSE(
process_cache_->IsEventFiltered(hierarchy[1].get(), hierarchy[0].get()));
}
TEST_F(ProcessCacheTestFixture, BpfCacheHit) {
const bpf::cros_process_start bpf_child = {
.task_info = {
.pid = 9999,
.ppid = kPidChildOfChild,
.start_time = 999999999,
.parent_start_time = mock_spawns_[kPidChildOfChild]
.process_start.task_info.start_time}};
process_cache_->PutFromBpfExec(mock_spawns_[kPidChildOfChild].process_start);
auto before = process_cache_->GetProcessHierarchy(
kPidChildOfChild,
mock_spawns_[kPidChildOfChild].process_start.task_info.start_time, 2);
EXPECT_EQ(2, before.size());
for (auto& proc : before) {
EXPECT_TRUE(proc->has_meta_first_appearance());
EXPECT_TRUE(proc->meta_first_appearance());
}
process_cache_->PutFromBpfExec(bpf_child);
auto after = process_cache_->GetProcessHierarchy(
bpf_child.task_info.pid, bpf_child.task_info.start_time, 4);
EXPECT_EQ(4, after.size());
// We've seen after[1] and after[2] earlier as before[0] and before[1]
// respectively.
for (int i = 0; i < 4; ++i) {
EXPECT_TRUE(after[i]->has_meta_first_appearance());
bool expected_first_appearance = (i == 0 || i == 3);
EXPECT_EQ(expected_first_appearance, after[i]->meta_first_appearance());
// Clearing this volatile metadata as it's not present in the
// expected_proto.
after[i]->clear_meta_first_appearance();
}
// Cheat and copy the UUID because we don't have a real Partial matcher.
mock_spawns_[kPidChildOfChild].expected_proto.set_process_uuid(
after[1]->process_uuid());
EXPECT_THAT(mock_spawns_[kPidChildOfChild].expected_proto,
EqualsProto(*after[1]));
ExpectPartialMatch(mock_procfs_[kPidChildOfInit].expected_proto, *after[2]);
ExpectPartialMatch(mock_procfs_[kPidInit].expected_proto, *after[3]);
}
TEST_F(ProcessCacheTestFixture, TruncateAtInit) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 5);
// Asked for 5, got 3 including init.
EXPECT_EQ(3, actual.size());
}
TEST_F(ProcessCacheTestFixture, TruncateOnBpfParentPidReuse) {
bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_start.task_info.parent_start_time -= 10;
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
// Asked for 3, got 1 because parent start time didn't match.
EXPECT_EQ(1, actual.size());
}
TEST_F(ProcessCacheTestFixture, TruncateOnBpfParentNotFound) {
bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_start.task_info.ppid -= 10;
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
// Asked for 3, got 1 because parent pid doesn't exist in procfs.
EXPECT_EQ(1, actual.size());
}
TEST_F(ProcessCacheTestFixture, DontFailProcfsIfParentLinkageNotFound) {
bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
// "Kill" init
brillo::DeletePathRecursively(
fake_root_.GetPath().Append("proc").Append(std::to_string(kPidInit)));
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
// Asked for 3, got 2. Init doesn't exist but we at least got "child" even
// though we failed to resolve its parent linkage.
EXPECT_EQ(2, actual.size());
}
TEST_F(ProcessCacheTestFixture, ParseTrickyComm) {
bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_start.task_info.ppid = kPidTrickyComm;
process_start.task_info.parent_start_time =
mock_procfs_[kPidTrickyComm].starttime_ns;
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
// Asked for 3, got 3. I.e we were able to parse commspoofer's stat to find
// its parent.
EXPECT_EQ(3, actual.size());
}
TEST_F(ProcessCacheTestFixture, TestChildOfKthread) {
bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_start.task_info.ppid = kPidKthreadd;
process_start.task_info.parent_start_time =
mock_procfs_[kPidKthreadd].starttime_ns;
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 3);
// Kthread doesn't have a parent. So we only get one ancestral process despite
// asking for 3 as usual.
EXPECT_EQ(2, actual.size());
ExpectPartialMatch(mock_procfs_[kPidKthreadd].expected_proto, *actual[1]);
}
TEST_F(ProcessCacheTestFixture, TestErase) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
process_cache_->PutFromBpfExec(process_start);
auto before = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 1);
EXPECT_EQ(1, before.size());
process_cache_->EraseProcess(process_start.task_info.pid,
process_start.task_info.start_time);
auto after = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 1);
EXPECT_EQ(0, after.size());
}
TEST_F(ProcessCacheTestFixture, TestProcessEraseNotInCache) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
// Nothing explodes if we call erase on an uncached process.
process_cache_->EraseProcess(process_start.task_info.pid,
process_start.task_info.start_time);
}
TEST_F(ProcessCacheTestFixture, ImageCacheMissThenHit) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
// The following call will cause the image file to be read and checksummed.
process_cache_->PutFromBpfExec(process_start);
auto before = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 1);
EXPECT_STRCASEEQ(mock_spawns_[kPidChildOfChild].exe_sha256.c_str(),
before[0]->image().sha256().c_str());
// Delete the file image to verify that we're then onward getting cached
// information. Note that the file deletion is a bit of a cheat because
// there's otherwise no externally visible signal for a cache hit. We'll never
// get an exec from BPF for a deleted file.
ASSERT_TRUE(brillo::DeleteFile(base::FilePath(
mock_spawns_[kPidChildOfChild].process_start.image_info.pathname)));
// Make this a "new" process spawn so that we also miss the process cache.
bpf::cros_process_start new_proc_same_image = process_start;
new_proc_same_image.task_info.pid += 1;
new_proc_same_image.task_info.start_time += 1;
process_cache_->PutFromBpfExec(new_proc_same_image);
auto after = process_cache_->GetProcessHierarchy(
new_proc_same_image.task_info.pid,
new_proc_same_image.task_info.start_time, 1);
EXPECT_STRCASEEQ(mock_spawns_[kPidChildOfChild].exe_sha256.c_str(),
after[0]->image().sha256().c_str());
}
TEST_F(ProcessCacheTestFixture, ImageCacheMissDueToModification) {
const bpf::cros_process_start& process_start =
mock_spawns_[kPidChildOfChild].process_start;
// The following call will cause the image file to be read and checksummed.
process_cache_->PutFromBpfExec(process_start);
auto before = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 1);
EXPECT_STRCASEEQ(mock_spawns_[kPidChildOfChild].exe_sha256.c_str(),
before[0]->image().sha256().c_str());
bpf::cros_process_start new_proc_modified_image = process_start;
new_proc_modified_image.task_info.pid += 1;
new_proc_modified_image.task_info.start_time += 1;
// Modify the file and update the mtime as the BPF usually would. Unsure why
// but tmpfs needs some persuasion to update mtime. CrOS by design doesn't run
// any executables from tmpfs.
while ((new_proc_modified_image.image_info.mtime.tv_sec ==
process_start.image_info.mtime.tv_sec) &&
(new_proc_modified_image.image_info.mtime.tv_nsec ==
process_start.image_info.mtime.tv_nsec)) {
ASSERT_TRUE(base::WriteFile(
GetPathInCurrentMountNsOrDie(
new_proc_modified_image.image_info.pid_for_setns,
base::FilePath(new_proc_modified_image.image_info.pathname)),
"This file has been altered"));
ASSERT_NO_FATAL_FAILURE(
FillDynamicImageInfoFromMockFs(&new_proc_modified_image.image_info));
}
ASSERT_EQ(new_proc_modified_image.image_info.inode_device_id,
process_start.image_info.inode_device_id);
ASSERT_EQ(new_proc_modified_image.image_info.inode,
process_start.image_info.inode);
process_cache_->PutFromBpfExec(new_proc_modified_image);
auto after = process_cache_->GetProcessHierarchy(
new_proc_modified_image.task_info.pid,
new_proc_modified_image.task_info.start_time, 1);
EXPECT_NE(mock_spawns_[kPidChildOfChild].exe_sha256,
after[0]->image().sha256());
// # echo -ne "This file has been altered" | sha256sum -
// f55fb515f7ba4ed5e619e266168fde201e16da809f3e71438be84f435a160678 -
EXPECT_STRCASEEQ(
"F55FB515F7BA4ED5E619E266168FDE201E16DA809F3E71438BE84F435A160678",
after[0]->image().sha256().c_str());
}
TEST_F(ProcessCacheTestFixture, ImageCacheHashAFileLargerThanBuf) {
bpf::cros_process_start proc_with_large_image =
mock_spawns_[kPidChildOfChild].process_start;
ASSERT_TRUE(base::WriteFile(
GetPathInCurrentMountNsOrDie(
proc_with_large_image.image_info.pid_for_setns,
base::FilePath(proc_with_large_image.image_info.pathname)),
std::string(9999, '.')));
ASSERT_NO_FATAL_FAILURE(
FillDynamicImageInfoFromMockFs(&proc_with_large_image.image_info));
process_cache_->PutFromBpfExec(proc_with_large_image);
auto actual = process_cache_->GetProcessHierarchy(
proc_with_large_image.task_info.pid,
proc_with_large_image.task_info.start_time, 1);
// # printf '.%.0s' {1..9999} | sha256sum -
// 6c9c6e06f2269516f665541d40859dc514fa7ab87c114c6fdfae4bbdd6a93416 -
EXPECT_STRCASEEQ(
"6C9C6E06F2269516F665541D40859DC514FA7AB87C114C6FDFAE4BBDD6A93416",
actual[0]->image().sha256().c_str());
}
TEST_F(ProcessCacheTestFixture, HashImageInForeignMountNamespace) {
bpf::cros_process_start proc_in_foreign_ns =
mock_spawns_[kPidChildOfContainerInit].process_start;
// Assert that the exe doesn't exist in the init namespace.
ASSERT_FALSE(base::PathExists(GetPathInCurrentMountNsOrDie(
kPidInit, base::FilePath(proc_in_foreign_ns.image_info.pathname))));
process_cache_->PutFromBpfExec(proc_in_foreign_ns);
auto actual = process_cache_->GetProcessHierarchy(
proc_in_foreign_ns.task_info.pid, proc_in_foreign_ns.task_info.start_time,
2);
EXPECT_EQ(2, actual.size());
mock_spawns_[kPidChildOfContainerInit].expected_proto.set_process_uuid(
actual[0]->process_uuid());
for (auto& proc : actual) {
EXPECT_TRUE(proc->has_meta_first_appearance());
EXPECT_TRUE(proc->meta_first_appearance());
proc->clear_meta_first_appearance();
}
EXPECT_THAT(mock_spawns_[kPidChildOfContainerInit].expected_proto,
EqualsProto(*actual[0]));
ExpectPartialMatch(mock_procfs_[kPidContainerInit].expected_proto,
*actual[1]);
}
TEST_F(ProcessCacheTestFixture, TestPutFromBpfRedaction) {
bpf::cros_process_start process_start = {.task_info{
.pid = 123,
.start_time = 5029384029,
.commandline = "'start' 'arc-sdcard-mount' "
"'CHROMEOS_USER=username@email.com' 'CONTAINER_PID=4704'",
.commandline_len = 82}};
std::list<std::string> redacted_usernames;
redacted_usernames.push_front(kUsername);
EXPECT_CALL(*device_user_, GetUsernamesForRedaction)
.WillOnce(Return(redacted_usernames));
process_cache_->PutFromBpfExec(process_start);
auto actual = process_cache_->GetProcessHierarchy(
process_start.task_info.pid, process_start.task_info.start_time, 1);
EXPECT_EQ(
"''start' 'arc-sdcard-mount' "
"'CHROMEOS_USER=(EMAIL_REDACTED)' 'CONTAINER_PID=4704''",
actual[0]->commandline());
}
TEST_F(ProcessCacheTestFixture, TestScrapeFromProcFsRedaction) {
std::list<std::string> redacted_usernames;
redacted_usernames.push_front(kUsername);
redacted_usernames.push_front("random string");
EXPECT_CALL(*device_user_, GetUsernamesForRedaction)
.WillOnce(Return(redacted_usernames));
auto process = mock_procfs_[kPidChildOfInit];
process.cmdline = "\tusername@email.com\t !username@email.com!";
auto file_path = base::FilePath(fake_root_.GetPath().value() + "/proc/" +
std::to_string(kPidChildOfInit));
ASSERT_TRUE(base::WriteFile(file_path.Append("cmdline"),
process.cmdline.c_str(), process.cmdline.size()));
auto actual = process_cache_->GetProcessHierarchy(kPidChildOfInit,
process.starttime_ns, 1);
EXPECT_EQ("'\t(EMAIL_REDACTED)\t !(EMAIL_REDACTED)!'",
actual[0]->commandline());
}
} // namespace secagentd::testing