| // 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 <cstdint> |
| #include <list> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_replace.h" |
| #include "base/containers/lru_cache.h" |
| #include "base/containers/span.h" |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/hash/md5.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_tokenizer.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/lock.h" |
| #include "re2/re2.h" |
| #include "secagentd/bpf/bpf_types.h" |
| #include "secagentd/device_user.h" |
| #include "secagentd/metrics_sender.h" |
| #include "secagentd/proto/security_xdr_events.pb.h" |
| |
| namespace { |
| |
| namespace bpf = secagentd::bpf; |
| namespace pb = cros_xdr::reporting; |
| using ProcessCache = 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 kRedactMessage[] = "(EMAIL_REDACTED)"; |
| |
| std::string StableUuid(ProcessCache::InternalProcessKeyType seed) { |
| base::MD5Digest md5; |
| base::MD5Sum(base::byte_span_from_ref(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; |
| } |
| |
| 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()})); |
| } |
| std::string_view proc_stat = proc_stat_contents; |
| |
| // 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.rfind(')'); |
| if (end_of_comm == std::string_view::npos) { |
| return absl::OutOfRangeError( |
| base::StrCat({kErrorFailedToParse, stat_path.value()})); |
| } |
| |
| // The last ')' in comm is included as well. |
| std::string_view proc_stat_after_comm = proc_stat.substr(end_of_comm); |
| base::StringViewTokenizer t(proc_stat_after_comm, " "); |
| // We could avoid a separate loop here but the tokenizer API is awkward for |
| // random access. |
| std::vector<std::string_view> 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.find('('); |
| if (start_of_comm != std::string_view::npos && |
| (start_of_comm + 1 <= end_of_comm)) { |
| *set_comm_if_kthread = base::StrCat( |
| {"[", |
| proc_stat.substr(start_of_comm + 1, end_of_comm - start_of_comm - 1), |
| "]"}); |
| } |
| } |
| return absl::OkStatus(); |
| } |
| |
| // 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; |
| } |
| |
| void RedactCommandline(std::string* commandline, |
| const std::list<std::string>& redacted_usernames) { |
| // Since we are redacting usernames that always contain the @ symbol, if the |
| // commandline does NOT contain one do not parse for usernames. |
| if (commandline->empty() || commandline->find("@") == std::string::npos) { |
| return; |
| } |
| |
| int pos = 0; |
| for (auto username : redacted_usernames) { |
| if (!username.empty()) { |
| int result = |
| absl::StrReplaceAll({{username, kRedactMessage}}, commandline); |
| if (result != 0) { |
| // Sends which position the redacted username was found. Should always |
| // be <= 2, but goes up to 5 to be safe. |
| secagentd::MetricsSender::GetInstance().SendLinearMetricToUMA( |
| secagentd::metrics::kRedaction, |
| std::min(pos, secagentd::metrics::kRedactionBucketCount - 1), |
| secagentd::metrics::kRedactionBucketCount); |
| } |
| } |
| pos++; |
| } |
| } |
| |
| // 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); |
| } |
| } // namespace |
| |
| namespace secagentd { |
| |
| constexpr ProcessCache::InternalProcessCacheType::size_type |
| kProcessCacheMaxSize = 281; |
| |
| 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, |
| const std::list<std::string>& redacted_usernames) { |
| 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); |
| std::string cmdline = SafeTransformArgvEnvp(task_info.commandline, |
| sizeof(task_info.commandline), |
| task_info.commandline_len); |
| RedactCommandline(&cmdline, redacted_usernames); |
| process_proto->set_commandline(cmdline); |
| process_proto->set_rel_start_time_s(ClockTToSeconds(key.start_time_t)); |
| } |
| |
| ProcessCache::ProcessCache(const base::FilePath& root_path, |
| scoped_refptr<DeviceUserInterface> device_user, |
| scoped_refptr<ImageCacheInterface> image_cache) |
| : weak_ptr_factory_(this), |
| process_cache_( |
| std::make_unique<InternalProcessCacheType>(kProcessCacheMaxSize)), |
| device_user_(device_user), |
| root_path_(root_path), |
| earliest_seen_exec_rel_s_(INT64_MAX), |
| image_cache_(image_cache) {} |
| |
| ProcessCache::ProcessCache(scoped_refptr<DeviceUserInterface> device_user) |
| : ProcessCache(base::FilePath("/"), |
| device_user, |
| base::MakeRefCounted<ImageCache>()) {} |
| |
| void ProcessCache::FillProcessFromBpf( |
| const bpf::cros_process_task_info& task_info, |
| const bpf::cros_image_info& image_info, |
| pb::Process* process_proto, |
| const std::list<std::string>& redacted_usernames) { |
| ProcessCache::PartiallyFillProcessFromBpfTaskInfo(task_info, process_proto, |
| redacted_usernames); |
| FillImageFromBpf(image_info, process_proto->mutable_image()); |
| |
| process_proto->set_meta_first_appearance(process_proto->rel_start_time_s() <= |
| earliest_seen_exec_rel_s_); |
| ImageCacheInterface::ImageCacheKeyType image_key{ |
| image_info.inode_device_id, image_info.inode, image_info.mtime, |
| image_info.ctime}; |
| { |
| auto result = image_cache_->InclusiveGetImage( |
| image_key, true, image_info.pid_for_setns, |
| base::FilePath(image_info.pathname)); |
| if (result.ok()) { |
| process_proto->mutable_image()->set_sha256(result.value().sha256); |
| } |
| } |
| } |
| |
| void ProcessCache::PutFromBpfExec( |
| const bpf::cros_process_start& process_start) { |
| // Starts reporting of the cache fullness metric. |
| static bool started_reporting_cache_fullness = false; |
| if (!started_reporting_cache_fullness) { |
| MetricsSender::GetInstance().RegisterMetricOnFlushCallback( |
| base::BindRepeating(&ProcessCache::SendPolledMetrics, |
| weak_ptr_factory_.GetWeakPtr())); |
| started_reporting_cache_fullness = true; |
| } |
| |
| 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.task_info, process_start.image_info, |
| process_proto.get(), |
| device_user_->GetUsernamesForRedaction()); |
| InternalProcessKeyType parent_key{ |
| LossyNsecToClockT(process_start.task_info.parent_start_time), |
| process_start.task_info.ppid}; |
| // Execs from eBPF are always new processes. |
| process_proto->set_meta_first_appearance(true); |
| if (earliest_seen_exec_rel_s_ > process_proto->rel_start_time_s()) { |
| earliest_seen_exec_rel_s_ = process_proto->rel_start_time_s(); |
| LOG(INFO) << "Set first seen process exec time to " |
| << earliest_seen_exec_rel_s_; |
| } |
| |
| 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); |
| } |
| } |
| |
| std::pair<ProcessCache::InternalProcessCacheType::iterator, metrics::Cache> |
| 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) { |
| // Metric will not be logged. |
| return std::make_pair(process_cache_->end(), metrics::Cache(-1)); |
| } |
| auto it = process_cache_->Get(key); |
| if (it != process_cache_->end()) { |
| return std::make_pair(it, metrics::Cache::kCacheHit); |
| } |
| |
| absl::StatusOr<InternalProcessValueType> statusor; |
| { |
| base::AutoUnlock unlock(process_cache_lock_); |
| statusor = MakeFromProcfs(key); |
| if (!statusor.ok()) { |
| LOG(ERROR) << statusor.status(); |
| return std::make_pair(process_cache_->end(), metrics::Cache::kCacheMiss); |
| } |
| } |
| it = process_cache_->Put(key, std::move(*statusor)); |
| return std::make_pair(it, metrics::Cache::kProcfsFilled); |
| } |
| |
| 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 pair = InclusiveGetProcess(lookup_key); |
| auto it = pair.first; |
| if (lookup_key.pid != 0) { |
| MetricsSender::GetInstance().IncrementBatchedMetric(metrics::kCache, |
| pair.second); |
| } |
| 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)); |
| if (it->second.process_proto->meta_first_appearance()) { |
| it->second.process_proto->set_meta_first_appearance(false); |
| } |
| 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, &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 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()})); |
| } |
| std::string cmdline = |
| SafeTransformArgvEnvp(cmdline_contents.c_str(), cmdline_contents.size(), |
| cmdline_contents.size()); |
| RedactCommandline(&cmdline, device_user_->GetUsernamesForRedaction()); |
| process_proto->set_commandline(cmdline); |
| |
| auto status = FillImageFromProcfs(proc_pid_dir, key.pid, |
| process_proto->mutable_image()); |
| if (!status.ok()) { |
| // It's okay if we don't get the image. Report everything else. |
| LOG(ERROR) << "Failed to fill process image info from procfs " |
| << status.ToString(); |
| } |
| |
| // 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. |
| 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; |
| } |
| } |
| |
| // Heuristically determine if the scraped process would have been seen before. |
| // False positives are expected and acceptable. |
| process_proto->set_meta_first_appearance(process_proto->rel_start_time_s() <= |
| earliest_seen_exec_rel_s_); |
| |
| return InternalProcessValueType{std::move(process_proto), parent_key}; |
| } |
| |
| bool ProcessCache::IsEventFiltered( |
| const cros_xdr::reporting::Process* parent_process, |
| const cros_xdr::reporting::Process* process) { |
| 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::SendPolledMetrics() { |
| MetricsSender::GetInstance().SendPercentageMetricToUMA( |
| metrics::kCacheFullness, |
| trunc(100 * (static_cast<double>(process_cache_->size()) / |
| static_cast<double>(process_cache_->max_size())))); |
| } |
| |
| 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"}, |
| // TODO(b:274925855): dmsetup is called at 1 Hz by spaced. Evaluate |
| // if spaced needs to call it that often. |
| {.image_pathname = "sbin/dmsetup"}}; |
| |
| 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 = image_cache_->GenerateImageHash( |
| base::FilePath(k.image_pathname), true); |
| 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().sha256, 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; |
| } |
| } |
| } |
| |
| absl::Status ProcessCache::FillImageFromProcfs( |
| const base::FilePath& proc_pid_dir, |
| uint64_t pid_for_setns, |
| pb::FileImage* file_image_proto) { |
| const base::FilePath exe_symlink_path = proc_pid_dir.Append("exe"); |
| base::FilePath exe_path; |
| if (!base::ReadSymbolicLink(exe_symlink_path, &exe_path)) { |
| // Likely a kthread and there's no image to report. |
| return absl::OkStatus(); |
| } |
| base::stat_wrapper_t exe_stat; |
| auto statusorpath = |
| image_cache_->GetPathInCurrentMountNs(pid_for_setns, exe_path); |
| if (!statusorpath.ok()) { |
| return statusorpath.status(); |
| } |
| if (base::File::Stat(statusorpath.value(), &exe_stat)) { |
| return absl::NotFoundError( |
| base::StrCat({kErrorFailedToStat, statusorpath->value()})); |
| } |
| 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; |
| } |
| file_image_proto->set_pathname(exe_path.value()); |
| file_image_proto->set_mnt_ns(mnt_ns); |
| file_image_proto->set_inode_device_id(exe_stat.st_dev); |
| file_image_proto->set_inode(exe_stat.st_ino); |
| file_image_proto->set_canonical_uid(exe_stat.st_uid); |
| file_image_proto->set_canonical_gid(exe_stat.st_gid); |
| file_image_proto->set_mode(exe_stat.st_mode); |
| |
| ImageCacheInterface::ImageCacheKeyType 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}}; |
| { |
| auto result = image_cache_->InclusiveGetImage(image_key, true, |
| pid_for_setns, exe_path); |
| if (result.ok()) { |
| file_image_proto->set_sha256(result.value().sha256); |
| } |
| } |
| return absl::OkStatus(); |
| } |
| |
| } // namespace secagentd |