Compare commits

...

16 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
Russell Hancox
6093118ba1 sync: Improve logging when connection restored, avoid retries. (#1408)
* sync: Improve logging when connection restored, avoid retries.

Don't retry requests if the error is that the machine is offline
2024-07-31 13:29:25 -04:00
Russell Hancox
6719d4c32a sync: Upgrade from SCNetworkReachability -> nw_path_monitor (#1406) 2024-07-31 10:53:16 -04:00
Russell Hancox
1ce4756771 santad: Synchronize access to metric callback array (#1405) 2024-07-29 12:09:03 -04:00
Russell Hancox
9a7dcefb92 sync: Fix serial_num field name (#1404)
Disable the preserve_proto_field_names option when marshalling JSON requests as this prevents the json_name attribute on fields from working properly. Add that attribute to all fields so that they marshal as expected. Stop setting the always_print_enums_as_ints field as the value we're setting to is the default anyway.

Also add a test that preflight request data looks as expected.
2024-07-29 12:08:21 -04:00
39 changed files with 677 additions and 221 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

@@ -605,10 +605,15 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
/** Export current state of the SNTMetricSet as an NSDictionary. */
- (NSDictionary *)export {
NSDictionary *exported = nil;
NSDictionary *exported;
NSArray *callbacks;
@synchronized(self) {
callbacks = [_callbacks mutableCopy];
}
// Invoke callbacks to ensure metrics are up to date.
for (void (^cb)(void) in _callbacks) {
for (void (^cb)(void) in callbacks) {
cb();
}

View File

@@ -54,4 +54,19 @@
///
+ (NSString *)modelIdentifier;
///
/// @return The Santa product version, e.g. 2024.6
///
+ (NSString *)santaProductVersion;
///
/// @return The Santa build version, e.g. 655965194
///
+ (NSString *)santaBuildVersion;
///
/// @return The full Santa versoin, e.g. 2024.6.655965194
///
+ (NSString *)santaFullVersion;
@end

View File

@@ -74,6 +74,21 @@
return @(model);
}
+ (NSString *)santaProductVersion {
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
return info_dict[@"CFBundleShortVersionString"];
}
+ (NSString *)santaBuildVersion {
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
return [[info_dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
}
+ (NSString *)santaFullVersion {
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
return info_dict[@"CFBundleVersion"];
}
#pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {

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",
],
)
@@ -823,6 +824,7 @@ objc_library(
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SystemResources",
],
@@ -839,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"],
@@ -1450,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

@@ -20,6 +20,7 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTSystemInfo.h"
#import "Source/common/SystemResources.h"
#import "Source/santad/Santad.h"
#include "Source/santad/SantadDeps.h"
@@ -111,13 +112,10 @@ int main(int argc, char *argv[]) {
// Do not wait on child processes
signal(SIGCHLD, SIG_IGN);
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
NSString *product_version = [SNTSystemInfo santaProductVersion];
NSString *build_version = [SNTSystemInfo santaBuildVersion];
NSProcessInfo *pi = [NSProcessInfo processInfo];
NSString *product_version = info_dict[@"CFBundleShortVersionString"];
NSString *build_version =
[[info_dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
if ([pi.arguments containsObject:@"-v"]) {
printf("%s (build %s)\n", [product_version UTF8String], [build_version UTF8String]);
return 0;

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")
@@ -22,7 +23,7 @@ objc_library(
name = "FCM_lib",
srcs = ["SNTSyncFCM.m"],
hdrs = ["SNTSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
sdk_frameworks = ["Network"],
deps = [
"@MOLAuthenticatingURLSession",
],
@@ -55,6 +56,7 @@ objc_library(
],
hdrs = ["SNTSyncManager.h"],
sdk_dylibs = ["libz"],
sdk_frameworks = ["Network"],
deps = [
":FCM_lib",
":broadcaster_lib",

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

@@ -14,7 +14,7 @@
#import "Source/santasyncservice/SNTSyncFCM.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <Network/Network.h>
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
@@ -80,7 +80,7 @@ static const uint32_t kDefaultConnectDelayMaxSeconds = 10;
@property(copy, nonatomic) SNTSyncFCMMessageHandler messageHandler;
/** Is used throughout the class to reconnect to FCM after a connection loss. */
@property SCNetworkReachabilityRef reachability;
@property nw_path_monitor_t pathMonitor;
/** FCM client identities. */
@property(nonatomic, readonly) NSString *project;
@@ -97,21 +97,6 @@ static const uint32_t kDefaultConnectDelayMaxSeconds = 10;
@end
#pragma mark SCNetworkReachabilityCallBack
/** Called when the network state changes. */
static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
void *info) {
dispatch_async(dispatch_get_main_queue(), ^{
if (flags & kSCNetworkReachabilityFlagsReachable) {
SNTSyncFCM *FCMClient = (__bridge SNTSyncFCM *)info;
SEL s = @selector(reachabilityRestored);
[NSObject cancelPreviousPerformRequestsWithTarget:FCMClient selector:s object:nil];
[FCMClient performSelector:s withObject:nil afterDelay:1];
}
});
}
@implementation SNTSyncFCM
#pragma mark init/dealloc methods
@@ -184,11 +169,6 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
messageHandler:messageHandler];
}
/** Before this object is released ensure reachability release. */
- (void)dealloc {
[self stopReachability];
}
#pragma mark property methods
- (BOOL)isConnected {
@@ -212,24 +192,27 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
/** Start listening for network state changes on a background thread. */
- (void)startReachability {
if (self.reachability) return;
if (self.pathMonitor) return;
self.pathMonitor = nw_path_monitor_create();
nw_path_monitor_set_queue(self.pathMonitor, dispatch_get_main_queue());
nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) {
if (nw_path_get_status(path) == nw_path_status_satisfied) {
SEL s = @selector(reachabilityRestored);
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:s object:nil];
[self performSelector:s withObject:nil afterDelay:1];
}
});
nw_path_monitor_set_cancel_handler(self.pathMonitor, ^{
self.pathMonitor = nil;
});
nw_path_monitor_start(self.pathMonitor);
LOGD(@"Reachability started.");
self.reachability =
SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, _connectComponents.host.UTF8String);
SCNetworkReachabilityContext context = {.info = (__bridge void *)self};
if (SCNetworkReachabilitySetCallback(self.reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
self.reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
}
}
/** Stop listening for network state changes. */
- (void)stopReachability {
if (self.reachability) {
SCNetworkReachabilitySetDispatchQueue(self.reachability, NULL);
if (self.reachability) CFRelease(self.reachability);
self.reachability = NULL;
}
if (!self.pathMonitor) return;
nw_path_monitor_cancel(self.pathMonitor);
}
#pragma mark message methods

View File

@@ -16,7 +16,7 @@
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <Network/Network.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
@@ -35,9 +35,7 @@
static const uint8_t kMaxEnqueuedSyncs = 2;
@interface SNTSyncManager () <SNTPushNotificationsDelegate> {
SCNetworkReachabilityRef _reachability;
}
@interface SNTSyncManager () <SNTPushNotificationsDelegate>
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@@ -48,6 +46,7 @@ static const uint8_t kMaxEnqueuedSyncs = 2;
@property(nonatomic) MOLXPCConnection *daemonConn;
@property(nonatomic) BOOL reachable;
@property nw_path_monitor_t pathMonitor;
@property SNTPushNotifications *pushNotifications;
@@ -58,22 +57,6 @@ static const uint8_t kMaxEnqueuedSyncs = 2;
@end
// Called when the network state changes
static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
void *info) {
// Put this check and set on the main thread to ensure serial access.
dispatch_async(dispatch_get_main_queue(), ^{
SNTSyncManager *commandSyncManager = (__bridge SNTSyncManager *)info;
// Only call the setter when there is a change. This will filter out the redundant calls to this
// callback whenever the network interface states change.
int reachable =
(flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable;
if (commandSyncManager.reachable != reachable) {
commandSyncManager.reachable = reachable;
}
});
}
@implementation SNTSyncManager
#pragma mark init
@@ -102,11 +85,6 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
return self;
}
- (void)dealloc {
// Ensure reachability is always stopped
[self stopReachability];
}
#pragma mark SNTSyncServiceXPC methods
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)isFromBundle {
@@ -279,8 +257,8 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
return [self eventUploadWithSyncState:syncState];
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
SLOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
return SNTSyncStatusTypePreflightFailed;
}
@@ -408,39 +386,36 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
LOGD(@"Internet connection has been restored, triggering a new sync.");
[self stopReachability];
[self sync];
}
}
// Start listening for network state changes on a background thread
// Start listening for network state changes.
- (void)startReachability {
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_reachability) return;
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].host.UTF8String;
self->_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
SCNetworkReachabilityContext context = {
.info = (__bridge_retained void *)self,
.release = (void (*)(const void *))CFBridgingRelease,
};
if (SCNetworkReachabilitySetCallback(self->_reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
self->_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
} else {
[self stopReachability];
if (self.pathMonitor) return;
self.pathMonitor = nw_path_monitor_create();
// Put the callback on the main thread to ensure serial access.
nw_path_monitor_set_queue(self.pathMonitor, dispatch_get_main_queue());
nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) {
// Only call the setter when there is a change. This will filter out the redundant calls to
// this callback whenever the network interface states change.
int reachable = nw_path_get_status(path) == nw_path_status_satisfied;
if (self.reachable != reachable) {
self.reachable = reachable;
}
});
nw_path_monitor_set_cancel_handler(self.pathMonitor, ^{
self.pathMonitor = nil;
});
nw_path_monitor_start(self.pathMonitor);
}
// Stop listening for network state changes
- (void)stopReachability {
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_reachability) {
SCNetworkReachabilitySetDispatchQueue(self->_reachability, NULL);
if (self->_reachability) CFRelease(self->_reachability);
self->_reachability = NULL;
}
});
if (!self.pathMonitor) return;
nw_path_monitor_cancel(self.pathMonitor);
}
@end

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

