mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
1 Commits
main
...
feature/an
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73ae919d02 |
@@ -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")
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"],
|
||||
@@ -1451,6 +1451,7 @@ test_suite(
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool_test",
|
||||
"//Source/santad/ProcessTree:process_tree_test",
|
||||
"//Source/santad/ProcessTree/annotations:originator_test",
|
||||
"//Source/santad/ProcessTree/annotations:ancestry_test",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
89
Source/santad/ProcessTree/annotations/ancestry.cc
Normal file
89
Source/santad/ProcessTree/annotations/ancestry.cc
Normal file
@@ -0,0 +1,89 @@
|
||||
/// 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::AddEntryToAncestry(
|
||||
ptpb::Annotations::Ancestry &ancestry, int pid, uint64_t secondary_id) {
|
||||
ptpb::AncestryProcessID *ancestor = ancestry.add_ancestor();
|
||||
ancestor->set_pid(pid);
|
||||
ancestor->set_secondary_id(secondary_id);
|
||||
}
|
||||
|
||||
void AncestryAnnotator::CopyAncestorsToAncestry(
|
||||
ptpb::Annotations::Ancestry &ancestry,
|
||||
std::vector<std::shared_ptr<const Process>> ancestors) {
|
||||
// Add ancestors starting from the root process
|
||||
for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) {
|
||||
AddEntryToAncestry(ancestry, (*it)->pid_.pid, (*it)->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<AncestryAnnotator>(parent)) {
|
||||
ancestry.CopyFrom((*parent_annotation)->getAncestry());
|
||||
AddEntryToAncestry(ancestry, parent.pid_.pid, parent.creation_timestamp);
|
||||
// Otherwise, get all ancestors of the child and add them.
|
||||
} else {
|
||||
std::vector<std::shared_ptr<const Process>> ancestors =
|
||||
tree.GetAncestors(child);
|
||||
CopyAncestorsToAncestry(ancestry, ancestors);
|
||||
}
|
||||
tree.AnnotateProcess(child, std::make_shared<AncestryAnnotator>(ancestry));
|
||||
}
|
||||
|
||||
void AncestryAnnotator::AnnotateExec(ProcessTree &tree,
|
||||
const Process &orig_process,
|
||||
const Process &new_process) {
|
||||
ptpb::Annotations::Ancestry ancestry;
|
||||
// If original process has ancestry annotation, copy entries.
|
||||
if (auto orig_process_annotation =
|
||||
tree.GetAnnotation<AncestryAnnotator>(orig_process)) {
|
||||
ancestry.CopyFrom((*orig_process_annotation)->getAncestry());
|
||||
// Otherwise, compute all ancestors of the new process and add them.
|
||||
} else {
|
||||
std::vector<std::shared_ptr<const Process>> ancestors =
|
||||
tree.GetAncestors(new_process);
|
||||
CopyAncestorsToAncestry(ancestry, ancestors);
|
||||
}
|
||||
tree.AnnotateProcess(new_process,
|
||||
std::make_shared<AncestryAnnotator>(ancestry));
|
||||
}
|
||||
|
||||
std::optional<ptpb::Annotations> 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
|
||||
53
Source/santad/ProcessTree/annotations/ancestry.h
Normal file
53
Source/santad/ProcessTree/annotations/ancestry.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/// 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 <optional>
|
||||
|
||||
#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 AddEntryToAncestry(
|
||||
::santa::pb::v1::process_tree::Annotations::Ancestry &ancestry, int pid,
|
||||
uint64_t secondary_id);
|
||||
void CopyAncestorsToAncestry(
|
||||
::santa::pb::v1::process_tree::Annotations::Ancestry &ancestry,
|
||||
std::vector<std::shared_ptr<const Process>> ancestors);
|
||||
::santa::pb::v1::process_tree::Annotations::Ancestry ancestry_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::process_tree
|
||||
|
||||
#endif
|
||||
193
Source/santad/ProcessTree/annotations/ancestry_test.mm
Normal file
193
Source/santad/ProcessTree/annotations/ancestry_test.mm
Normal file
@@ -0,0 +1,193 @@
|
||||
/// 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 <Foundation/Foundation.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#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<ProcessTreeTestPeer> tree;
|
||||
@property std::shared_ptr<const Process> initProc;
|
||||
@end
|
||||
|
||||
@implementation AncestryAnnotatorTest
|
||||
|
||||
- (void)setUp {
|
||||
std::vector<std::unique_ptr<Annotator>> annotators;
|
||||
annotators.emplace_back(std::make_unique<AncestryAnnotator>());
|
||||
self.tree = std::make_shared<ProcessTreeTestPeer>(std::move(annotators));
|
||||
self.initProc = self.tree->InsertInit();
|
||||
}
|
||||
|
||||
- (void)testSingleFork_childHasAncestryAnnotation {
|
||||
// PID 1.1: fork() -> PID 1.1
|
||||
// -> PID 2.2
|
||||
uint64_t event_id = 123;
|
||||
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<AncestryAnnotator>(*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).secondary_id(), 0);
|
||||
}
|
||||
|
||||
- (void)testDoubleFork_grandchildHasAncestryAnnotation {
|
||||
// PID 1.1: fork() -> PID 1.1
|
||||
// -> PID 2.2 fork() -> PID 2.2
|
||||
// -> PID 3.3
|
||||
uint64_t event_id = 123;
|
||||
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<AncestryAnnotator>(*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).secondary_id(), 0);
|
||||
XCTAssertEqual(grandchild_proto.ancestry().ancestor().Get(1).pid(), 2);
|
||||
XCTAssertEqual(grandchild_proto.ancestry().ancestor().Get(1).secondary_id(), 123);
|
||||
}
|
||||
|
||||
- (void)testRootProcessExec_processAfterExecStillHasNoAncestors {
|
||||
// PID 1.1: exec() -> PID 1.2
|
||||
uint64_t event_id = 123;
|
||||
const struct Cred cred = {.uid = 0, .gid = 0};
|
||||
const struct Pid pid_after_exec = {.pid = 1, .pidversion = 2};
|
||||
const struct Program program = {.executable = "/any/executable", .arguments = {}};
|
||||
|
||||
self.tree->HandleExec(event_id++, *self.initProc, pid_after_exec, program, cred);
|
||||
|
||||
auto process_after_exec = *self.tree->Get(pid_after_exec);
|
||||
auto annotation_opt = self.tree->GetAnnotation<AncestryAnnotator>(*process_after_exec);
|
||||
XCTAssertTrue(annotation_opt.has_value());
|
||||
auto proto_opt = (*annotation_opt)->Proto();
|
||||
XCTAssertTrue(proto_opt.has_value());
|
||||
auto proto = *proto_opt;
|
||||
XCTAssertEqual(proto.ancestry().ancestor_size(), 0);
|
||||
}
|
||||
|
||||
- (void)testForkAndExec_processAfterExecHasTheSameAnnotation {
|
||||
// PID 1.1: fork() -> PID 1.1
|
||||
// -> PID 2.2 exec() -> PID 2.3
|
||||
uint64_t event_id = 123;
|
||||
const struct Cred cred = {.uid = 0, .gid = 0};
|
||||
const struct Pid child_pid = {.pid = 2, .pidversion = 2};
|
||||
const struct Pid child_pid_after_exec = {.pid = 2, .pidversion = 3};
|
||||
const struct Program program = {.executable = "/any/executable", .arguments = {}};
|
||||
|
||||
self.tree->HandleFork(event_id++, *self.initProc, child_pid);
|
||||
auto child = *self.tree->Get(child_pid);
|
||||
self.tree->HandleExec(event_id++, *child, child_pid_after_exec, program, cred);
|
||||
|
||||
auto child_after_exec = *self.tree->Get(child_pid_after_exec);
|
||||
auto annotation_opt = self.tree->GetAnnotation<AncestryAnnotator>(*child_after_exec);
|
||||
XCTAssertTrue(annotation_opt.has_value());
|
||||
auto proto_opt = (*annotation_opt)->Proto();
|
||||
XCTAssertTrue(proto_opt.has_value());
|
||||
auto proto = *proto_opt;
|
||||
XCTAssertEqual(proto.ancestry().ancestor_size(), 1);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(0).pid(), 1);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(0).secondary_id(), 0);
|
||||
}
|
||||
|
||||
- (void)testForkAndParentExit_childHasOriginalAncestryAfterParentExit {
|
||||
// PID 1.1: fork() -> PID 1.1
|
||||
// -> PID 2.2 fork() -> PID 2.2 exit()
|
||||
// -> PID 3.3
|
||||
uint64_t event_id = 123;
|
||||
const struct Pid parent_pid = {.pid = 2, .pidversion = 2};
|
||||
const struct Pid child_pid = {.pid = 3, .pidversion = 3};
|
||||
|
||||
// Double fork to not call exit on the root process
|
||||
self.tree->HandleFork(event_id++, *self.initProc, parent_pid);
|
||||
auto parent = *self.tree->Get(parent_pid);
|
||||
self.tree->HandleFork(event_id++, *parent, child_pid);
|
||||
auto child = *self.tree->Get(child_pid);
|
||||
self.tree->HandleExit(event_id++, *parent);
|
||||
|
||||
auto annotation_opt = self.tree->GetAnnotation<AncestryAnnotator>(*child);
|
||||
XCTAssertTrue(annotation_opt.has_value());
|
||||
auto proto_opt = (*annotation_opt)->Proto();
|
||||
XCTAssertTrue(proto_opt.has_value());
|
||||
auto proto = *proto_opt;
|
||||
XCTAssertEqual(proto.ancestry().ancestor_size(), 2);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(0).pid(), 1);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(0).secondary_id(), 0);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(1).pid(), 2);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(1).secondary_id(), 123);
|
||||
}
|
||||
|
||||
- (void)testBackfillInsertChildren_processesHaveAnnotation {
|
||||
/*
|
||||
PID 1.1
|
||||
/ \
|
||||
PID 2.2 PID 3.3
|
||||
/
|
||||
PID 4.4
|
||||
*/
|
||||
const struct Cred cred = {.uid = 0, .gid = 0};
|
||||
const struct Program program = {.executable = "/any/executable", .arguments = {}};
|
||||
absl::flat_hash_map<pid_t, std::vector<Process>> parent_map;
|
||||
Process p1 = // root
|
||||
Process({.pid = 1, .pidversion = 1}, cred, std::make_shared<Program>(program), nullptr, 0);
|
||||
Process p2 =
|
||||
Process({.pid = 2, .pidversion = 2}, cred, std::make_shared<Program>(program), nullptr, 0);
|
||||
Process p3 =
|
||||
Process({.pid = 3, .pidversion = 3}, cred, std::make_shared<Program>(program), nullptr, 0);
|
||||
Process p4 =
|
||||
Process({.pid = 4, .pidversion = 4}, cred, std::make_shared<Program>(program), nullptr, 0);
|
||||
parent_map[1].push_back(p2);
|
||||
parent_map[1].push_back(p3);
|
||||
parent_map[3].push_back(p4);
|
||||
std::vector<std::unique_ptr<Annotator>> annotators;
|
||||
annotators.emplace_back(std::make_unique<AncestryAnnotator>());
|
||||
auto tree = std::make_shared<ProcessTreeTestPeer>(std::move(annotators));
|
||||
|
||||
tree->BackfillInsertChildren(parent_map, std::shared_ptr<Process>(), p1);
|
||||
|
||||
auto p4_from_tree = *tree->Get(p4.pid_);
|
||||
auto annotation_opt = tree->GetAnnotation<AncestryAnnotator>(*p4_from_tree);
|
||||
XCTAssertTrue(annotation_opt.has_value());
|
||||
auto proto_opt = (*annotation_opt)->Proto();
|
||||
XCTAssertTrue(proto_opt.has_value());
|
||||
auto proto = *proto_opt;
|
||||
XCTAssertEqual(proto.ancestry().ancestor_size(), 2);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(0).pid(), 1);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(0).secondary_id(), 0);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(1).pid(), 3);
|
||||
XCTAssertEqual(proto.ancestry().ancestor().Get(1).secondary_id(), 0);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -75,10 +75,12 @@ class Process {
|
||||
public:
|
||||
explicit Process(const Pid pid, const Cred cred,
|
||||
std::shared_ptr<const Program> program,
|
||||
std::shared_ptr<const Process> parent)
|
||||
std::shared_ptr<const Process> 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<const Program> program_;
|
||||
const uint64_t creation_timestamp;
|
||||
|
||||
private:
|
||||
// This is not API.
|
||||
|
||||
@@ -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<std::shared_ptr<const Process>> ProcessTree::GetAncestors(
|
||||
const Process &process) {
|
||||
std::vector<std::shared_ptr<const Process>> ancestors;
|
||||
if (!process.parent_) {
|
||||
return ancestors;
|
||||
}
|
||||
{
|
||||
absl::MutexLock lock(&mtx_);
|
||||
std::optional<std::shared_ptr<Process>> ancestor =
|
||||
GetLocked(process.parent_->pid_);
|
||||
while (ancestor != std::nullopt) {
|
||||
// ::santa::pb::v1::process_tree::AncestryProcessID process_id;
|
||||
// process_id.set_pid((*ancestor)->pid_.pid);
|
||||
// process_id.set_secondary_id((*ancestor)->creation_timestamp);
|
||||
ancestors.push_back(*ancestor);
|
||||
if ((*ancestor)->parent_) {
|
||||
ancestor = GetLocked((*ancestor)->parent_->pid_);
|
||||
} else {
|
||||
ancestor = 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<Process>(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<Process>(
|
||||
new_pid, c, std::make_shared<const Program>(prog), p.parent_);
|
||||
new_pid, c, std::make_shared<const Program>(prog), p.parent_,
|
||||
timestamp);
|
||||
{
|
||||
absl::MutexLock lock(&mtx_);
|
||||
remove_at_.push_back({timestamp, p.pid_});
|
||||
|
||||
@@ -43,6 +43,11 @@ class ProcessTree {
|
||||
// Initialize the tree with the processes currently running on the system.
|
||||
absl::Status Backfill();
|
||||
|
||||
// Returns a vector containing the parent of the process, the parent of the
|
||||
// parent of the process, etc. up to the root process.
|
||||
std::vector<std::shared_ptr<const Process>> 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,
|
||||
@@ -102,6 +107,10 @@ class ProcessTree {
|
||||
// Traverse the tree from the given Process to its parent.
|
||||
std::shared_ptr<const Process> GetParent(const Process &p) const;
|
||||
|
||||
void BackfillInsertChildren(
|
||||
absl::flat_hash_map<pid_t, std::vector<Process>> &parent_map,
|
||||
std::shared_ptr<Process> parent, const Process &unlinked_proc);
|
||||
|
||||
#if SANTA_PROCESS_TREE_DEBUG
|
||||
// Dump the tree in a human readable form to the given ostream.
|
||||
void DebugDump(std::ostream &stream) const;
|
||||
@@ -109,10 +118,6 @@ class ProcessTree {
|
||||
|
||||
private:
|
||||
friend class ProcessTreeTestPeer;
|
||||
void BackfillInsertChildren(
|
||||
absl::flat_hash_map<pid_t, std::vector<Process>> &parent_map,
|
||||
std::shared_ptr<Process> parent, const Process &unlinked_proc);
|
||||
|
||||
// Mark that an event with the given timestamp is being processed.
|
||||
// Returns whether the given timestamp is "novel", and the tree should be
|
||||
// updated with the results of the event.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ absl::StatusOr<Process> LoadPID(pid_t pid) {
|
||||
.executable = path,
|
||||
.arguments = args,
|
||||
}),
|
||||
nullptr);
|
||||
nullptr, 0);
|
||||
}
|
||||
|
||||
absl::Status ProcessTree::Backfill() {
|
||||
|
||||
@@ -243,4 +243,25 @@ using namespace santa::santad::process_tree;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testGetAncestors {
|
||||
// PID 1.1: fork() -> PID 1.1
|
||||
// -> PID 2.2 fork() -> PID 2.2
|
||||
// -> PID 3.3
|
||||
uint64_t event_id = 1;
|
||||
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 ancestors = self.tree->GetAncestors(*grandchild);
|
||||
|
||||
XCTAssertEqual(ancestors.size(), 2);
|
||||
XCTAssertEqual(ancestors[0]->pid_.pid, 2);
|
||||
XCTAssertEqual(ancestors[0]->creation_timestamp, 1);
|
||||
XCTAssertEqual(ancestors[1]->pid_.pid, 1);
|
||||
XCTAssertEqual(ancestors[1]->creation_timestamp, 0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -34,7 +34,8 @@ std::shared_ptr<const Process> ProcessTreeTestPeer::InsertInit() {
|
||||
};
|
||||
auto proc = std::make_shared<Process>(
|
||||
initpid, (Cred){.uid = 0, .gid = 0},
|
||||
std::make_shared<Program>((Program){.executable = "/init", .arguments = {"/init"}}), nullptr);
|
||||
std::make_shared<Program>((Program){.executable = "/init", .arguments = {"/init"}}), nullptr,
|
||||
0);
|
||||
map_.emplace(initpid, proc);
|
||||
return proc;
|
||||
}
|
||||
|
||||
@@ -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 @@ std::unique_ptr<SantadDeps> SantadDeps::Create(SNTConfigurator *configurator,
|
||||
for (NSString *annotation in [configurator enabledProcessAnnotations]) {
|
||||
if ([[annotation lowercaseString] isEqualToString:@"originator"]) {
|
||||
annotators.emplace_back(std::make_unique<santa::santad::process_tree::OriginatorAnnotator>());
|
||||
} else if ([[annotation lowercaseString] isEqualToString:@"ancestry"]) {
|
||||
annotators.emplace_back(std::make_unique<santa::santad::process_tree::AncestryAnnotator>());
|
||||
} else {
|
||||
LOGW(@"Unrecognized process annotation %@", annotation);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user