diff --git a/Source/common/BUILD b/Source/common/BUILD index 196f70f0b..2e48d21c4 100644 --- a/Source/common/BUILD +++ b/Source/common/BUILD @@ -1,3 +1,4 @@ +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("//:helper.bzl", "santa_unit_test") diff --git a/Source/common/santa.proto b/Source/common/santa.proto index ba3cf328a..9b323f973 100644 --- a/Source/common/santa.proto +++ b/Source/common/santa.proto @@ -129,13 +129,13 @@ message FileDescriptor { // Process information message ProcessInfo { // Process ID of the process - optional ProcessID id = 1; + optional santa.pb.v1.ProcessID id = 1; // Process ID of the parent process - optional ProcessID parent_id = 2; + optional santa.pb.v1.ProcessID parent_id = 2; // Process ID of the process responsible for this one - optional ProcessID responsible_id = 3; + optional santa.pb.v1.ProcessID responsible_id = 3; // Original parent ID, remains stable in the event a process is reparented optional int32 original_parent_pid = 4; @@ -181,10 +181,10 @@ message ProcessInfo { // Light variant of ProcessInfo message to help minimize on-disk/on-wire sizes message ProcessInfoLight { // Process ID of the process - optional ProcessID id = 1; + optional santa.pb.v1.ProcessID id = 1; // Process ID of the parent process - optional ProcessID parent_id = 2; + optional santa.pb.v1.ProcessID parent_id = 2; // Original parent ID, remains stable in the event a process is reparented optional int32 original_parent_pid = 3; diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 7d44e1f4c..6eabd602d 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -802,6 +802,7 @@ objc_library( "//Source/common:Unit", "//Source/santad/ProcessTree:process_tree", "//Source/santad/ProcessTree/annotations:originator", + "//Source/santad/ProcessTree/annotations:ancestry", "@MOLXPCConnection", ], ) @@ -840,7 +841,6 @@ macos_bundle( ], entitlements = select({ "//:adhoc_build": "com.google.santa.daemon.systemextension-adhoc.entitlements", - # Non-adhoc builds get their entitlements from the provisioning profile. "//conditions:default": None, }), infoplists = ["Info.plist"], diff --git a/Source/santad/Logs/EndpointSecurity/Writers/FSSpool/BUILD b/Source/santad/Logs/EndpointSecurity/Writers/FSSpool/BUILD index e7cbe5d2e..45fc5b3d3 100644 --- a/Source/santad/Logs/EndpointSecurity/Writers/FSSpool/BUILD +++ b/Source/santad/Logs/EndpointSecurity/Writers/FSSpool/BUILD @@ -1,3 +1,4 @@ +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("//:helper.bzl", "santa_unit_test") diff --git a/Source/santad/ProcessTree/BUILD b/Source/santad/ProcessTree/BUILD index add141c13..4ecc25b15 100644 --- a/Source/santad/ProcessTree/BUILD +++ b/Source/santad/ProcessTree/BUILD @@ -1,3 +1,4 @@ +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("//:helper.bzl", "santa_unit_test") diff --git a/Source/santad/ProcessTree/annotations/BUILD b/Source/santad/ProcessTree/annotations/BUILD index a10fd7ac2..c803cc903 100644 --- a/Source/santad/ProcessTree/annotations/BUILD +++ b/Source/santad/ProcessTree/annotations/BUILD @@ -25,6 +25,19 @@ cc_library( ], ) +cc_library( + name = "ancestry", + srcs = ["ancestry.cc"], + hdrs = ["ancestry.h"], + deps = [ + ":annotator", + "//Source/santad/ProcessTree:process", + "//Source/santad/ProcessTree:process_tree", + "//Source/santad/ProcessTree:process_tree_cc_proto", + "@com_google_absl//absl/container:flat_hash_map", + ], +) + santa_unit_test( name = "originator_test", srcs = ["originator_test.mm"], @@ -35,3 +48,14 @@ santa_unit_test( "//Source/santad/ProcessTree:process_tree_test_helpers", ], ) + +santa_unit_test( + name = "ancestry_test", + srcs = ["ancestry_test.mm"], + deps = [ + ":ancestry", + "//Source/santad/ProcessTree:process", + "//Source/santad/ProcessTree:process_tree_cc_proto", + "//Source/santad/ProcessTree:process_tree_test_helpers", + ], +) diff --git a/Source/santad/ProcessTree/annotations/ancestry.cc b/Source/santad/ProcessTree/annotations/ancestry.cc new file mode 100644 index 000000000..64e6633d0 --- /dev/null +++ b/Source/santad/ProcessTree/annotations/ancestry.cc @@ -0,0 +1,74 @@ +/// Copyright 2023 Google LLC +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +#include "Source/santad/ProcessTree/annotations/ancestry.h" + +#include "Source/santad/ProcessTree/process.h" +#include "Source/santad/ProcessTree/process_tree.h" +#include "Source/santad/ProcessTree/process_tree.pb.h" +#include "absl/container/flat_hash_map.h" + +namespace ptpb = ::santa::pb::v1::process_tree; + +namespace santa::santad::process_tree { + +::santa::pb::v1::process_tree::Annotations::Ancestry +AncestryAnnotator::getAncestry() const { + ::santa::pb::v1::process_tree::Annotations::Ancestry ancestry; + ancestry.CopyFrom(ancestry_); + return ancestry; +} + +void AncestryAnnotator::AddProcessToAncestry( + ptpb::Annotations::Ancestry &ancestry, const Process &process) { + ptpb::AncestryProcessID *ancestor = ancestry.add_ancestor(); + ancestor->set_pid(process.pid_.pid); + ancestor->set_secondary_id(process.creation_timestamp); +} + +void AncestryAnnotator::AnnotateFork(ProcessTree &tree, const Process &parent, + const Process &child) { + ptpb::Annotations::Ancestry ancestry; + + // If parent process has ancestry annotation, copy and add parent. + if (auto parent_annotation = tree.GetAnnotation(parent)) { + ancestry.CopyFrom((*parent_annotation)->getAncestry()); + AddProcessToAncestry(ancestry, parent); + // Otherwise, get all ancestors of the child and add them. + } else { + std::vector ancestors = tree.GetAncestors(child); + // Add ancestors starting from the root process + for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) { + ptpb::AncestryProcessID *ancestor = ancestry.add_ancestor(); + ancestor->set_pid(it->pid()); + ancestor->set_secondary_id(it->secondary_id()); + } + } + tree.AnnotateProcess(child, std::make_shared(ancestry)); +} + +void AncestryAnnotator::AnnotateExec(ProcessTree &tree, + const Process &orig_process, + const Process &new_process) { + // Do not annotate process on exec + return; +} + +std::optional AncestryAnnotator::Proto() const { + auto annotation = ptpb::Annotations(); + auto *ancestry_ptr = annotation.mutable_ancestry(); + ancestry_ptr->CopyFrom(AncestryAnnotator::getAncestry()); + return annotation; +} + +} // namespace santa::santad::process_tree \ No newline at end of file diff --git a/Source/santad/ProcessTree/annotations/ancestry.h b/Source/santad/ProcessTree/annotations/ancestry.h new file mode 100644 index 000000000..8807eedbf --- /dev/null +++ b/Source/santad/ProcessTree/annotations/ancestry.h @@ -0,0 +1,50 @@ +/// Copyright 2023 Google LLC +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +#ifndef SANTA__SANTAD_PROCESSTREE_ANNOTATIONS_ANCESTRY_H +#define SANTA__SANTAD_PROCESSTREE_ANNOTATIONS_ANCESTRY_H + +#include + +#include "Source/santad/ProcessTree/annotations/annotator.h" +#include "Source/santad/ProcessTree/process.h" +#include "Source/santad/ProcessTree/process_tree.pb.h" + +namespace santa::santad::process_tree { + +class AncestryAnnotator : public Annotator { + public: + // clang-format off + AncestryAnnotator() {} + explicit AncestryAnnotator( + ::santa::pb::v1::process_tree::Annotations::Ancestry ancestry) + : ancestry_(ancestry) {}; + // clang-format on + void AnnotateFork(ProcessTree &tree, const Process &parent, + const Process &child) override; + void AnnotateExec(ProcessTree &tree, const Process &orig_process, + const Process &new_process) override; + std::optional<::santa::pb::v1::process_tree::Annotations> Proto() + const override; + ::santa::pb::v1::process_tree::Annotations::Ancestry getAncestry() const; + + private: + void AddProcessToAncestry( + ::santa::pb::v1::process_tree::Annotations::Ancestry &ancestry, + const Process &process); + ::santa::pb::v1::process_tree::Annotations::Ancestry ancestry_; +}; + +} // namespace santa::santad::process_tree + +#endif \ No newline at end of file diff --git a/Source/santad/ProcessTree/annotations/ancestry_test.mm b/Source/santad/ProcessTree/annotations/ancestry_test.mm new file mode 100644 index 000000000..48211c1aa --- /dev/null +++ b/Source/santad/ProcessTree/annotations/ancestry_test.mm @@ -0,0 +1,98 @@ +/// Copyright 2023 Google LLC +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// https://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import +#import + +#include "Source/santad/ProcessTree/annotations/ancestry.h" +#include "Source/santad/ProcessTree/process.h" +#include "Source/santad/ProcessTree/process_tree.pb.h" +#include "Source/santad/ProcessTree/process_tree_test_helpers.h" + +using namespace santa::santad::process_tree; +namespace ptpb = ::santa::pb::v1::process_tree; + +@interface AncestryAnnotatorTest : XCTestCase +@property std::shared_ptr tree; +@property std::shared_ptr initProc; +@end + +@implementation AncestryAnnotatorTest + +- (void)setUp { + std::vector> annotators; + annotators.emplace_back(std::make_unique()); + self.tree = std::make_shared(std::move(annotators)); + self.initProc = self.tree->InsertInit(); +} + +- (void)testSingleFork_childHasAncestryAnnotation { + uint64_t event_id = 1; + // PID 1.1: fork() -> PID 2.2 + const struct Pid child_pid = {.pid = 2, .pidversion = 2}; + self.tree->HandleFork(event_id++, *self.initProc, child_pid); + + auto child = *self.tree->Get(child_pid); + auto annotation_opt = self.tree->GetAnnotation(*child); + XCTAssertTrue(annotation_opt.has_value()); + auto proto_opt = (*annotation_opt)->Proto(); + + XCTAssertTrue(proto_opt.has_value()); + XCTAssertEqual(proto_opt->ancestry().ancestor_size(), 1); + XCTAssertEqual(proto_opt->ancestry().ancestor().Get(0).pid(), 1); + XCTAssertEqual(proto_opt->ancestry().ancestor().Get(0).pidversion(), 1); +} + +- (void)testDoubleFork_grandchildHasAncestryAnnotation { + uint64_t event_id = 1; + // PID 1.1: fork() -> PID 2.2 fork() -> PID 3.3 + const struct Pid child_pid = {.pid = 2, .pidversion = 2}; + const struct Pid grandchild_pid = {.pid = 3, .pidversion = 3}; + + self.tree->HandleFork(event_id++, *self.initProc, child_pid); + auto child = *self.tree->Get(child_pid); + self.tree->HandleFork(event_id++, *child, grandchild_pid); + + auto grandchild = *self.tree->Get(grandchild_pid); + auto annotation_opt = self.tree->GetAnnotation(*grandchild); + XCTAssertTrue(annotation_opt.has_value()); + auto grandchild_proto_opt = (*annotation_opt)->Proto(); + XCTAssertTrue(grandchild_proto_opt.has_value()); + auto grandchild_proto = *grandchild_proto_opt; + XCTAssertEqual(grandchild_proto.ancestry().ancestor_size(), 2); + XCTAssertEqual(grandchild_proto.ancestry().ancestor().Get(0).pid(), 1); + XCTAssertEqual(grandchild_proto.ancestry().ancestor().Get(0).pidversion(), 1); + XCTAssertEqual(grandchild_proto.ancestry().ancestor().Get(1).pid(), 2); + XCTAssertEqual(grandchild_proto.ancestry().ancestor().Get(1).pidversion(), 2); +} + +- (void)testExec_noEffectOnAncestryAnnotation() { + uint64_t event_id = 1; + + // PID 1.1: fork() -> PID 2.2 + const struct Pid child_pid = {.pid = 2, .pidversion = 2}; + self.tree->HandleFork(event_id++, *self.initProc, child_pid); + auto child = *self.tree->Get(child_pid); + auto annotation_opt = self.tree->GetAnnotation(*child); + XCTAssertTrue(annotation_opt.has_value()); + auto proto_opt = (*annotation_opt)->Proto(); + + // PID 2.2: exec("/usr/bin/login") -> PID 2.3 + const struct Pid login_exec_pid = {.pid = 2, .pidversion = 3}; + const struct Program login_prog = {.executable = "/usr/bin/login", .arguments = {}}; + auto login = *self.tree->Get(login_pid); + self.tree->HandleExec(event_id++, *login, login_exec_pid, login_prog, cred); +} + +@end \ No newline at end of file diff --git a/Source/santad/ProcessTree/process.h b/Source/santad/ProcessTree/process.h index 3afbf42e0..b2ec6a686 100644 --- a/Source/santad/ProcessTree/process.h +++ b/Source/santad/ProcessTree/process.h @@ -75,10 +75,12 @@ class Process { public: explicit Process(const Pid pid, const Cred cred, std::shared_ptr program, - std::shared_ptr parent) + std::shared_ptr parent, + const uint64_t creation_timestamp) : pid_(pid), effective_cred_(cred), program_(program), + creation_timestamp(creation_timestamp), annotations_(), parent_(parent), refcnt_(0), @@ -92,6 +94,7 @@ class Process { const struct Pid pid_; const struct Cred effective_cred_; const std::shared_ptr program_; + const uint64_t creation_timestamp; private: // This is not API. diff --git a/Source/santad/ProcessTree/process_tree.cc b/Source/santad/ProcessTree/process_tree.cc index 0f4bb3843..4f9d4db35 100644 --- a/Source/santad/ProcessTree/process_tree.cc +++ b/Source/santad/ProcessTree/process_tree.cc @@ -45,7 +45,7 @@ void ProcessTree::BackfillInsertChildren( (parent && *(unlinked_proc.program_) == *(parent->program_)) ? parent->program_ : unlinked_proc.program_, - parent); + parent, 0); { absl::MutexLock lock(&mtx_); map_.emplace(unlinked_proc.pid_, proc); @@ -67,6 +67,31 @@ void ProcessTree::BackfillInsertChildren( } } +std::vector<::santa::pb::v1::process_tree::AncestryProcessID> +ProcessTree::GetAncestors(const Process &process) { + std::vector<::santa::pb::v1::process_tree::AncestryProcessID> ancestors; + if (!process.parent_) { + return ancestors; + } + { + absl::MutexLock lock(&mtx_); + std::optional> chainProcess = + GetLocked(process.parent_->pid_); + while (chainProcess != std::nullopt) { + ::santa::pb::v1::process_tree::AncestryProcessID process_id; + process_id.set_pid((*chainProcess)->pid_.pid); + process_id.set_secondary_id((*chainProcess)->pid_.pidversion); + ancestors.push_back(process_id); + if ((*chainProcess)->parent_) { + chainProcess = GetLocked((*chainProcess)->parent_->pid_); + } else { + chainProcess = std::nullopt; + } + } + } + return ancestors; +} + void ProcessTree::HandleFork(uint64_t timestamp, const Process &parent, const Pid new_pid) { if (Step(timestamp)) { @@ -74,7 +99,8 @@ void ProcessTree::HandleFork(uint64_t timestamp, const Process &parent, { absl::MutexLock lock(&mtx_); child = std::make_shared(new_pid, parent.effective_cred_, - parent.program_, map_[parent.pid_]); + parent.program_, map_[parent.pid_], + timestamp); map_.emplace(new_pid, child); } for (const auto &annotator : annotators_) { @@ -92,7 +118,8 @@ void ProcessTree::HandleExec(uint64_t timestamp, const Process &p, assert(new_pid.pid == p.pid_.pid); auto new_proc = std::make_shared( - new_pid, c, std::make_shared(prog), p.parent_); + new_pid, c, std::make_shared(prog), p.parent_, + timestamp); { absl::MutexLock lock(&mtx_); remove_at_.push_back({timestamp, p.pid_}); diff --git a/Source/santad/ProcessTree/process_tree.h b/Source/santad/ProcessTree/process_tree.h index 1abfd9ae0..954efc1f3 100644 --- a/Source/santad/ProcessTree/process_tree.h +++ b/Source/santad/ProcessTree/process_tree.h @@ -43,6 +43,9 @@ class ProcessTree { // Initialize the tree with the processes currently running on the system. absl::Status Backfill(); + std::vector<::santa::pb::v1::process_tree::AncestryProcessID> GetAncestors( + const Process &process); + // Inform the tree of a fork event, in which the parent process spawns a child // with the only difference between the two being the pid. void HandleFork(uint64_t timestamp, const Process &parent, diff --git a/Source/santad/ProcessTree/process_tree.proto b/Source/santad/ProcessTree/process_tree.proto index 6dc73fb69..1b1962580 100644 --- a/Source/santad/ProcessTree/process_tree.proto +++ b/Source/santad/ProcessTree/process_tree.proto @@ -2,6 +2,12 @@ syntax = "proto3"; package santa.pb.v1.process_tree; + +message AncestryProcessID { + optional int32 pid = 1; + optional uint64 secondary_id = 2; +} + message Annotations { enum Originator { UNSPECIFIED = 0; @@ -9,5 +15,10 @@ message Annotations { CRON = 2; } + message Ancestry { + repeated AncestryProcessID ancestor = 1; + } + Originator originator = 1; + Ancestry ancestry = 2; } diff --git a/Source/santad/ProcessTree/process_tree_macos.mm b/Source/santad/ProcessTree/process_tree_macos.mm index fc8f36b84..f4fb681d8 100644 --- a/Source/santad/ProcessTree/process_tree_macos.mm +++ b/Source/santad/ProcessTree/process_tree_macos.mm @@ -134,7 +134,7 @@ struct Pid PidFromAuditToken(const audit_token_t &tok) { .executable = path, .arguments = args, }), - nullptr); + nullptr, 0); } absl::Status ProcessTree::Backfill() { diff --git a/Source/santad/ProcessTree/process_tree_test_helpers.mm b/Source/santad/ProcessTree/process_tree_test_helpers.mm index 6bc4f3752..c19fc92a6 100644 --- a/Source/santad/ProcessTree/process_tree_test_helpers.mm +++ b/Source/santad/ProcessTree/process_tree_test_helpers.mm @@ -34,7 +34,8 @@ }; auto proc = std::make_shared( initpid, (Cred){.uid = 0, .gid = 0}, - std::make_shared((Program){.executable = "/init", .arguments = {"/init"}}), nullptr); + std::make_shared((Program){.executable = "/init", .arguments = {"/init"}}), nullptr, + 0); map_.emplace(initpid, proc); return proc; } diff --git a/Source/santad/SantadDeps.mm b/Source/santad/SantadDeps.mm index 08af2fece..0a7d43232 100644 --- a/Source/santad/SantadDeps.mm +++ b/Source/santad/SantadDeps.mm @@ -24,6 +24,7 @@ #import "Source/santad/DataLayer/SNTRuleTable.h" #include "Source/santad/DataLayer/WatchItems.h" #include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" +#include "Source/santad/ProcessTree/annotations/ancestry.h" #include "Source/santad/ProcessTree/annotations/originator.h" #include "Source/santad/ProcessTree/process_tree.h" #import "Source/santad/SNTDatabaseController.h" @@ -162,6 +163,8 @@ for (NSString *annotation in [configurator enabledProcessAnnotations]) { if ([[annotation lowercaseString] isEqualToString:@"originator"]) { annotators.emplace_back(std::make_unique()); + } else if ([[annotation lowercaseString] isEqualToString:@"ancestry"]) { + annotators.emplace_back(std::make_unique()); } else { LOGW(@"Unrecognized process annotation %@", annotation); } diff --git a/Source/santasyncservice/BUILD b/Source/santasyncservice/BUILD index aff76ecc5..7e6d8a46f 100644 --- a/Source/santasyncservice/BUILD +++ b/Source/santasyncservice/BUILD @@ -1,3 +1,4 @@ +load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library") load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application") load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("//:helper.bzl", "santa_unit_test")