@@ -87,8 +87,7 @@ The following table expands upon the above logic to list most of the permutation
req->set_os_version(NSStringToUTF8String([SNTSystemInfo osVersion]));
req->set_os_build(NSStringToUTF8String([SNTSystemInfo osBuild]));
req->set_model_identifier(NSStringToUTF8String([SNTSystemInfo modelIdentifier]));
req->set_santa_version(
NSStringToUTF8String([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]));
req->set_santa_version(NSStringToUTF8String([SNTSystemInfo santaFullVersion]));
req->set_primary_user(NSStringToUTF8String(self.syncState.machineOwner));
if (self.syncState.pushNotificationsToken) {

View File

@@ -14,6 +14,7 @@
#import "Source/santasyncservice/SNTSyncStage.h"
#include <Foundation/Foundation.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
@@ -74,10 +75,7 @@ using santa::NSStringToUTF8String;
contentType:@"application/x-protobuf"];
}
google::protobuf::json::PrintOptions options{
.always_print_enums_as_ints = false,
.preserve_proto_field_names = true,
};
google::protobuf::json::PrintOptions options{};
std::string json;
absl::Status status = google::protobuf::json::MessageToJsonString(*message, &json, options);
@@ -88,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"];
@@ -179,6 +176,11 @@ using santa::NSStringToUTF8String;
data = [self performRequest:request timeout:timeout response:&response error:&requestError];
if (response.statusCode == 200) break;
// If the original request failed because of a "No network" error, break out of the loop,
// subsequent retries are pointless and the entire sync will be retried once a connection
// is established.
if (requestError.code == NSURLErrorNotConnectedToInternet) break;
// If the original request failed because of an auth error, attempt to get a new XSRF token and
// try again. Unfortunately some servers cause NSURLSession to return 'client cert required' or
// 'could not parse response' when a 403 occurs and SSL cert auth is enabled.

