| // 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 <algorithm> |
| #include <cinttypes> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "base/containers/lru_cache.h" |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/hash/md5.h" |
| #include "base/logging.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece_forward.h" |
| #include "base/strings/string_tokenizer.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/lock.h" |
| #include "openssl/sha.h" |
| #include "re2/re2.h" |
| #include "secagentd/bpf/process.h" |
| #include "secagentd/proto/security_xdr_events.pb.h" |
| |
| namespace { |
| |
| namespace bpf = secagentd::bpf; |
| namespace pb = cros_xdr::reporting; |
| using secagentd::ProcessCache; |
| |
| static const char kErrorFailedToStat[] = "Failed to stat "; |
| static const char kErrorFailedToResolve[] = "Failed to resolve "; |
| static const char kErrorFailedToRead[] = "Failed to read "; |
| static const char kErrorFailedToParse[] = "Failed to parse "; |
| static const char kErrorSslSha[] = "SSL SHA error"; |
| |
| std::string StableUuid(ProcessCache::InternalProcessKeyType seed) { |
| base::MD5Digest md5; |
| base::MD5Sum(&seed, sizeof(seed), &md5); |
| // Convert the hash to a UUID string. Pretend to be version 4, variant 1. |
| md5.a[4] = (md5.a[4] & 0x0f) | 0x40; |
| md5.a[6] = (md5.a[6] & 0x3f) | 0x80; |
| return base::StringPrintf( |
| "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", |
| md5.a[0], md5.a[1], md5.a[2], md5.a[3], md5.a[4], md5.a[5], md5.a[6], |
| md5.a[7], md5.a[8], md5.a[9], md5.a[10], md5.a[11], md5.a[12], md5.a[13], |
| md5.a[14], md5.a[15]); |
| } |
| |
| // Kernel arg and env lists use '\0' to delimit elements. Tokenize the string |
| // and use single quotes (') to designate atomic elements. |
| // bufsize is the total capacity of buf (used for bounds checking). |
| // payload_len is the length of actual payload including the final '\0'. |
| std::string SafeTransformArgvEnvp(const char* buf, |
| size_t bufsize, |
| size_t payload_len) { |
| std::string str; |
| if (payload_len <= 0 || payload_len > bufsize) { |
| return str; |
| } |
| base::CStringTokenizer t(buf, buf + payload_len, std::string("\0", 1)); |
| while (t.GetNext()) { |
| str.append(base::StringPrintf("'%s' ", t.token().c_str())); |
| } |
| if (str.length() > 0) { |
| str.pop_back(); |
| } |
| return str; |
| } |
| |
| // Fills a FileImage proto with contents from bpf image_info. |
| void FillImageFromBpf(const bpf::cros_image_info& image_info, |
| pb::FileImage* file_image_proto) { |
| file_image_proto->set_pathname(std::string(image_info.pathname)); |
| file_image_proto->set_mnt_ns(image_info.mnt_ns); |
| file_image_proto->set_inode_device_id(image_info.inode_device_id); |
| file_image_proto->set_inode(image_info.inode); |
| file_image_proto->set_canonical_uid(image_info.uid); |
| file_image_proto->set_canonical_gid(image_info.gid); |
| file_image_proto->set_mode(image_info.mode); |
| } |
| |
| void FillProcessFromBpf(const bpf::cros_process_start& process_start, |
| pb::Process* process_proto) { |
| ProcessCache::PartiallyFillProcessFromBpfTaskInfo(process_start.task_info, |
| process_proto); |
| FillImageFromBpf(process_start.image_info, process_proto->mutable_image()); |
| } |
| |
| absl::Status GetNsFromPath(const base::FilePath& ns_symlink_path, |
| uint64_t* ns) { |
| // mnt_ns_symlink is not actually pathlike. E.g: "mnt:[4026531840]". |
| constexpr char kMntNsPattern[] = R"(mnt:\[(\d+)\])"; |
| static const LazyRE2 kMntNsRe = {kMntNsPattern}; |
| base::FilePath ns_symlink; |
| if (!base::ReadSymbolicLink(ns_symlink_path, &ns_symlink)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToResolve, ns_symlink_path.value()})); |
| } |
| if (!RE2::FullMatch(ns_symlink.value(), *kMntNsRe, ns)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToParse, ns_symlink.value()})); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status GetStatFromProcfs(const base::FilePath& stat_path, |
| uint64_t* ppid, |
| uint64_t* starttime_t, |
| std::string* set_comm_if_kthread) { |
| std::string proc_stat_contents; |
| if (!base::ReadFileToString(stat_path, &proc_stat_contents)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToRead, stat_path.value()})); |
| } |
| |
| // See https://man7.org/linux/man-pages/man5/proc.5.html for |
| // /proc/[pid]/stat format. All tokens are delimited with a whitespace. One |
| // major caveat is that comm (field 2) token may have an embedded whitespace |
| // and is so delimited by parentheses. The token may also have embedded |
| // parentheses though so we just ignore everything until the final ')'. |
| // StringTokenizer::set_quote_chars does not help with this. It accepts |
| // multiple quote chars but does not work for asymmetric quoting. |
| size_t end_of_comm = proc_stat_contents.rfind(')'); |
| if (end_of_comm == std::string::npos) { |
| return absl::OutOfRangeError( |
| base::StrCat({kErrorFailedToParse, stat_path.value()})); |
| } |
| base::StringTokenizer t(proc_stat_contents.begin() + end_of_comm, |
| proc_stat_contents.end(), " "); |
| // We could avoid a separate loop here but the tokenizer API is awkward for |
| // random access. |
| std::vector<base::StringPiece> stat_tokens; |
| while (t.GetNext()) { |
| stat_tokens.push_back(t.token_piece()); |
| } |
| |
| // We need the following fields (1-indexed in man page): |
| // (4) ppid %d |
| // (9) flags %u |
| // (22) starttime %llu |
| // And remember that we started tokenizing at (2) comm. |
| static const size_t kPpidField = 2; |
| static const size_t kFlagsField = 7; |
| static const size_t kStarttimeField = 20; |
| uint32_t flags; |
| if ((stat_tokens.size() <= kStarttimeField) || |
| (!base::StringToUint64(stat_tokens[kPpidField], ppid)) || |
| (!base::StringToUint(stat_tokens[kFlagsField], &flags)) || |
| (!base::StringToUint64(stat_tokens[kStarttimeField], starttime_t))) { |
| return absl::OutOfRangeError( |
| base::StrCat({kErrorFailedToParse, stat_path.value()})); |
| } |
| constexpr uint32_t kPfKthread = 0x00200000; // Defined in linux/sched.h. |
| if (flags & kPfKthread) { |
| size_t start_of_comm = proc_stat_contents.find('('); |
| if (start_of_comm != std::string::npos && |
| (start_of_comm + 1 <= end_of_comm)) { |
| *set_comm_if_kthread = base::StrCat( |
| {"[", |
| base::MakeStringPiece(proc_stat_contents.begin() + start_of_comm + 1, |
| proc_stat_contents.begin() + end_of_comm), |
| "]"}); |
| } |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<std::string> GenerateImageHash( |
| const base::FilePath& image_path_in_ns) { |
| base::File image(image_path_in_ns, |
| base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (!image.IsValid()) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToRead, image_path_in_ns.value()})); |
| } |
| SHA256_CTX ctx; |
| if (!SHA256_Init(&ctx)) { |
| return absl::InternalError(kErrorSslSha); |
| } |
| std::array<char, 4096> buf; |
| int bytes_read = 0; |
| while ((bytes_read = image.ReadAtCurrentPos(buf.data(), buf.size())) > 0) { |
| if (!SHA256_Update(&ctx, buf.data(), bytes_read)) { |
| return absl::InternalError(kErrorSslSha); |
| } |
| } |
| if (bytes_read < 0) { |
| return absl::AbortedError( |
| base::StrCat({kErrorFailedToRead, image_path_in_ns.value()})); |
| } |
| static_assert(sizeof(buf) >= SHA256_DIGEST_LENGTH); |
| if (!SHA256_Final(reinterpret_cast<unsigned char*>(buf.data()), &ctx)) { |
| return absl::InternalError(kErrorSslSha); |
| } |
| return base::HexEncode(buf.data(), SHA256_DIGEST_LENGTH); |
| } |
| |
| absl::StatusOr<ProcessCache::InternalImageValueType> |
| VerifyStatAndGenerateImageHash( |
| const ProcessCache::InternalImageKeyType& image_key, |
| const base::FilePath& image_path_in_ns) { |
| auto hash = GenerateImageHash(image_path_in_ns); |
| if (!hash.ok()) { |
| return hash.status(); |
| } |
| base::stat_wrapper_t image_stat; |
| if (base::File::Stat(image_path_in_ns.value().c_str(), &image_stat) || |
| (image_stat.st_dev != image_key.inode_device_id) || |
| (image_stat.st_ino != image_key.inode) || |
| (image_stat.st_mtim.tv_sec != image_key.mtime.tv_sec) || |
| (image_stat.st_mtim.tv_nsec != image_key.mtime.tv_nsec) || |
| (image_stat.st_ctim.tv_sec != image_key.ctime.tv_sec) || |
| (image_stat.st_ctim.tv_nsec != image_key.ctime.tv_nsec)) { |
| return absl::NotFoundError( |
| base::StrCat({"Failed to match stat of image hashed at ", |
| image_path_in_ns.value()})); |
| } |
| return ProcessCache::InternalImageValueType{.sha256 = hash.value()}; |
| } |
| |
| // Safely initialized and trivially destructible static value of SC_CLK_TCK. |
| const uint64_t GetScClockTck() { |
| static const uint64_t kScClockTck = sysconf(_SC_CLK_TCK); |
| return kScClockTck; |
| } |
| |
| } // namespace |
| |
| namespace secagentd { |
| |
| constexpr ProcessCache::InternalProcessCacheType::size_type |
| kProcessCacheMaxSize = 256; |
| constexpr ProcessCache::InternalImageCacheType::size_type kImageCacheMaxSize = |
| 256; |
| |
| uint64_t ProcessCache::LossyNsecToClockT(bpf::time_ns_t ns) { |
| static constexpr uint64_t kNsecPerSec = 1000000000; |
| const uint64_t sc_clock_tck = GetScClockTck(); |
| // Copied from the kernel procfs code though we unfortunately cannot use |
| // ifdefs and need to do comparisons live. |
| if ((kNsecPerSec % sc_clock_tck) == 0) { |
| return ns / (kNsecPerSec / sc_clock_tck); |
| } else if ((sc_clock_tck % 512) == 0) { |
| return (ns * sc_clock_tck / 512) / (kNsecPerSec / 512); |
| } else { |
| return (ns * 9) / |
| ((9ull * kNsecPerSec + (sc_clock_tck / 2)) / sc_clock_tck); |
| } |
| } |
| |
| // Converts clock_t to seconds. |
| int64_t ProcessCache::ClockTToSeconds(uint64_t clock_t) { |
| return clock_t / GetScClockTck(); |
| } |
| |
| void ProcessCache::PartiallyFillProcessFromBpfTaskInfo( |
| const bpf::cros_process_task_info& task_info, pb::Process* process_proto) { |
| ProcessCache::InternalProcessKeyType key{ |
| LossyNsecToClockT(task_info.start_time), task_info.pid}; |
| process_proto->set_process_uuid(StableUuid(key)); |
| process_proto->set_canonical_pid(task_info.pid); |
| process_proto->set_canonical_uid(task_info.uid); |
| process_proto->set_commandline(SafeTransformArgvEnvp( |
| task_info.commandline, sizeof(task_info.commandline), |
| task_info.commandline_len)); |
| process_proto->set_rel_start_time_s(ClockTToSeconds(key.start_time_t)); |
| } |
| |
| ProcessCache::ProcessCache(const base::FilePath& root_path) |
| : process_cache_( |
| std::make_unique<InternalProcessCacheType>(kProcessCacheMaxSize)), |
| image_cache_( |
| std::make_unique<InternalImageCacheType>(kImageCacheMaxSize)), |
| root_path_(root_path) {} |
| |
| ProcessCache::ProcessCache() : ProcessCache(base::FilePath("/")) {} |
| |
| void ProcessCache::PutFromBpfExec( |
| const bpf::cros_process_start& process_start) { |
| InternalProcessKeyType key{ |
| LossyNsecToClockT(process_start.task_info.start_time), |
| process_start.task_info.pid}; |
| auto process_proto = std::make_unique<pb::Process>(); |
| FillProcessFromBpf(process_start, process_proto.get()); |
| InternalProcessKeyType parent_key{ |
| LossyNsecToClockT(process_start.task_info.parent_start_time), |
| process_start.task_info.ppid}; |
| InternalImageKeyType image_key{ |
| process_start.image_info.inode_device_id, process_start.image_info.inode, |
| process_start.image_info.mtime, process_start.image_info.ctime}; |
| { |
| base::AutoLock cache_lock(image_cache_lock_); |
| auto it = InclusiveGetImage( |
| image_key, base::FilePath(process_start.image_info.pathname)); |
| if (it != image_cache_->end()) { |
| process_proto->mutable_image()->set_sha256(it->second.sha256); |
| } |
| } |
| base::AutoLock lock(process_cache_lock_); |
| process_cache_->Put( |
| key, InternalProcessValueType({std::move(process_proto), parent_key})); |
| } |
| |
| void ProcessCache::EraseProcess(uint64_t pid, bpf::time_ns_t start_time_ns) { |
| InternalProcessKeyType key{LossyNsecToClockT(start_time_ns), pid}; |
| base::AutoLock lock(process_cache_lock_); |
| auto it = process_cache_->Peek(key); |
| if (it != process_cache_->end()) { |
| process_cache_->Erase(it); |
| } |
| } |
| |
| ProcessCache::InternalProcessCacheType::const_iterator |
| ProcessCache::InclusiveGetProcess(const InternalProcessKeyType& key) { |
| process_cache_lock_.AssertAcquired(); |
| // PID 0 doesn't exist and is also used to signify the end of the process |
| // "linked list". |
| if (key.pid == 0) { |
| return process_cache_->end(); |
| } |
| auto it = process_cache_->Get(key); |
| if (it != process_cache_->end()) { |
| return it; |
| } |
| |
| absl::StatusOr<InternalProcessValueType> statusor; |
| { |
| base::AutoUnlock unlock(process_cache_lock_); |
| statusor = MakeFromProcfs(key); |
| if (!statusor.ok()) { |
| LOG(ERROR) << statusor.status(); |
| return process_cache_->end(); |
| } |
| } |
| |
| it = process_cache_->Put(key, std::move(*statusor)); |
| return it; |
| } |
| |
| ProcessCache::InternalImageCacheType::const_iterator |
| ProcessCache::InclusiveGetImage(const InternalImageKeyType& image_key, |
| const base::FilePath& image_path_in_ns) { |
| image_cache_lock_.AssertAcquired(); |
| auto it = image_cache_->Get(image_key); |
| if (it != image_cache_->end()) { |
| if (it->first.mtime.tv_sec == 0 || it->first.ctime.tv_sec == 0) { |
| // Invalidate entry and force checksum if its cached ctime or mtime seems |
| // missing. |
| image_cache_->Erase(it); |
| it = image_cache_->end(); |
| } else { |
| return it; |
| } |
| } |
| |
| absl::StatusOr<InternalImageValueType> statusor; |
| { |
| base::AutoUnlock unlock(image_cache_lock_); |
| // TODO(b/253661187): nsenter the process' mount namespace for correctness. |
| statusor = VerifyStatAndGenerateImageHash(image_key, image_path_in_ns); |
| if (!statusor.ok()) { |
| LOG(ERROR) << statusor.status(); |
| return image_cache_->end(); |
| } |
| } |
| |
| it = image_cache_->Put(image_key, std::move(*statusor)); |
| return it; |
| } |
| |
| std::vector<std::unique_ptr<pb::Process>> ProcessCache::GetProcessHierarchy( |
| uint64_t pid, bpf::time_ns_t start_time_ns, int num_generations) { |
| std::vector<std::unique_ptr<pb::Process>> processes; |
| InternalProcessKeyType lookup_key{LossyNsecToClockT(start_time_ns), pid}; |
| base::AutoLock lock(process_cache_lock_); |
| for (int i = 0; i < num_generations; ++i) { |
| auto it = InclusiveGetProcess(lookup_key); |
| if (it != process_cache_->end()) { |
| auto process_proto = std::make_unique<pb::Process>(); |
| process_proto->CopyFrom(*it->second.process_proto); |
| processes.push_back(std::move(process_proto)); |
| lookup_key = it->second.parent_key; |
| } else { |
| // Process no longer exists or we've reached init. Break and best-effort |
| // return what we were able to retrieve. |
| break; |
| } |
| } |
| return processes; |
| } |
| |
| absl::StatusOr<ProcessCache::InternalProcessValueType> |
| ProcessCache::MakeFromProcfs(const ProcessCache::InternalProcessKeyType& key) { |
| InternalProcessKeyType parent_key; |
| auto process_proto = std::make_unique<pb::Process>(); |
| process_proto->set_canonical_pid(key.pid); |
| process_proto->set_process_uuid(StableUuid(key)); |
| process_proto->set_rel_start_time_s(ClockTToSeconds(key.start_time_t)); |
| |
| const base::FilePath proc_pid_dir = |
| root_path_.Append(base::StringPrintf("proc/%" PRIu64, key.pid)); |
| base::stat_wrapper_t pid_dir_stat; |
| if (base::File::Stat(proc_pid_dir.value().c_str(), &pid_dir_stat)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToStat, proc_pid_dir.value()})); |
| } |
| process_proto->set_canonical_uid(pid_dir_stat.st_uid); |
| |
| const base::FilePath exe_symlink_path = proc_pid_dir.Append("exe"); |
| base::FilePath exe_path; |
| if (base::ReadSymbolicLink(exe_symlink_path, &exe_path)) { |
| // TODO(b/253661187): nsenter the process' mount namespace for correctness. |
| base::stat_wrapper_t exe_stat; |
| if (base::File::Stat(exe_path.value().c_str(), &exe_stat)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToStat, exe_path.value()})); |
| } |
| |
| auto image_proto = process_proto->mutable_image(); |
| const base::FilePath mnt_ns_symlink_path = |
| proc_pid_dir.Append("ns").Append("mnt"); |
| uint64_t mnt_ns; |
| auto status = GetNsFromPath(mnt_ns_symlink_path, &mnt_ns); |
| if (!status.ok()) { |
| return status; |
| } |
| image_proto->set_pathname(exe_path.value()); |
| image_proto->set_mnt_ns(mnt_ns); |
| image_proto->set_inode_device_id(exe_stat.st_dev); |
| image_proto->set_inode(exe_stat.st_ino); |
| image_proto->set_canonical_uid(exe_stat.st_uid); |
| image_proto->set_canonical_gid(exe_stat.st_gid); |
| image_proto->set_mode(exe_stat.st_mode); |
| |
| InternalImageKeyType image_key{ |
| exe_stat.st_dev, |
| exe_stat.st_ino, |
| {exe_stat.st_mtim.tv_sec, exe_stat.st_mtim.tv_nsec}, |
| {exe_stat.st_ctim.tv_sec, exe_stat.st_ctim.tv_nsec}}; |
| { |
| base::AutoLock lock(image_cache_lock_); |
| auto it = InclusiveGetImage(image_key, exe_path); |
| if (it != image_cache_->end()) { |
| process_proto->mutable_image()->set_sha256(it->second.sha256); |
| } |
| } |
| } // Else we're likely processing a kthread and there's no image to report. |
| |
| const base::FilePath cmdline_path = proc_pid_dir.Append("cmdline"); |
| std::string cmdline_contents; |
| if (!base::ReadFileToString(cmdline_path, &cmdline_contents)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToRead, cmdline_path.value()})); |
| } |
| process_proto->set_commandline( |
| SafeTransformArgvEnvp(cmdline_contents.c_str(), cmdline_contents.size(), |
| cmdline_contents.size())); |
| |
| // This must be the last file that we read for this process because process |
| // starttime is used as a key against pid reuse. |
| const base::FilePath stat_path = proc_pid_dir.Append("stat"); |
| uint64_t procfs_start_time_t; |
| // mutable_commandline is already empty if this process is a kthread. So put |
| // in the comm instead. |
| auto status = |
| GetStatFromProcfs(stat_path, &parent_key.pid, &procfs_start_time_t, |
| process_proto->mutable_commandline()); |
| if (!status.ok()) { |
| return status; |
| } |
| |
| // TODO(b/254291026): Incoming ns is currently not derived using |
| // timens_add_boottime_ns. |
| if (key.start_time_t != procfs_start_time_t) { |
| return absl::AbortedError( |
| base::StringPrintf("Detected PID reuse on %" PRIu64 |
| " (want time %" PRIu64 ", got time %" PRIu64 ")", |
| key.pid, key.start_time_t, procfs_start_time_t)); |
| } |
| |
| // parent_key.pid is filled in by this point but we also need start_time. |
| // parent_key.pid == 0 implies current process is init or a kthread. No need |
| // to traverse further. |
| if (parent_key.pid != 0) { |
| const base::FilePath parent_stat_path = root_path_.Append( |
| base::StringPrintf("proc/%" PRIu64 "/stat", parent_key.pid)); |
| uint64_t unused_ppid; |
| std::string unused_comm; |
| status = GetStatFromProcfs(parent_stat_path, &unused_ppid, |
| &parent_key.start_time_t, &unused_comm); |
| if (!status.ok() || key.start_time_t < parent_key.start_time_t) { |
| LOG(WARNING) << "Failed to establish parent linkage for PID " << key.pid; |
| // Signifies end of our "linked list". |
| parent_key.pid = 0; |
| } |
| } |
| return InternalProcessValueType{std::move(process_proto), parent_key}; |
| } |
| |
| bool ProcessCache::IsEventFiltered( |
| const cros_xdr::reporting::XdrProcessEvent& event) { |
| const cros_xdr::reporting::Process* process = nullptr; |
| const cros_xdr::reporting::Process* parent_process = nullptr; |
| if (event.has_process_exec()) { |
| parent_process = event.process_exec().has_process() |
| ? &event.process_exec().process() |
| : nullptr; |
| process = event.process_exec().has_spawn_process() |
| ? &event.process_exec().spawn_process() |
| : nullptr; |
| } else if (event.has_process_terminate()) { |
| parent_process = event.process_terminate().has_parent_process() |
| ? &event.process_terminate().parent_process() |
| : nullptr; |
| process = event.process_terminate().has_process() |
| ? &event.process_terminate().process() |
| : nullptr; |
| } |
| const auto& parent_filter = filter_rules_parent_; |
| const auto& image_filter = filter_rules_process_; |
| const auto& should_filter = [](const cros_xdr::reporting::Process& p, |
| const InternalFilterRuleSetType& filters, |
| const std::string& type) -> bool { |
| const auto& matching_filter = filters.find(p.image().sha256()); |
| if (matching_filter == filters.end()) { |
| return false; |
| } |
| if (matching_filter->second.commandline.empty()) { |
| // Commands match and there is no shell script to match. |
| return true; |
| } |
| for (const auto& commandline : matching_filter->second.commandline) { |
| if (p.commandline() == commandline) { |
| // Exact commandline match. |
| return true; |
| } |
| } |
| // Commands match but no matching shell script. |
| return false; |
| }; |
| |
| if (parent_process && |
| should_filter(*parent_process, parent_filter, "parent_process")) { |
| return true; |
| } |
| if (process && should_filter(*process, image_filter, "process")) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void ProcessCache::InitializeFilter(bool underscorify) { |
| // Image pathnames are adjusted by root_path_ for testing. Also they need |
| // to be underscorified for the unit test framework to function correctly. |
| |
| // Since shell scripts just look at commandline they don't need to |
| // be underscorified or adjusted by root_path_ for testing. |
| |
| std::vector<InternalFilterRule> parent_filter_seeds = { |
| // Shell rules |
| // TODO(b:267391331): make temp logger into a real application. |
| { |
| .image_pathname = "bin/sh", |
| .commandline = |
| {"'/bin/sh' '/usr/share/cros/init/temp_logger.sh'", |
| "'/bin/sh' '/usr/local/libexec/recover-duts/recover_duts'", |
| "'/bin/sh' " |
| "'/usr/local/libexec/recover-duts/hooks/check_ethernet.hook'"}, |
| }}; |
| |
| std::vector<InternalFilterRule> process_filter_seeds = { |
| // Command rules |
| // TODO(b:267391049): We think this is being execve by some base library |
| // to determine how much space is left on the system. This spams the event |
| // logs so we add a filter. The base library should really be fixed. |
| {.image_pathname = "usr/sbin/spaced_cli"}}; |
| |
| std::vector< |
| std::pair<InternalFilterRuleSetType&, std::vector<InternalFilterRule>&>> |
| filter_seeds = {{filter_rules_parent_, parent_filter_seeds}, |
| {filter_rules_process_, process_filter_seeds}}; |
| |
| for (auto& v : filter_seeds) { |
| for (auto& k : v.second) { |
| if (underscorify) { |
| std::replace(k.image_pathname.begin(), k.image_pathname.end(), '/', |
| '_'); |
| } |
| k.image_pathname = root_path_.Append(k.image_pathname).value(); |
| auto result = GenerateImageHash(base::FilePath(k.image_pathname)); |
| if (!result.ok()) { |
| LOG(ERROR) << "XdrProcessEvent filter failed to create rule for " |
| << "image_path_name:" << k.image_pathname |
| << " error:" << result.status(); |
| continue; |
| } |
| |
| v.first.emplace(std::make_pair(result.value(), std::move(k))); |
| } |
| } |
| LOG(INFO) << "Process filter rules created:"; |
| for (const auto& key : filter_rules_parent_) { |
| LOG(INFO) << "PARENT: SHA256:" << key.first |
| << " pathname:" << key.second.image_pathname; |
| if (!key.second.commandline.empty()) |
| LOG(INFO) << "Commands:"; |
| for (auto commandline : key.second.commandline) { |
| LOG(INFO) << commandline; |
| } |
| } |
| |
| for (const auto& key : filter_rules_process_) { |
| LOG(INFO) << "PROCESS: SHA256:" << key.first |
| << " pathname:" << key.second.image_pathname; |
| if (!key.second.commandline.empty()) |
| LOG(INFO) << "Commands:"; |
| for (auto commandline : key.second.commandline) { |
| LOG(INFO) << commandline; |
| } |
| } |
| } |
| } // namespace secagentd |