Compare commits

...

12 Commits

Author SHA1 Message Date
Kuba Laguna
73ae919d02 Add ancestry annotator 2024-12-10 17:08:29 +01:00
Tom Burgin
737525b746 kvo static rules (#1425) 2024-09-12 19:23:30 -04:00
Matt W
8199348091 Use runtime platform binary check for exec evals (#1424)
* Use runtime platform binary check for exec evals

* PR Feedback

* Remove parens to mitigate insane clang-formatting
2024-09-10 09:07:50 -04:00
Pete Markowsky
9f41fbb124 Fix: Change uint64 fields in syncv1.proto to uint32 for backwards compatibility (#1422)
Change the uint64 fields in the syncv1.proto to uint32 to ensure backwards compatibility.

This also updates the SNTSyncEventUpload code to use the uint32 values and updates sync protocol docs.
2024-09-08 15:46:30 -04:00
Matt W
ff0efe952b Use proper CanWrite method to safeguard TTY struct access (#1420) 2024-08-21 16:29:33 -04:00
Tom Burgin
c711129ac9 s/NSDictionary/NSBundle/ (#1418) 2024-08-15 12:36:01 -04:00
Russell Hancox
a56f6c5447 Project: Update rules_apple to 3.8.0 (#1417) 2024-08-13 14:20:19 -04:00
Russell Hancox
fadc9b505b sync: Drop rules_* fields in postflight to uint32 (#1415)
* sync: Drop rules_* fields in postflight to uint32

This lets the protobuf json serializer to send the values as ints (like NSJSONSerialization did) instead of strings. This will cause problems if someone has 4B rules but that's probably a sign of bigger problems
2024-08-12 15:40:01 -04:00
Tom Burgin
c7766d5993 length and count check (#1413) 2024-08-09 17:17:13 -04:00
Russell Hancox
341abf044b sync: Fix Content-Type logic bug, add test (#1412) 2024-08-08 17:43:53 -04:00
Tom Burgin
b1cf83a7e3 switch to CFBundleCopyInfoDictionaryInDirectory (#1411) 2024-08-08 16:30:33 -04:00
Russell Hancox
013b0b40af sync: Remove debug logging of request JSON. (#1410)
This was helpful while switching to the protobuf lib but due to the way SLOGD works it isn't limited to just debug invocations and is quite noisy
2024-08-08 14:06:52 -04:00
31 changed files with 521 additions and 78 deletions

View File

@@ -4,7 +4,7 @@ bazel_dep(name = "apple_support", version = "1.15.1", repo_name = "build_bazel_a
bazel_dep(name = "abseil-cpp", version = "20240116.2", repo_name = "com_google_absl")
bazel_dep(name = "rules_python", version = "0.33.2")
bazel_dep(name = "rules_cc", version = "0.0.9")
bazel_dep(name = "rules_apple", version = "3.6.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_apple", version = "3.8.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_swift", version = "2.0.0-rc1", repo_name = "build_bazel_rules_swift")
bazel_dep(name = "rules_fuzzing", version = "0.5.2")
bazel_dep(name = "protobuf", version = "27.2", repo_name = "com_google_protobuf")

View File

@@ -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")

View File

@@ -368,7 +368,12 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
}
+ (NSSet *)keyPathsForValuesAffectingStaticRules {
return [self configStateSet];
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(cachedStaticRules))];
});
return set;
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {

View File

@@ -423,8 +423,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return self.infoDict;
}
d = self.bundle.infoDictionary;
if (d) {
// `-[NSBundle infoDictionary]` is heavily cached, changes to the Info.plist are not realized.
// Use `CFBundleCopyInfoDictionaryInDirectory` instead, which does not appear to cache.
NSString *bundlePath = [self bundlePath];
if (bundlePath.length) {
d = CFBridgingRelease(CFBundleCopyInfoDictionaryInDirectory(
(__bridge CFURLRef)[NSURL fileURLWithPath:bundlePath]));
}
if (d.count) {
self.infoDict = d;
return self.infoDict;
}

View File

@@ -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;

View File

@@ -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"],
)

View File

@@ -688,7 +688,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
// Notify users on block decisions
if (ShouldNotifyUserDecision(policyDecision) &&
(!policy->silent || (!policy->silent_tty && msg->process->tty->path.length > 0))) {
(!policy->silent || (!policy->silent_tty && TTYWriter::CanWrite(msg->process)))) {
SNTCachedDecision *cd =
[self.decisionCache cachedDecisionForFile:msg->process->executable->stat];

View File

@@ -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")

View File

@@ -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")

View File

@@ -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",
],
)

View 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

View 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

View 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

View File

@@ -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.

View File

@@ -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_});

View File

@@ -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.

View File

@@ -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;
}

View File

@@ -134,7 +134,7 @@ absl::StatusOr<Process> LoadPID(pid_t pid) {
.executable = path,
.arguments = args,
}),
nullptr);
nullptr, 0);
}
absl::Status ProcessTree::Backfill() {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -45,12 +45,6 @@
/// only guaranteed for the duration of the call to the block. Do not perform
/// any async processing without extending their lifetimes.
///
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
entitlementsFilterCallback:
(NSDictionary *_Nullable (^_Nonnull)(
const char *_Nullable teamID,
NSDictionary *_Nullable entitlements))entitlementsFilterCallback;
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
preCodesignCheckCallback:(void (^_Nullable)(void))preCodesignCheckCallback

View File

@@ -30,6 +30,12 @@
#import "Source/santad/DataLayer/SNTRuleTable.h"
#include "absl/container/flat_hash_map.h"
enum class PlatformBinaryState {
kRuntimeTrue = 0,
kRuntimeFalse,
kStaticCheck,
};
@interface SNTPolicyProcessor ()
@property SNTRuleTable *ruleTable;
@property SNTConfigurator *configurator;
@@ -129,7 +135,7 @@
}
static void UpdateCachedDecisionSigningInfo(
SNTCachedDecision *cd, MOLCodesignChecker *csInfo,
SNTCachedDecision *cd, MOLCodesignChecker *csInfo, PlatformBinaryState platformBinaryState,
NSDictionary *_Nullable (^entitlementsFilterCallback)(NSDictionary *_Nullable entitlements)) {
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
@@ -144,11 +150,18 @@ static void UpdateCachedDecisionSigningInfo(
cd.signingID = FormatSigningID(csInfo);
}
// Ensure that if no teamID exists that the signing info confirms it is a
// platform binary. If not, remove the signingID.
// Ensure that if no teamID exists but a signingID does exist, that the binary
// is a platform binary. If not, remove the signingID.
if (!cd.teamID && cd.signingID) {
if (!csInfo.platformBinary) {
cd.signingID = nil;
switch (platformBinaryState) {
case PlatformBinaryState::kRuntimeTrue: break;
case PlatformBinaryState::kStaticCheck:
if (!csInfo.platformBinary) {
cd.signingID = nil;
}
break;
case PlatformBinaryState::kRuntimeFalse: OS_FALLTHROUGH;
default: cd.signingID = nil; break;
}
}
@@ -170,6 +183,7 @@ static void UpdateCachedDecisionSigningInfo(
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID
signingID:(nullable NSString *)signingID
platformBinaryState:(PlatformBinaryState)platformBinaryState
isProdSignedCallback:(BOOL (^_Nonnull)())isProdSignedCallback
entitlementsFilterCallback:(NSDictionary *_Nullable (^_Nullable)(
NSDictionary *_Nullable entitlements))entitlementsFilterCallback
@@ -215,7 +229,7 @@ static void UpdateCachedDecisionSigningInfo(
cd.signingID = nil;
cd.cdhash = nil;
} else {
UpdateCachedDecisionSigningInfo(cd, csInfo, entitlementsFilterCallback);
UpdateCachedDecisionSigningInfo(cd, csInfo, platformBinaryState, entitlementsFilterCallback);
}
}
@@ -276,18 +290,6 @@ static void UpdateCachedDecisionSigningInfo(
}
}
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
entitlementsFilterCallback:
(NSDictionary *_Nullable (^_Nonnull)(
const char *_Nullable teamID,
NSDictionary *_Nullable entitlements))entitlementsFilterCallback {
return [self decisionForFileInfo:fileInfo
targetProcess:targetProc
preCodesignCheckCallback:nil
entitlementsFilterCallback:entitlementsFilterCallback];
}
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
targetProcess:(nonnull const es_process_t *)targetProc
preCodesignCheckCallback:(void (^_Nullable)(void))preCodesignCheckCallback
@@ -338,6 +340,8 @@ static void UpdateCachedDecisionSigningInfo(
certificateSHA256:nil
teamID:teamID
signingID:signingID
platformBinaryState:targetProc->is_platform_binary ? PlatformBinaryState::kRuntimeTrue
: PlatformBinaryState::kRuntimeFalse
isProdSignedCallback:^BOOL {
return ((targetProc->codesigning_flags & CS_DEV_CODE) == 0);
}
@@ -369,6 +373,7 @@ static void UpdateCachedDecisionSigningInfo(
certificateSHA256:identifiers.certificateSHA256
teamID:identifiers.teamID
signingID:identifiers.signingID
platformBinaryState:PlatformBinaryState::kStaticCheck
isProdSignedCallback:^BOOL {
if (csInfo) {
// Development OID values defined by Apple and used by the Security Framework

View File

@@ -315,12 +315,9 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
}],
[[SNTKVOManager alloc] initWithObject:configurator
selector:@selector(staticRules)
type:[NSArray class]
callback:^(NSArray *oldValue, NSArray *newValue) {
NSSet *oldValueSet = [NSSet setWithArray:oldValue ?: @[]];
NSSet *newValueSet = [NSSet setWithArray:newValue ?: @[]];
if ([oldValueSet isEqualToSet:newValueSet]) {
type:[NSDictionary class]
callback:^(NSDictionary *oldValue, NSDictionary *newValue) {
if ([oldValue isEqualToDictionary:newValue]) {
return;
}

View File

@@ -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);
}

View File

@@ -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")

View File

@@ -150,8 +150,8 @@ using santa::NSStringToUTF8String;
e->set_file_bundle_version(NSStringToUTF8String(event.fileBundleVersion));
e->set_file_bundle_version_string(NSStringToUTF8String(event.fileBundleVersionString));
e->set_file_bundle_hash(NSStringToUTF8String(event.fileBundleHash));
e->set_file_bundle_hash_millis([event.fileBundleHashMilliseconds longLongValue]);
e->set_file_bundle_binary_count([event.fileBundleBinaryCount longLongValue]);
e->set_file_bundle_hash_millis([event.fileBundleHashMilliseconds unsignedIntValue]);
e->set_file_bundle_binary_count([event.fileBundleBinaryCount unsignedIntValue]);
e->set_pid([event.pid unsignedIntValue]);
e->set_ppid([event.ppid unsignedIntValue]);

View File

@@ -38,8 +38,8 @@ using santa::NSStringToUTF8String;
google::protobuf::Arena arena;
auto req = google::protobuf::Arena::Create<::pbv1::PostflightRequest>(&arena);
req->set_machine_id(NSStringToUTF8String(self.syncState.machineID));
req->set_rules_received(self.syncState.rulesReceived);
req->set_rules_processed(self.syncState.rulesProcessed);
req->set_rules_received(static_cast<uint32_t>(self.syncState.rulesReceived));
req->set_rules_processed(static_cast<uint32_t>(self.syncState.rulesProcessed));
::pbv1::PostflightResponse response;
[self performRequest:[self requestWithMessage:req] intoMessage:&response timeout:30];

View File

@@ -86,13 +86,12 @@ using santa::NSStringToUTF8String;
return nil;
}
SLOGD(@"Request JSON: %s", json.c_str());
return [self requestWithData:[NSData dataWithBytes:json.data() length:json.size()]
contentType:@"application/json"];
}
- (NSMutableURLRequest *)requestWithData:(NSData *)requestBody contentType:(NSString *)contentType {
if (contentType.length) contentType = @"application/octet-stream";
if (!contentType.length) contentType = @"application/octet-stream";
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[self stageURL]];
[req setHTTPMethod:@"POST"];

View File

@@ -280,15 +280,17 @@
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_basic.json"];
[self stubRequestBody:respData
response:nil
error:nil
validateBlock:^BOOL(NSURLRequest *req) {
NSData *gotReqData = [req HTTPBody];
NSData *expectedReqData = [self dataFromFixture:@"sync_preflight_request.json"];
XCTAssertEqualObjects(gotReqData, expectedReqData);
return YES;
}];
[self
stubRequestBody:respData
response:nil
error:nil
validateBlock:^BOOL(NSURLRequest *req) {
NSData *gotReqData = [req HTTPBody];
NSData *expectedReqData = [self dataFromFixture:@"sync_preflight_request.json"];
XCTAssertEqualObjects(gotReqData, expectedReqData);
XCTAssertEqualObjects([req valueForHTTPHeaderField:@"Content-Type"], @"application/json");
return YES;
}];
XCTAssertTrue([sut sync]);
XCTAssertEqual(self.syncState.clientMode, SNTClientModeMonitor);

View File

@@ -125,16 +125,16 @@ message PreflightResponse {
optional bool disable_unknown_event_upload = 7;
// Specifies the time interval in seconds between full syncs. Defaults to 600 (10 minutes). Cannot be set lower than 60.
uint64 full_sync_interval_seconds = 8 [json_name="full_sync_interval"];
uint32 full_sync_interval_seconds = 8 [json_name="full_sync_interval"];
// When push notifications are enabled, this overrides the full_sync_interval above. It is expected that Santa will not
// need to perform a full sync as frequently when push notifications are working. Defaults to 14400 (6 hours).
uint64 push_notification_full_sync_interval_seconds = 9 [json_name="push_notification_full_sync_interval"];
uint32 push_notification_full_sync_interval_seconds = 9 [json_name="push_notification_full_sync_interval"];
// The maximum number of seconds Santa can wait before triggering a rule sync after receiving a "global rule sync" notification.
// As these notifications cause every Santa client to try and sync, we add a random delay to each client to try and spread the
// load out on the sync server. This defaults to 600 (10 minutes).
uint64 push_notification_global_rule_sync_deadline_seconds = 10 [json_name="push_notification_global_rule_sync_deadline"];
uint32 push_notification_global_rule_sync_deadline_seconds = 10 [json_name="push_notification_global_rule_sync_deadline"];
// These two regexes are used to allow/block executions whose path matches. The provided regex must conform to ICU format.
// While this feature can be useful, its use should be very carefully considered as it is much riskier than real rules.
@@ -159,8 +159,8 @@ message PreflightResponse {
optional bool deprecated_enabled_transitive_whitelisting = 1000 [json_name="enabled_transitive_whitelisting", deprecated=true];
optional bool deprecated_transitive_whitelisting_enabled = 1001 [json_name="transitive_whitelisting_enabled", deprecated=true];
optional bool deprecated_bundles_enabled = 1002 [json_name="bundles_enabled", deprecated=true];
optional uint64 deprecated_fcm_full_sync_interval_seconds = 1003 [json_name="fcm_full_sync_interval", deprecated=true];
optional uint64 deprecated_fcm_global_rule_sync_deadline_seconds = 1004 [json_name="fcm_global_rule_sync_deadline", deprecated=true];
optional uint32 deprecated_fcm_full_sync_interval_seconds = 1003 [json_name="fcm_full_sync_interval", deprecated=true];
optional uint32 deprecated_fcm_global_rule_sync_deadline_seconds = 1004 [json_name="fcm_global_rule_sync_deadline", deprecated=true];
optional string deprecated_whitelist_regex = 1005 [json_name="whitelist_regex", deprecated=true];
optional string deprecated_blacklist_regex = 1006 [json_name="blacklist_regex", deprecated=true];
@@ -214,8 +214,8 @@ message Event {
string file_bundle_version = 13 [json_name="file_bundle_version"];
string file_bundle_version_string = 14 [json_name="file_bundle_version_string"];
string file_bundle_hash = 15 [json_name="file_bundle_hash"];
uint64 file_bundle_hash_millis = 16 [json_name="file_bundle_hash_millis"];
uint64 file_bundle_binary_count = 17 [json_name="file_bundle_binary_count"];
uint32 file_bundle_hash_millis = 16 [json_name="file_bundle_hash_millis"];
uint32 file_bundle_binary_count = 17 [json_name="file_bundle_binary_count"];
// pid_t is an int32
int32 pid = 18 [json_name="pid"];
@@ -305,8 +305,8 @@ message RuleDownloadResponse {
}
message PostflightRequest {
uint64 rules_received = 1 [json_name="rules_received"];
uint64 rules_processed = 2 [json_name="rules_processed"];
uint32 rules_received = 1 [json_name="rules_received"];
uint32 rules_processed = 2 [json_name="rules_processed"];
// The UUID of the machine that is sending this postflight.
string machine_id = 3 [json_name="machine_id"];
}

View File

@@ -140,7 +140,7 @@ The JSON object has the following keys:
| enable_bundles | Use previous setting | boolean | Enable bundle scanning | true |
| enable_transitive_rules | Use previous setting | boolean | Whether or not to enable transitive allowlisting | true |
| batch_size | Use a Santa-defined default value | integer | Number of events to upload at a time | 128 |
| full_sync_interval | Defaults to 600 seconds | integer | Number of seconds between full syncs. Note: Santa enforces a minimum value of 60. The default value will be used if a smaller value is provided. | 600 |
| full_sync_interval | Defaults to 600 seconds | uint32 | Number of seconds between full syncs. Note: Santa enforces a minimum value of 60. The default value will be used if a smaller value is provided. | 600 |
| client_mode | Use previous setting | string | Operating mode to set for the client | either `MONITOR` or `LOCKDOWN` |
| allowed_path_regex | Use previous setting | string | Regular expression to allow a binary to execute from a path | "/Users/markowsk/foo/.\*" |
| blocked_path_regex | Use previous setting | string | Regular expression to block a binary from executing by path | "/tmp/" |
@@ -223,8 +223,8 @@ sequenceDiagram
| file_bundle_version | NO | string | The bundle version string | "9999.1.1" |
| file_bundle_version_string | NO | string | Bundle short version string | "2.3.4" |
| file_bundle_hash | NO | string | SHA256 hash of all executables in the bundle | "7466e3687f540bcb7792c6d14d5a186667dbe18a85021857b42effe9f0370805" |
| file_bundle_hash_millis | NO | float64 | The time in milliseconds it took to find all of the binaries, hash and produce the bundle_hash | 1234775 |
| file_bundle_binary_count | NO | integer | The number of binaries in a bundle | 13 |
| file_bundle_hash_millis | NO | uint32 | The time in milliseconds it took to find all of the binaries, hash and produce the bundle_hash | 1234775 |
| file_bundle_binary_count | NO | uint32 | The number of binaries in a bundle | 13 |
| pid | NO | int | Process id of the executable that was blocked | 1234 |
| ppid | NO | int | Parent process id of the executable that was blocked | 456 |
| parent_name | NO | Parent process short command name of the executable that was blocked | "bar" |