View File

@@ -22,6 +22,7 @@
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTSyncConstants.h"
#import "Source/common/SNTSystemInfo.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncEventUpload.h"
#import "Source/santasyncservice/SNTSyncPostflight.h"
@@ -60,11 +61,19 @@
OCMStub([self.syncState.daemonConn remoteObjectProxy]).andReturn(self.daemonConnRop);
OCMStub([self.syncState.daemonConn synchronousRemoteObjectProxy]).andReturn(self.daemonConnRop);
id siMock = OCMClassMock([SNTSystemInfo class]);
OCMStub([siMock serialNumber]).andReturn(@"QYGF4QM373");
OCMStub([siMock longHostname]).andReturn(@"full-hostname.example.com");
OCMStub([siMock osVersion]).andReturn(@"14.5");
OCMStub([siMock osBuild]).andReturn(@"23F79");
OCMStub([siMock modelIdentifier]).andReturn(@"MacBookPro18,3");
OCMStub([siMock santaFullVersion]).andReturn(@"2024.6.655965194");
self.syncState.session = OCMClassMock([NSURLSession class]);
self.syncState.syncBaseURL = [NSURL URLWithString:@"https://myserver.local/"];
self.syncState.machineID = [[NSUUID UUID] UUIDString];
self.syncState.machineOwner = NSUserName();
self.syncState.machineID = @"50C7E1EB-2EF5-42D4-A084-A7966FC45A95";
self.syncState.machineOwner = @"username1";
}
#pragma mark Test Helpers
@@ -271,7 +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:nil];
[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

@@ -35,27 +35,27 @@ enum ClientMode {
}
message PreflightRequest {
string serial_number = 1 [json_name="serial_num"];
string hostname = 2;
string os_version = 3;
string os_build = 4;
string model_identifier = 5;
string santa_version = 6;
string primary_user = 7;
string push_notification_token = 8;
string serial_number = 1 [json_name="serial_num"];
string hostname = 2 [json_name="hostname"];
string os_version = 3 [json_name="os_version"];
string os_build = 4 [json_name="os_build"];
string model_identifier = 5 [json_name="model_identifier"];
string santa_version = 6 [json_name="santa_version"];
string primary_user = 7 [json_name="primary_user"];
string push_notification_token = 8 [json_name="push_notification_token"];
ClientMode client_mode = 9;
bool request_clean_sync = 10;
ClientMode client_mode = 9 [json_name="client_mode"];
bool request_clean_sync = 10 [json_name="request_clean_sync"];
uint32 binary_rule_count = 11;
uint32 certificate_rule_count = 12;
uint32 compiler_rule_count = 13;
uint32 transitive_rule_count = 14;
uint32 teamid_rule_count = 15;
uint32 signingid_rule_count = 16;
uint32 cdhash_rule_count = 17;
uint32 binary_rule_count = 11 [json_name="binary_rule_count"];
uint32 certificate_rule_count = 12 [json_name="certificate_rule_count"];
uint32 compiler_rule_count = 13 [json_name="compiler_rule_count"];
uint32 transitive_rule_count = 14 [json_name="transitive_rule_count"];
uint32 teamid_rule_count = 15 [json_name="teamid_rule_count"];
uint32 signingid_rule_count = 16 [json_name="signingid_rule_count"];
uint32 cdhash_rule_count = 17 [json_name="cdhash_rule_count"];
// The UUID of the machine that is sending this preflight.
string machine_id = 18;
string machine_id = 18 [json_name="machine_id"];
}
enum SyncType {
@@ -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];
@@ -193,53 +193,53 @@ message Certificate {
string cn = 2;
string org = 3;
string ou = 4;
uint32 valid_from = 5;
uint32 valid_until = 6;
uint32 valid_from = 5 [json_name="valid_from"];
uint32 valid_until = 6 [json_name="valid_until"];
}
message Event {
string file_sha256 = 1;
string file_path = 2;
string file_name = 3;
string executing_user = 4;
double execution_time = 5;
repeated string logged_in_users = 6;
repeated string current_sessions = 7;
Decision decision = 8;
string file_sha256 = 1 [json_name="file_sha256"];
string file_path = 2 [json_name="file_path"];
string file_name = 3 [json_name="file_name"];
string executing_user = 4 [json_name="executing_user"];
double execution_time = 5 [json_name="execution_time"];
repeated string logged_in_users = 6 [json_name="logged_in_users"];
repeated string current_sessions = 7 [json_name="current_sessions"];
Decision decision = 8 [json_name="decision"];
string file_bundle_id = 9;
string file_bundle_path = 10;
string file_bundle_executable_rel_path = 11;
string file_bundle_name = 12;
string file_bundle_version = 13;
string file_bundle_version_string = 14;
string file_bundle_hash = 15;
uint64 file_bundle_hash_millis = 16;
uint64 file_bundle_binary_count = 17;
string file_bundle_id = 9 [json_name="file_bundle_id"];
string file_bundle_path = 10 [json_name="file_bundle_path"];
string file_bundle_executable_rel_path = 11 [json_name="file_bundle_executable_rel_path"];
string file_bundle_name = 12 [json_name="file_bundle_name"];
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"];
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;
int32 ppid = 19;
string parent_name = 20;
int32 pid = 18 [json_name="pid"];
int32 ppid = 19 [json_name="ppid"];
string parent_name = 20 [json_name="parent_name"];
string team_id = 21;
string signing_id = 22;
string cdhash = 23;
string team_id = 21 [json_name="team_id"];
string signing_id = 22 [json_name="signing_id"];
string cdhash = 23 [json_name="cdhash"];
string quarantine_data_url = 24;
string quarantine_referer_url = 25;
string quarantine_data_url = 24 [json_name="quarantine_data_url"];
string quarantine_referer_url = 25 [json_name="quarantine_referer_url"];
// Seconds since UNIX epoch. This field would ideally be an int64 but the protobuf library
// encodes that as a string, unlike NSJSONSerialization
uint32 quarantine_timestamp = 26;
string quarantine_agent_bundle_id = 27;
uint32 quarantine_timestamp = 26 [json_name="quarantine_timestamp"];
string quarantine_agent_bundle_id = 27 [json_name="quarantine_agent_bundle_id"];
repeated Certificate signing_chain = 28;
repeated Certificate signing_chain = 28 [json_name="signing_chain"];
}
message EventUploadRequest {
repeated Event events = 1;
repeated Event events = 1 [json_name="events"];
// The UUID of the machine where the event(s) occurred
string machine_id = 2;
string machine_id = 2 [json_name="machine_id"];
}
message EventUploadResponse {
@@ -294,9 +294,9 @@ message Rule {
}
message RuleDownloadRequest {
string cursor = 1;
string cursor = 1 [json_name="cursor"];
// The UUID of the machine that is requesting the rules.
string machine_id = 2;
string machine_id = 2 [json_name="machine_id"];
}
message RuleDownloadResponse {
@@ -305,10 +305,10 @@ message RuleDownloadResponse {
}
message PostflightRequest {
uint64 rules_received = 1;
uint64 rules_processed = 2;
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;
string machine_id = 3 [json_name="machine_id"];
}
message PostflightResponse { }

View File

@@ -0,0 +1 @@
{"serial_num":"QYGF4QM373","hostname":"full-hostname.example.com","os_version":"14.5","os_build":"23F79","model_identifier":"MacBookPro18,3","santa_version":"2024.6.655965194","primary_user":"username1","client_mode":"MONITOR","machine_id":"50C7E1EB-2EF5-42D4-A084-A7966FC45A95"}

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