Compare commits

..

23 Commits

Author SHA1 Message Date
Russell Hancox
4236d57e96 Project: Update packaging script to do tarball creation in a scratch dir (#793) 2022-04-28 15:25:48 -04:00
Russell Hancox
36d463a1dc Project: Include syncservice.plist in release builds and loads (#792) 2022-04-28 14:42:19 -04:00
Tom Burgin
adbafd6bab syncservice: sign and package (#790)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-04-28 13:31:20 -04:00
Tom Burgin
b5ebe1259c syncservice: implementation and migration (#775)
* review updates

* fix test

* review updates

* log level cleanup

Co-authored-by: Tom Burgin <bur@chromium.org>
Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2022-04-27 14:54:56 -04:00
Ryan Diers
e0ae0f481b santa/gui: Update buttons to push style to better stand out (#788) 2022-04-19 20:08:35 -04:00
Matt W
8037c79fc0 Populate critical paths from the ES default mute set (#786)
* Populate critical paths from the ES default mute set

* Attempt to fix build on older macos

* Link ES to build SNTRuleTableTest

* Workflow test

* Use preprocessor macros to support building on older SDKs

* Add API availability
2022-04-18 15:11:43 -04:00
Walter Lee
892d303de1 Disable layering check for Objective-C, part two (#787) 2022-04-18 12:15:08 -04:00
Russell Hancox
ff3979263e santad: Use TTY path provided by ES (#785) 2022-04-15 12:48:06 -04:00
np5
01afefd3d4 santactl/sync: Fix event team ID decision value (#784) 2022-04-15 10:27:48 -04:00
Kent Ma
830627e7bc Docs: Add "Team ID" to description on AllowedPathRegex (#782) 2022-04-14 13:13:51 -04:00
Walter Lee
601d726fcc Disable layering check for Objective-C (#781) 2022-04-12 09:06:55 -04:00
Tom Burgin
0be1ca0199 ES_EVENT_TYPE_NOTIFY_UNMOUNT: flush the cache off the ES handler thread (#778)
Co-authored-by: Tom Burgin <bur@chromium.org>
2022-04-06 12:07:08 -04:00
Kent Ma
8602593149 Fix dead link (#774) 2022-03-25 13:33:08 -04:00
Matt W
9bca601ce6 Modified build target names for santa proto (#772) 2022-03-25 13:07:57 -04:00
Kent Ma
c73acd59d4 Update logo image of Santa (#773) 2022-03-25 12:46:34 -04:00
Russell Hancox
3c334e8882 Project: Fix coverage collection (#770) 2022-03-24 11:33:46 -04:00
Russell Hancox
5f811cadf8 Project: Update apple_rules dep, add .bazelversion for bazelisk users (#769) 2022-03-23 17:34:04 -04:00
Russell Hancox
4252475de0 Project: Fix fallback version (#767) 2022-03-23 15:13:30 -04:00
Kent Ma
45f1822681 Exclude bazel-out from test coverage generation (#768) 2022-03-23 15:10:46 -04:00
Russell Hancox
498a23d907 Project: Make versioning dynamic through bazel's --embed-label. (#766)
The apple_rules allow versioning using an apple_bundle_version rule that extracts elements from an embedded label. We haven't been able to use this until now because the kernel extension needed access to the version in a define.
2022-03-23 14:53:51 -04:00
Russell Hancox
5dff8a18f4 santad: Split ES cache into root/non-root varieties (#765) 2022-03-23 09:43:14 -04:00
Russell Hancox
676c02626d santactl/metrics: Allow filtering metrics (#763) 2022-03-22 18:12:14 +00:00
Russell Hancox
64950d0a99 Project: Show test errors in output from CI (#764) 2022-03-22 11:39:01 -04:00
72 changed files with 1364 additions and 814 deletions

1
.bazelversion Normal file
View File

@@ -0,0 +1 @@
5.0.0

View File

@@ -73,7 +73,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Run All Tests
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci --test_output=errors
test_coverage:
runs-on: macos-11
@@ -87,7 +87,7 @@ jobs:
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./CoverageData/info.lcov
path-to-lcov: ./bazel-out/_coverage/_coverage_report.dat
flag-name: Unit
benchmark:

19
BUILD
View File

@@ -1,6 +1,5 @@
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
load("//:helper.bzl", "run_command")
load("//:version.bzl", "SANTA_VERSION")
package(default_visibility = ["//:santa_package_group"])
@@ -11,13 +10,14 @@ exports_files(["LICENSE"])
# The version label for mac_* rules.
apple_bundle_version(
name = "version",
build_label_pattern = "{build}",
build_version = SANTA_VERSION + ".{build}",
build_label_pattern = ".*santa_{release}\\.{build}",
build_version = "{release}.{build}",
capture_groups = {
"release": "\\d{4}\\.\\d+",
"build": "\\d+",
},
fallback_build_label = "1",
short_version_string = SANTA_VERSION,
fallback_build_label = "santa_9999.1.1",
short_version_string = "{release}",
)
# Used to detect release builds
@@ -54,6 +54,7 @@ run_command(
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.syncservice.plist 2>/dev/null
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
""",
)
@@ -64,6 +65,7 @@ run_command(
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
launchctl load /Library/LaunchAgents/com.google.santa.plist
""",
)
@@ -98,6 +100,7 @@ genrule(
"Conf/uninstall.sh",
"Conf/com.google.santa.bundleservice.plist",
"Conf/com.google.santa.metricservice.plist",
"Conf/com.google.santa.syncservice.plist",
"Conf/com.google.santad.plist",
"Conf/com.google.santa.plist",
"Conf/com.google.santa.newsyslog.conf",
@@ -107,7 +110,7 @@ genrule(
"Conf/Package/postinstall",
"Conf/Package/preinstall",
],
outs = ["santa-" + SANTA_VERSION + ".tar.gz"],
outs = ["santa-release.tar.gz"],
cmd = select({
"//conditions:default": """
echo "ERROR: Trying to create a release tarball without optimization."
@@ -149,6 +152,10 @@ genrule(
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
;;
*santasyncservice.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santasyncservice.dSYM
;;
*Santa.app.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM

View File

@@ -48,6 +48,7 @@ readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.s
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
@@ -64,7 +65,7 @@ readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
EN="${ENTITLEMENTS}/${BN}.entitlements"
@@ -113,11 +114,11 @@ echo "verifying signatures"
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
echo "creating fresh release tarball"
/bin/mkdir -p "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${RELEASE_ROOT}/${RELEASE_NAME}"
/usr/bin/tar -C "${RELEASE_ROOT}" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
echo "creating app pkg"
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
@@ -130,6 +131,7 @@ echo "creating app pkg"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"

View File

@@ -15,8 +15,8 @@
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<false/>
<key>KeepAlive</key>
<true/>
<false/>
</dict>
</plist>

View File

@@ -27,7 +27,10 @@ fi
# Unload metric service
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
# Unload kext.
# Unload sync service
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Determine if anyone is logged into the GUI
@@ -59,6 +62,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
@@ -74,6 +78,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Load com.google.santa.metricservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load com.google.santa.syncservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
# Load GUI agent if someone is logged in.
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -13,6 +13,7 @@
# remove helper XPC services
/bin/launchctl remove com.google.santa.bundleservice
/bin/launchctl remove com.google.santa.metricservice
/bin/launchctl remove com.google.santa.syncservice
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
user=$(/usr/bin/stat -f '%u' /dev/console)
@@ -28,6 +29,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.plist
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
/bin/rm -f /usr/local/bin/santactl # just a symlink

View File

@@ -1,7 +1,7 @@
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml) [![Coverage Status](https://coveralls.io/repos/github/google/santa/badge.svg?branch=main)](https://coveralls.io/github/google/santa?branch=main)
<p align="center">
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
<img src="https://raw.githubusercontent.com/google/santa/main/Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
</p>
Santa is a binary authorization system for macOS. It consists of a system

View File

@@ -3,14 +3,15 @@ load("@rules_proto_grpc//objc:defs.bzl", "objc_proto_library")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
licenses(["notice"])
proto_library(
name = "log_proto",
name = "santa_proto",
srcs = ["santa.proto"],
features = ["layering_check"],
deps = [
"@com_google_protobuf//:any_proto",
"@com_google_protobuf//:timestamp_proto",
@@ -18,10 +19,10 @@ proto_library(
)
objc_proto_library(
name = "log_objc_proto",
name = "santa_objc_proto",
copts = ["-fno-objc-arc"],
non_arc_srcs = ["Santa.pbobjc.m"],
protos = [":log_proto"],
protos = [":santa_proto"],
)
cc_library(
@@ -225,16 +226,6 @@ objc_library(
deps = [":SNTCommonEnums"],
)
objc_library(
name = "SNTXPCSyncdInterface",
srcs = ["SNTXPCSyncdInterface.m"],
hdrs = ["SNTXPCSyncdInterface.h"],
deps = [
":SNTCommonEnums",
":SNTStoredEvent",
],
)
objc_library(
name = "SNTXPCSyncServiceInterface",
srcs = ["SNTXPCSyncServiceInterface.m"],

View File

@@ -26,8 +26,6 @@
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// Enum defining actions that can be passed down the IODataQueue and in
// response methods.
typedef enum {
ACTION_UNSET = 0,
@@ -74,15 +72,9 @@ typedef struct santa_vnode_id_t {
bool operator==(const santa_vnode_id_t &rhs) const {
return fsid == rhs.fsid && fileid == rhs.fileid;
}
// This _must not_ be used for anything security-sensitive. It exists solely
// to make the msleep/wakeup calls easier.
uint64_t unsafe_simple_id() const {
return (((uint64_t)fsid << 32) | fileid);
}
#endif
} santa_vnode_id_t;
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
santa_vnode_id_t vnode_id;
@@ -93,18 +85,17 @@ typedef struct {
pid_t ppid;
char path[MAXPATHLEN];
char newpath[MAXPATHLEN];
char ttypath[MAXPATHLEN];
// For file events, this is the process name.
// For exec requests, this is the parent process name.
// While process names can technically be 4*MAXPATHLEN, that never
// actually happens, so only take MAXPATHLEN and throw away any excess.
char pname[MAXPATHLEN];
// For messages that originate from EndpointSecurity, this points to a copy of
// the message.
// This points to a copy of the original ES message.
void *es_message;
// For messages that originate from EndpointSecurity, this points to an
// NSArray of the arguments.
// This points to an NSArray of the process arguments.
void *args_array;
} santa_message_t;

View File

@@ -95,6 +95,21 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
SNTEventLogTypeProtobuf,
};
// The return status of a sync.
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
SNTSyncStatusTypeSuccess,
SNTSyncStatusTypePreflightFailed,
SNTSyncStatusTypeEventUploadFailed,
SNTSyncStatusTypeRuleDownloadFailed,
SNTSyncStatusTypePostflightFailed,
SNTSyncStatusTypeTooManySyncsInProgress,
SNTSyncStatusTypeMissingSyncBaseURL,
SNTSyncStatusTypeMissingMachineID,
SNTSyncStatusTypeDaemonTimeout,
SNTSyncStatusTypeSyncStarted,
SNTSyncStatusTypeUnknown,
};
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,

View File

@@ -49,6 +49,9 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
/// Get the logging level for this process.
LogLevel EffectiveLogLevel();
#ifdef __cplusplus
} // extern C
#endif

View File

@@ -19,12 +19,6 @@
#import <asl.h>
#import <pthread.h>
#ifdef DEBUG
static LogLevel logLevel = LOG_LEVEL_DEBUG;
#else
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
#endif
void syslogClientDestructor(void *arg) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -32,6 +26,21 @@ void syslogClientDestructor(void *arg) {
#pragma clang diagnostic pop
}
LogLevel EffectiveLogLevel() {
#ifdef DEBUG
static LogLevel logLevel = LOG_LEVEL_DEBUG;
#else
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
#endif
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([SNTConfigurator configurator].enableDebugLogging) {
logLevel = LOG_LEVEL_DEBUG;
}
});
return logLevel;
}
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
static BOOL useSyslog = NO;
static NSString *binaryName;
@@ -41,10 +50,6 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
dispatch_once(&pred, ^{
binaryName = [[NSProcessInfo processInfo] processName];
if ([SNTConfigurator configurator].enableDebugLogging) {
logLevel = LOG_LEVEL_DEBUG;
}
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
@@ -53,7 +58,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
}
});
if (logLevel < level) return;
if (EffectiveLogLevel() < level) return;
va_list args;
va_start(args, format);

View File

@@ -27,9 +27,9 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
default: typeStr = @"SNTMetricTypeUnknown"; break;
default: typeStr = [NSString stringWithFormat:@"SNTMetricTypeUnknown %ld", metricType]; break;
}
return [NSString stringWithFormat:@"%@ %ld", typeStr, metricType];
return typeStr;
}
/**

View File

@@ -45,10 +45,10 @@
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
[c incrementForFieldValues:@[ @"certificate" ]];
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
@"Counter not incremendted by 1");
@"Counter not incremented by 1");
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
@"Counter not incremendted by 3");
@"Counter not incremented by 3");
}
- (void)testExportNSDictionary {
@@ -630,39 +630,39 @@
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
@"expected" : @"SNTMetricTypeConstantBool 1"
@"expected" : @"SNTMetricTypeConstantBool"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
@"expected" : @"SNTMetricTypeConstantString 2"
@"expected" : @"SNTMetricTypeConstantString"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
@"expected" : @"SNTMetricTypeConstantInt64 3"
@"expected" : @"SNTMetricTypeConstantInt64"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
@"expected" : @"SNTMetricTypeConstantDouble 4"
@"expected" : @"SNTMetricTypeConstantDouble"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
@"expected" : @"SNTMetricTypeGaugeBool 5"
@"expected" : @"SNTMetricTypeGaugeBool"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
@"expected" : @"SNTMetricTypeGaugeString 6"
@"expected" : @"SNTMetricTypeGaugeString"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
@"expected" : @"SNTMetricTypeGaugeInt64 7"
@"expected" : @"SNTMetricTypeGaugeInt64"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
@"expected" : @"SNTMetricTypeGaugeDouble 8"
@"expected" : @"SNTMetricTypeGaugeDouble"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
@"expected" : @"SNTMetricTypeCounter 9"
@"expected" : @"SNTMetricTypeCounter"
}
];

View File

@@ -55,7 +55,6 @@
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)(void))reply;
@end

View File

@@ -20,18 +20,37 @@
@class SNTStoredEvent;
/// A block that reports the number of rules processed.
/// TODO(bur): Add more details about the sync.
typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
///
/// Protocol implemented by syncservice and utilized by daemon and ctl for communication with a
/// sync server.
///
@protocol SNTSyncServiceXPC
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)isFCMListening:(void (^)(BOOL))reply;
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply;
// The syncservice regularly syncs with a configured sync server. Use this method to sync out of
// band. The syncservice ensures syncs do not run concurrently.
//
// Pass an NSXPCListenerEndpoint whose associated NSXPCListener exports an object that implements
// the SNTSyncServiceLogReceiverXPC protocol. The caller will receive sync logs over this listener.
// This is required.
//
// Syncs are enqueued in order and executed serially. kMaxEnqueuedSyncs limits the number of syncs
// in the queue. If the queue is full calls to this method will be dropped and
// SNTSyncStatusTypeTooManySyncsInProgress will be passed into the reply block.
//
// Pass true to isClean to perform a clean sync, defaults to false.
//
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
isClean:(BOOL)cleanSync
reply:(void (^)(SNTSyncStatusType))reply;
// Spindown the syncservice. The syncservice will not automatically start back up.
// A new connection to the syncservice will bring it back up. This allows us to avoid running
// the syncservice needlessly when there is no configured sync server.
- (void)spindown;
@end
@interface SNTXPCSyncServiceInterface : NSObject
@@ -54,3 +73,11 @@ typedef void (^SNTFullSyncReplyBlock)(NSNumber *rulesProcessed);
+ (MOLXPCConnection *)configuredConnection;
@end
///
/// Protocol implemented by santactl sync and used to receive log messages from
/// the syncservice during a user initiated sync.
///
@protocol SNTSyncServiceLogReceiverXPC
- (void)didReceiveLog:(NSString *)log;
@end

View File

@@ -1,37 +0,0 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/common/SNTCommonEnums.h"
@class SNTStoredEvent;
/// Protocol implemented by santactl and utilized by santad
@protocol SNTSyncdXPC
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end
@interface SNTXPCSyncdInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)syncdInterface;
@end

View File

@@ -1,32 +0,0 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTStoredEvent.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postEventsToSyncServer:isFromBundle:)
argumentIndex:0
ofReply:NO];
return r;
}
@end

View File

@@ -54,6 +54,7 @@ macos_application(
"//Source/santactl": "MacOS",
"//Source/santabundleservice": "MacOS",
"//Source/santametricservice": "MacOS",
"//Source/santasyncservice": "MacOS",
"//Source/santad:com.google.santa.daemon": "Library/SystemExtensions",
},
app_icons = glob(["Resources/Images.xcassets/**"]),
@@ -66,7 +67,7 @@ macos_application(
],
entitlements = "Santa.app.entitlements",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -17,7 +17,7 @@
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -15,7 +15,7 @@
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="515" height="318"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="875"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="515" height="318"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -21,13 +21,13 @@
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" misplaced="YES" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="1728" height="1079"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="540" height="462"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="451" width="37" height="32"/>
<rect key="frame" x="16" y="434" width="37" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ -240,17 +240,16 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="153" y="35" width="114" height="23"/>
<rect key="frame" x="147" y="30" width="126" height="32"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<buttonCell key="cell" type="push" title="Open Event..." bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
@@ -285,18 +284,17 @@ DQ
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="277" y="33" width="112" height="25"/>
<rect key="frame" x="271" y="28" width="124" height="34"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
Gw
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>

View File

@@ -156,6 +156,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
// Otherwise abandon bundle hashing and display the blockable event.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[withController updateBlockNotification:event withBundleHash:nil];
LOGE(@"Timeout connecting to bundle service");
return;
}

View File

@@ -23,8 +23,17 @@ objc_library(
macos_command_line_application(
name = "santabundleservice",
bundle_id = "com.google.santa.bundleservice",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santabs_lib"],

View File

@@ -5,7 +5,7 @@ licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
objc_library(
@@ -49,6 +49,7 @@ objc_library(
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"//Source/santasyncservice:sync_lib",
"@FMDB",
@@ -66,7 +67,7 @@ macos_command_line_application(
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",

View File

@@ -19,4 +19,5 @@
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args;
@end

View File

@@ -41,6 +41,7 @@ REGISTER_COMMAND_NAME(@"metrics")
+ (NSString *)longHelpText {
return (@"Provides metrics about Santa's operation while it's running.\n"
@"Pass prefixes to filter list of metrics, if desired.\n"
@" Use --json to output in JSON format");
}
@@ -122,6 +123,26 @@ REGISTER_COMMAND_NAME(@"metrics")
[self prettyPrintMetricValues:normalizedMetrics[@"metrics"]];
}
- (NSDictionary *)filterMetrics:(NSDictionary *)metrics withArguments:(NSArray *)args {
NSMutableDictionary *outer = [metrics mutableCopy];
NSMutableDictionary *inner = [NSMutableDictionary dictionary];
__block BOOL hadFilter = NO;
[metrics[@"metrics"] enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
for (NSString *arg in args) {
if ([arg hasPrefix:@"-"]) continue;
hadFilter = YES;
if ([key hasPrefix:arg]) {
inner[key] = value;
}
}
}];
outer[@"metrics"] = inner;
return hadFilter ? outer : metrics;
}
- (void)runWithArguments:(NSArray *)arguments {
__block NSDictionary *metrics;
@@ -138,6 +159,8 @@ REGISTER_COMMAND_NAME(@"metrics")
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
}
metrics = [self filterMetrics:metrics withArguments:arguments];
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
exit(0);
}

View File

@@ -131,4 +131,26 @@
@"Metrics command command did not produce expected output");
}
- (void)testFiltering {
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSDictionary *metricDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
NSDictionary *filtered;
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[]];
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"], @"No filtering with no args");
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json" ]];
XCTAssertEqualObjects(metricDict[@"metrics"], filtered[@"metrics"],
@"No filtering with no metric args");
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"--json", @"/santa" ]];
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 3,
@"Expected filter of metrics with /santa to return 3 metrics");
filtered = [metricsCmd filterMetrics:metricDict withArguments:@[ @"/build", @"/santa" ]];
XCTAssertEqual(((NSDictionary *)filtered[@"metrics"]).count, 4,
@"Expected filter of metrics with /build and /santa to return 4 metrics");
}
@end

View File

@@ -82,7 +82,7 @@ REGISTER_COMMAND_NAME(@"status")
BOOL cachingEnabled = [configurator enableSysxCache];
// Kext status
// Cache status
__block uint64_t rootCacheCount = -1, nonRootCacheCount = -1;
if (cachingEnabled) {
dispatch_group_enter(group);
@@ -237,6 +237,7 @@ REGISTER_COMMAND_NAME(@"status")
if (cachingEnabled) {
printf(">>> Cache Info\n");
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
}
printf(">>> Database Info\n");

View File

@@ -18,14 +18,11 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
#import "Source/santasyncservice/SNTSyncManager.h"
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol>
@property MOLXPCConnection *listener;
@property SNTSyncManager *syncManager;
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol, SNTSyncServiceLogReceiverXPC>
@end
@implementation SNTCommandSync
@@ -38,9 +35,8 @@ REGISTER_COMMAND_NAME(@"sync")
return YES;
}
// Connect to santad while we are root, so that we pass the XPC authentication.
+ (BOOL)requiresDaemonConn {
return YES;
return NO; // We talk directly with the syncservice.
}
+ (NSString *)shortHelpText {
@@ -66,53 +62,38 @@ REGISTER_COMMAND_NAME(@"sync")
LOGE(@"Missing SyncBaseURL. Exiting.");
exit(1);
}
MOLXPCConnection *ss = [SNTXPCSyncServiceInterface configuredConnection];
ss.invalidationHandler = ^(void) {
LOGE(@"Failed to connect to the sync service.");
exit(1);
};
[ss resume];
BOOL daemon = [arguments containsObject:@"--daemon"];
self.syncManager = [[SNTSyncManager alloc] initWithDaemonConnection:self.daemonConn
isDaemon:daemon];
NSXPCListener *logListener = [NSXPCListener anonymousListener];
MOLXPCConnection *lr = [[MOLXPCConnection alloc] initServerWithListener:logListener];
lr.exportedObject = self;
lr.unprivilegedInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceLogReceiverXPC)];
[lr resume];
BOOL isClean = [NSProcessInfo.processInfo.arguments containsObject:@"--clean"];
[[ss remoteObjectProxy]
syncWithLogListener:logListener.endpoint
isClean:isClean
reply:^(SNTSyncStatusType status) {
if (status == SNTSyncStatusTypeTooManySyncsInProgress) {
[self didReceiveLog:@"Too many syncs in progress, try again later."];
}
exit((int)status);
}];
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil]];
if (!self.syncManager.daemon) return [self.syncManager fullSync];
[self syncdWithDaemonConnection:self.daemonConn];
// Do not return from this scope.
[[NSRunLoop mainRunLoop] run];
}
#pragma mark daemon methods
- (void)syncdWithDaemonConnection:(MOLXPCConnection *)daemonConn {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[MOLXPCConnection alloc] initServerWithListener:listener];
self.listener.privilegedInterface = [SNTXPCSyncdInterface syncdInterface];
self.listener.exportedObject = self.syncManager;
self.listener.acceptedHandler = ^{
LOGD(@"santad <--> santactl connections established");
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
// If santad is unloaded kill santactl
LOGD(@"exiting");
exit(0);
};
[self.listener resume];
// Tell daemon to connect back to the above listener.
[[daemonConn remoteObjectProxy] setSyncdListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
self.listener.invalidationHandler = nil;
[self.listener invalidate];
[self performSelectorInBackground:@selector(syncdWithDaemonConnection:) withObject:daemonConn];
}
[self.syncManager fullSyncSecondsFromNow:15];
/// Implement the SNTSyncServiceLogReceiverXPC protocol.
- (void)didReceiveLog:(NSString *)log {
printf("%s\n", log.UTF8String);
fflush(stdout);
}
@end

View File

@@ -10,7 +10,7 @@
>>> Metrics
Metric Name | /santa/rules
Description | Number of rules
Type | SNTMetricTypeGaugeInt64 7
Type | SNTMetricTypeGaugeInt64
Field | rule_type=binary
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
@@ -22,14 +22,14 @@
Metric Name | /proc/memory/resident_size
Description | The resident set size of this process
Type | SNTMetricTypeGaugeInt64 7
Type | SNTMetricTypeGaugeInt64
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 123456789
Metric Name | /santa/events
Description | Count of process exec events on the host
Type | SNTMetricTypeCounter 9
Type | SNTMetricTypeCounter
Field | rule_type=binary
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
@@ -41,28 +41,28 @@
Metric Name | /santa/using_endpoint_security_framework
Description | Is santad using the endpoint security framework
Type | SNTMetricTypeConstantBool 1
Type | SNTMetricTypeConstantBool
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Metric Name | /proc/birth_timestamp
Description | Start time of this santad instance, in microseconds since epoch
Type | SNTMetricTypeConstantInt64 3
Type | SNTMetricTypeConstantInt64
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1250999830800
Metric Name | /proc/memory/virtual_size
Description | The virtual memory size of this process
Type | SNTMetricTypeGaugeInt64 7
Type | SNTMetricTypeGaugeInt64
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 987654321
Metric Name | /build/label
Description | Software version running
Type | SNTMetricTypeConstantString 2
Type | SNTMetricTypeConstantString
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 20210809.0.1

View File

@@ -3,7 +3,7 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
licenses(["notice"])
@@ -93,7 +93,7 @@ objc_library(
deps = [
":event_logs_common",
"//Source/common:SNTMetricSet",
"//Source/common:log_objc_proto",
"//Source/common:santa_objc_proto",
],
)
@@ -184,7 +184,7 @@ objc_library(
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"//Source/santad:SNTApplicationCoreMetrics",
"@FMDB",
"@MOLXPCConnection",
@@ -248,7 +248,7 @@ macos_bundle(
],
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
@@ -280,7 +280,6 @@ santa_unit_test(
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"@MOLCodesignChecker",
"@MOLXPCConnection",
@@ -326,6 +325,9 @@ santa_unit_test(
"@MOLCertificate",
"@MOLCodesignChecker",
],
sdk_dylibs = [
"EndpointSecurity",
],
)
santa_unit_test(
@@ -440,7 +442,7 @@ santa_unit_test(
":EndpointSecurityTestLib",
":event_logs",
"//Source/common:SNTConfigurator",
"//Source/common:log_objc_proto",
"//Source/common:santa_objc_proto",
"@OCMock",
],
)

View File

@@ -14,6 +14,7 @@
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import <EndpointSecurity/EndpointSecurity.h>
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
@@ -29,6 +30,45 @@ static const NSUInteger kTransitiveRuleCullingThreshold = 500000;
// Consider transitive rules out of date if they haven't been used in six months.
static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABLE(macos(12.0)) {
// Note: This function uses API introduced in macOS 12, but we want to continue to support
// building in older environments. API Availability checks do not help for this use case,
// instead we use the following preprocessor macros to conditionally compile these API. The
// drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed
// on macOS 12 or later, the dynamic mute set will not be computed.
#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
// Create a temporary ES client in order to grab the default set of muted paths.
// TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created
es_client_t *client = NULL;
es_new_client_result_t ret = es_new_client(&client, ^(es_client_t *c, const es_message_t *m){
// noop
});
if (ret != ES_NEW_CLIENT_RESULT_SUCCESS) {
// Creating the client failed, so we cannot grab the current default mute set.
LOGE(@"Failed to create client to grab default muted paths");
return;
}
es_muted_paths_t *mps = NULL;
if (es_muted_paths_events(client, &mps) != ES_RETURN_SUCCESS) {
LOGE(@"Failed to obtain list of default muted paths.");
es_delete_client(client);
return;
}
for (size_t i = 0; i < mps->count; i++) {
// Only add literal paths, prefix paths would require recursive directory search
if (mps->paths[i].type == ES_MUTE_PATH_TYPE_LITERAL) {
[criticalPaths addObject:@(mps->paths[i].path.data)];
}
}
es_release_muted_paths(mps);
es_delete_client(client);
#endif
}
@interface SNTRuleTable ()
@property MOLCodesignChecker *santadCSInfo;
@property MOLCodesignChecker *launchdCSInfo;
@@ -42,32 +82,54 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
// ES on Monterey now has a default mute set of paths that are automatically applied to each ES
// client. This mute set contains most (not all) AUTH event types for some paths that were deemed
// system critical.
// Retain this list for < 12.0 versions of ES, but we should be able to rely on the paths muted by
// default (visible with es_muted_paths_events any time after connecting a new client and before
// modifying any of the mute state).
+ (NSArray *)criticalSystemBinaryPaths {
return @[
@"/usr/libexec/trustd",
@"/usr/libexec/xpcproxy",
@"/usr/libexec/amfid",
@"/usr/libexec/opendirectoryd",
@"/usr/libexec/runningboardd",
@"/usr/libexec/syspolicyd",
@"/usr/libexec/watchdogd",
@"/usr/libexec/cfprefsd",
@"/usr/sbin/securityd",
@"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd",
@"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer",
@"/usr/sbin/ocspd",
@"/usr/lib/dyld",
@"/Applications/Santa.app/Contents/MacOS/Santa",
@"/Applications/Santa.app/Contents/MacOS/santactl",
@"/Applications/Santa.app/Contents/MacOS/santabundleservice",
// This entry is for on <10.15 - on 10.15+ the binary is actually executed
// from a system-controlled path but will only ever be executed by
// the OS anyway.
@"/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon",
];
static dispatch_once_t onceToken;
static NSArray *criticalPaths = nil;
dispatch_once(&onceToken, ^{
// These paths have previously existed in the ES default mute set. They are hardcoded
// here in case grabbing the current default mute set fails, or if Santa is running on
// an OS that did not yet support this feature.
NSSet *fallbackDefaultMuteSet = [[NSSet alloc] initWithArray:@[
@"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer",
@"/System/Library/PrivateFrameworks/TCC.framework/Support/tccd",
@"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd",
@"/usr/sbin/cfprefsd",
@"/usr/sbin/securityd",
@"/usr/libexec/opendirectoryd",
@"/usr/libexec/sandboxd",
@"/usr/libexec/syspolicyd",
@"/usr/libexec/runningboardd",
@"/usr/libexec/amfid",
@"/usr/libexec/watchdogd",
]];
// This is a Santa-curated list of paths to check on startup. This list will be merged
// with the set of default muted paths from ES.
NSSet *santaDefinedCriticalPaths = [NSSet setWithArray:@[
@"/usr/libexec/trustd",
@"/usr/lib/dyld",
@"/usr/libexec/xpcproxy",
@"/usr/sbin/ocspd",
@"/Applications/Santa.app/Contents/MacOS/Santa",
@"/Applications/Santa.app/Contents/MacOS/santactl",
@"/Applications/Santa.app/Contents/MacOS/santabundleservice",
@"/Applications/Santa.app/Contents/MacOS/santametricservice",
@"/Applications/Santa.app/Contents/MacOS/santasyncservice",
]];
// Combine the fallback default mute set and Santa-curated set
NSMutableSet *superSet = [NSMutableSet setWithSet:fallbackDefaultMuteSet];
[superSet unionSet:santaDefinedCriticalPaths];
if (@available(macOS 12.0, *)) {
// Attempt to add the real default mute set
addPathsFromDefaultMuteSet(superSet);
}
criticalPaths = [superSet allObjects];
});
return criticalPaths;
}
- (void)setupSystemCriticalBinaries {

View File

@@ -29,21 +29,34 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
}
@implementation SNTCachingEndpointSecurityManager {
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
// Create 2 separate caches, mapping from the (filesysem + vnode ID) to a decision with a timestamp.
// The root cache is for decisions on the root volume, which can never be unmounted and the other
// is for executions from all other volumes. This cache will be emptied if any volume is unmounted.
SantaCache<santa_vnode_id_t, uint64_t> *_rootDecisionCache;
SantaCache<santa_vnode_id_t, uint64_t> *_nonRootDecisionCache;
uint64_t _rootVnodeID;
}
- (instancetype)init {
self = [super init];
if (self) {
// TODO(rah): Consider splitting into root/non-root cache
_decisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
_rootDecisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
_nonRootDecisionCache = new SantaCache<santa_vnode_id_t, uint64_t>();
// Store the filesystem ID of the root vnode for split-cache usage.
// If the stat fails for any reason _rootVnodeID will be 0 and all decisions will be in a single cache.
struct stat rootStat;
if (stat("/", &rootStat) == 0) {
_rootVnodeID = (uint64_t)rootStat.st_dev;
}
}
return self;
}
- (void)dealloc {
if (_decisionCache) delete _decisionCache;
if (_rootDecisionCache) delete _rootDecisionCache;
if (_nonRootDecisionCache) delete _nonRootDecisionCache;
}
- (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) {
@@ -120,6 +133,7 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
- (void)addToCache:(santa_vnode_id_t)identifier
decision:(santa_action_t)decision
currentTicks:(uint64_t)microsecs {
auto _decisionCache = [self cacheForVnodeID:identifier];
switch (decision) {
case ACTION_REQUEST_BINARY:
_decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);
@@ -150,25 +164,21 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
}
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly API_AVAILABLE(macos(10.15)) {
_decisionCache->clear();
_nonRootDecisionCache->clear();
if (!nonRootOnly) _rootDecisionCache->clear();
if (!self.connectionEstablished) return YES; // if not connected, there's nothing to flush.
return es_clear_cache(self.client) == ES_CLEAR_CACHE_RESULT_SUCCESS;
}
- (NSArray<NSNumber *> *)cacheCounts {
return @[ @(_decisionCache->count()), @(0) ];
}
- (NSArray<NSNumber *> *)cacheBucketCount {
// TODO: add this, maybe.
return nil;
return @[ @(_rootDecisionCache->count()), @(_nonRootDecisionCache->count()) ];
}
- (santa_action_t)checkCache:(santa_vnode_id_t)vnodeID {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
uint64_t cache_val = _decisionCache->get(vnodeID);
uint64_t cache_val = [self cacheForVnodeID:vnodeID]->get(vnodeID);
if (cache_val == 0) return result;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
@@ -179,7 +189,7 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (500 * 100000); // kMaxCacheDenyTimeMilliseconds
if (expiry_time < GetCurrentUptime()) {
_decisionCache->remove(vnodeID);
[self cacheForVnodeID:vnodeID]->remove(vnodeID);
return ACTION_UNSET;
}
}
@@ -188,9 +198,13 @@ uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const &t) {
}
- (kern_return_t)removeCacheEntryForVnodeID:(santa_vnode_id_t)vnodeID {
_decisionCache->remove(vnodeID);
[self cacheForVnodeID:vnodeID]->remove(vnodeID);
// TODO(rah): Look at a replacement for wakeup(), maybe NSCondition
return 0;
}
- (SantaCache<santa_vnode_id_t, uint64_t> *)cacheForVnodeID:(santa_vnode_id_t)vnodeID {
return (vnodeID.fsid == _rootVnodeID || _rootVnodeID == 0) ? _rootDecisionCache : _nonRootDecisionCache;
}
@end

View File

@@ -45,10 +45,8 @@ static const pid_t PID_MAX = 99999;
if (self) {
// To avoid nil deref from es_events arriving before listenForDecisionRequests or
// listenForLogRequests in the MockEndpointSecurity testing util.
_decisionCallback = ^(santa_message_t) {
};
_logCallback = ^(santa_message_t) {
};
_decisionCallback = ^(santa_message_t) {};
_logCallback = ^(santa_message_t) {};
[self establishClient];
_prefixTree = new SNTPrefixTree();
_esAuthQueue =
@@ -114,9 +112,10 @@ static const pid_t PID_MAX = 99999;
// Create a transitive rule if the file was modified by a running compiler
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
buffer:sm.path
size:sizeof(sm.path)];
BOOL truncated =
[SNTEndpointSecurityManager populateBufferFromESFile:m->event.close.target
buffer:sm.path
size:sizeof(sm.path)];
if (truncated) {
LOGE(@"CLOSE: error creating transitive rule, the path is truncated: path=%s pid=%d",
sm.path, pid);
@@ -182,7 +181,12 @@ static const pid_t PID_MAX = 99999;
case ES_EVENT_TYPE_NOTIFY_UNMOUNT: {
// Flush the non-root cache - the root disk cannot be unmounted
// so it isn't necessary to flush its cache.
[self flushCacheNonRootOnly:YES];
//
// Flushing the cache calls back into ES. We need to perform this off the handler thread
// otherwise we could potentially deadlock.
dispatch_async(self.esAuthQueue, ^() {
[self flushCacheNonRootOnly:YES];
});
// Skip all other processing
return;
@@ -288,6 +292,10 @@ static const pid_t PID_MAX = 99999;
targetFile = m->event.exec.target->executable;
targetProcess = m->event.exec.target;
callback = self.decisionCallback;
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->tty
buffer:sm.ttypath
size:sizeof(sm.ttypath)];
break;
}
case ES_EVENT_TYPE_NOTIFY_EXEC: {
@@ -544,6 +552,7 @@ static const pid_t PID_MAX = 99999;
// Returns YES if the path was truncated.
// The populated buffer will be NUL terminated.
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
if (file == NULL) return NO;
return [SNTEndpointSecurityManager populateBufferFromString:file->path.data
buffer:buffer
size:size];

View File

@@ -13,7 +13,6 @@
/// limitations under the License.
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -26,6 +25,7 @@
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCMetricServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
@@ -34,6 +34,7 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
#import "Source/santad/Logs/SNTEventLog.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import "Source/santad/SNTCompilerController.h"
#import "Source/santad/SNTDaemonControlController.h"
#import "Source/santad/SNTDatabaseController.h"
@@ -48,9 +49,9 @@
@property SNTDeviceManager *deviceManager;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@property MOLXPCConnection *metricsConnection;
@property dispatch_source_t metricsTimer;
@property SNTSyncdQueue *syncdQueue;
@end
@implementation SNTApplication
@@ -114,12 +115,8 @@
[self.eventProvider fileModificationPrefixFilterAdd:[configurator fileChangesPrefixFilters]];
});
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
self.notQueue = [[SNTNotificationQueue alloc] init];
self.syncdQueue = [[SNTSyncdQueue alloc] init];
// Listen for actionable config changes.
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
@@ -160,7 +157,7 @@
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
notificationQueue:self.notQueue
syncdQueue:syncdQueue];
syncdQueue:self.syncdQueue];
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceID]];
@@ -178,9 +175,9 @@
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:self.notQueue
syncdQueue:syncdQueue];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
syncdQueue:self.syncdQueue];
// Establish a connection with the sync service if a sync server exists.
[self establishSyncServiceConnection];
if (!_execController) return nil;
@@ -313,27 +310,20 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
dispatch_source_cancel(_metricsTimer);
}
- (void)startSyncd {
- (void)establishSyncServiceConnection {
// The syncBaseURL check is here to stop retrying if the sync server is removed.
// See -[syncBaseURLDidChange:] for more info.
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self stopSyncd];
self.syncdPID = fork();
if (self.syncdPID == -1) {
LOGI(@"Failed to fork");
self.syncdPID = 0;
} else if (self.syncdPID == 0) {
// The santactl executable will drop privileges just after the XPC
// connection has been estabilished; this is done this way so that
// the XPC authentication can occur
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
LOGI(@"santactl started with pid: %i", self.syncdPID);
}
- (void)stopSyncd {
if (!self.syncdPID) return;
int ret = kill(self.syncdPID, SIGKILL);
LOGD(@"kill(%i, 9) = %i", self.syncdPID, ret);
self.syncdPID = 0;
MOLXPCConnection *ss = [SNTXPCSyncServiceInterface configuredConnection];
// This will handle retying connection establishment if there are issues with the service
// during initialization (missing binary, malformed plist, bad code signature, etc.).
// Once those issues are resolved the connection will establish.
// This will also handle reestablishment if the service crashes or is killed.
ss.invalidationHandler = ^(void) {
[self establishSyncServiceConnection];
};
[ss resume]; // If there are issues establishing the connection resume will block for 2 seconds.
self.syncdQueue.syncConnection = ss;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
@@ -418,14 +408,15 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
- (void)syncBaseURLDidChange:(NSURL *)syncBaseURL {
if (syncBaseURL) {
LOGI(@"Starting santactl with new SyncBaseURL: %@", syncBaseURL);
LOGI(@"Establishing a new sync service connection with SyncBaseURL: %@", syncBaseURL);
[NSObject cancelPreviousPerformRequestsWithTarget:[SNTConfigurator configurator]
selector:@selector(clearSyncState)
object:nil];
[self startSyncd];
[[self.syncdQueue.syncConnection remoteObjectProxy] spindown];
[self establishSyncServiceConnection];
} else {
LOGI(@"SyncBaseURL removed, killing santactl pid: %i", self.syncdPID);
[self stopSyncd];
LOGI(@"SyncBaseURL removed, spinning down sync service");
[[self.syncdQueue.syncConnection remoteObjectProxy] spindown];
// Keep the syncState active for 10 min in case com.apple.ManagedClient is flapping.
[[SNTConfigurator configurator] performSelector:@selector(clearSyncState)
withObject:nil

View File

@@ -26,7 +26,7 @@
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCBundleServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
@@ -272,26 +272,8 @@ double watchdogRAMPeak = 0;
#pragma mark syncd Ops
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
// Only allow one active syncd connection
if (self.syncdQueue.syncdConnection) return;
MOLXPCConnection *c = [[MOLXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
c.invalidationHandler = ^{
[self.syncdQueue stopSyncingEvents];
[self.syncdQueue.syncdConnection invalidate];
self.syncdQueue.syncdConnection = nil;
if (self.syncdQueue.invalidationHandler) self.syncdQueue.invalidationHandler();
};
c.acceptedHandler = ^{
[self.syncdQueue startSyncingEvents];
};
[c resume];
self.syncdQueue.syncdConnection = c;
}
- (void)pushNotifications:(void (^)(BOOL))reply {
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
[self.syncdQueue.syncConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
reply(response);
}];
}

View File

@@ -175,7 +175,7 @@ static NSString *const kPrinterProxyPostMonterey =
if (action == ACTION_RESPOND_ALLOW) {
[[SNTEventLog logger] cacheDecision:cd];
} else {
ttyPath = [self ttyPathForPID:message.ppid];
ttyPath = @(message.ttypath);
}
// Upgrade the action to ACTION_RESPOND_ALLOW_COMPILER when appropriate, because we want the
@@ -324,19 +324,6 @@ static NSString *const kPrinterProxyPostMonterey =
return proxyInfo;
}
- (NSString *)ttyPathForPID:(pid_t)pid {
if (pid < 2) return nil; // don't bother even looking for launchd.
struct proc_bsdinfo taskInfo = {};
if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &taskInfo, sizeof(taskInfo)) < 1) return nil;
// 16-bytes here is for future-proofing. Currently kern.tty.ptmx_max is
// limited to 999 so 12 bytes should be enough.
char devPath[16] = "/dev/";
snprintf(devPath, 16, "/dev/%s", devname(taskInfo.e_tdev, S_IFCHR));
return @(devPath);
}
- (void)printMessage:(NSString *)msg toTTY:(NSString *)path {
int fd = open(path.UTF8String, O_WRONLY | O_NOCTTY);
write(fd, msg.UTF8String, msg.length);

View File

@@ -21,9 +21,7 @@
@interface SNTSyncdQueue : NSObject
@property(nonatomic) MOLXPCConnection *syncdConnection;
@property(copy) void (^invalidationHandler)(void);
@property(copy) void (^acceptedHandler)(void);
@property(nonatomic) MOLXPCConnection *syncConnection;
- (void)addEvents:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle;
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply;

View File

@@ -18,7 +18,7 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
@interface SNTSyncdQueue ()
@property NSCache<NSString *, NSDate *> *uploadBackoff;
@@ -45,15 +45,14 @@
NSString *hash = isFromBundle ? first.fileBundleHash : first.fileSHA256;
if (![self backoffForPrimaryHash:hash]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy postEventsToSyncServer:events
isFromBundle:isFromBundle];
[self.syncConnection.remoteObjectProxy postEventsToSyncServer:events fromBundle:isFromBundle];
}];
}
- (void)addBundleEvent:(SNTStoredEvent *)event reply:(void (^)(SNTBundleEventAction))reply {
if (![self backoffForPrimaryHash:event.fileBundleHash]) return;
[self dispatchBlockOnSyncdQueue:^{
[self.syncdConnection.remoteObjectProxy
[self.syncConnection.remoteObjectProxy
postBundleEventToSyncServer:event
reply:^(SNTBundleEventAction action) {
// Remove the backoff entry for the initial block event. The same

View File

@@ -3,7 +3,7 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
licenses(["notice"])

View File

@@ -2,7 +2,7 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
licenses(["notice"])

View File

@@ -2,7 +2,7 @@ load("//:helper.bzl", "santa_unit_test")
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
licenses(["notice"])

View File

@@ -5,7 +5,7 @@ licenses(["notice"])
package(
default_visibility = ["//:santa_package_group"],
features = ["layering_check"],
features = ["-layering_check"],
)
objc_library(
@@ -23,10 +23,16 @@ objc_library(
srcs = [
"NSData+Zlib.h",
"NSData+Zlib.m",
"SNTPushNotifications.h",
"SNTPushNotifications.m",
"SNTPushNotificationsTracker.h",
"SNTPushNotificationsTracker.m",
"SNTSyncConstants.h",
"SNTSyncConstants.m",
"SNTSyncEventUpload.h",
"SNTSyncEventUpload.m",
"SNTSyncLogging.h",
"SNTSyncLogging.m",
"SNTSyncManager.m",
"SNTSyncPostflight.h",
"SNTSyncPostflight.m",
@@ -43,10 +49,11 @@ objc_library(
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
":broadcaster_lib",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"@MOLAuthenticatingURLSession",
"@MOLXPCConnection",
],
@@ -60,10 +67,16 @@ santa_unit_test(
srcs = [
"NSData+Zlib.h",
"NSData+Zlib.m",
"SNTPushNotifications.h",
"SNTPushNotifications.m",
"SNTPushNotificationsTracker.h",
"SNTPushNotificationsTracker.m",
"SNTSyncConstants.h",
"SNTSyncConstants.m",
"SNTSyncEventUpload.h",
"SNTSyncEventUpload.m",
"SNTSyncLogging.h",
"SNTSyncLogging.m",
"SNTSyncPostflight.h",
"SNTSyncPostflight.m",
"SNTSyncPreflight.h",
@@ -83,6 +96,7 @@ santa_unit_test(
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
":broadcaster_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
@@ -91,13 +105,22 @@ santa_unit_test(
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLXPCConnection",
"@OCMock",
],
)
objc_library(
name = "broadcaster_lib",
srcs = ["SNTSyncBroadcaster.m"],
hdrs = ["SNTSyncBroadcaster.h"],
deps = [
"//Source/common:SNTXPCSyncServiceInterface",
"@MOLXPCConnection",
],
)
objc_library(
name = "santass_lib",
srcs = [
@@ -106,7 +129,10 @@ objc_library(
"main.m",
],
deps = [
":sync_lib",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTLogging",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncServiceInterface",
"@MOLCodesignChecker",
"@MOLXPCConnection",
@@ -116,8 +142,17 @@ objc_library(
macos_command_line_application(
name = "santasyncservice",
bundle_id = "com.google.santa.syncservice",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santass_lib"],

View File

@@ -0,0 +1,37 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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>
@protocol SNTPushNotificationsDelegate
- (void)sync;
- (void)syncSecondsFromNow:(uint64_t)seconds;
- (void)ruleSync;
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds;
- (void)preflightSync;
@end
@class SNTSyncState;
@class SNTSyncFCM;
@interface SNTPushNotifications : NSObject
- (void)listenWithSyncState:(SNTSyncState *)syncState;
- (void)stop;
@property(weak) id<SNTPushNotificationsDelegate> delegate;
@property(readonly) BOOL isConnected;
@property(readonly) NSString *token;
@property(readonly) NSUInteger pushNotificationsFullSyncInterval;
@end

View File

@@ -0,0 +1,189 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/santasyncservice/SNTPushNotifications.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncFCM.h"
#import "Source/santasyncservice/SNTSyncState.h"
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@interface SNTPushNotifications ()
@property SNTSyncFCM *FCMClient;
@property NSString *token;
@property NSUInteger pushNotificationsFullSyncInterval;
@property NSUInteger pushNotificationsGlobalRuleSyncDeadline;
@end
@implementation SNTPushNotifications
#pragma mark push notification methods
- (instancetype)init {
self = [super init];
if (self) {
_pushNotificationsFullSyncInterval = kDefaultPushNotificationsFullSyncInterval;
_pushNotificationsGlobalRuleSyncDeadline = kDefaultPushNotificationsGlobalRuleSyncDeadline;
}
return self;
}
- (void)listenWithSyncState:(SNTSyncState *)syncState {
self.pushNotificationsFullSyncInterval = syncState.pushNotificationsFullSyncInterval;
self.pushNotificationsGlobalRuleSyncDeadline = syncState.pushNotificationsGlobalRuleSyncDeadline;
if ([self.token isEqualToString:syncState.pushNotificationsToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.token = t;
[self.delegate preflightSync];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.token = nil;
[self.delegate syncSecondsFromNow:kDefaultFullSyncInterval];
};
[self.FCMClient connect];
}
- (void)stop {
[self.FCMClient disconnect];
self.FCMClient = nil;
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// We assume that the incoming FCM message contains name of binary/bundle and a hash. Rule count
// info for bundles will be sent out later with the rules themselves. If the message is related
// to a bundle, the hash is a bundle hash, otherwise it is just a hash for a single binary.
// For later use, we store a mapping of bundle/binary hash to a dictionary containing the
// binary/bundle name so we can send out relevant notifications once the rules are actually
// downloaded & added to local database. We use a dictionary value so that we can later add a
// count field when we start downloading the rules and receive the count information.
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[[SNTPushNotificationsTracker tracker] addNotification:[@{kFileName : fileName} mutableCopy]
forHash:fileHash];
}
LOGD(@"Push notification action '%@' received", action);
if ([action isEqualToString:kFullSync] || [action isEqualToString:kConfigSync] ||
[action isEqualToString:kLogSync]) {
[self.delegate sync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
[self.delegate ruleSync];
} else {
uint32_t delaySeconds =
arc4random_uniform((uint32_t)self.pushNotificationsGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self.delegate ruleSyncSecondsFromNow:delaySeconds];
}
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
if (!messageData) {
LOGD(@"Unable to parse push notification message data");
return nil;
}
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
- (BOOL)isConnected {
return self.FCMClient.isConnected;
}
@end

View File

@@ -0,0 +1,31 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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>
// SNTPushNotificationsTracker stores info from push notification messages. The binary/bundle hash
// is used as a key mapping to values that are themselves dictionaries. These dictionary values
// contain the name of the binary/bundle and a count of associated binary rules.
@interface SNTPushNotificationsTracker : NSObject
// Retrieve an initialized singleton SNTPushNotificationsTracker object.
// Use this instead of init.
+ (instancetype)tracker;
- (void)addNotification:(NSDictionary *)notification forHash:(NSString *)hash;
- (void)removeNotificationsForHashes:(NSArray<NSString *> *)hashes;
- (void)decrementPendingRulesForHash:(NSString *)hash totalRuleCount:(NSNumber *)totalRuleCount;
- (NSDictionary *)all;
@end

View File

@@ -0,0 +1,99 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/common/SNTLogging.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
@interface SNTPushNotificationsTracker ()
@property dispatch_queue_t notificationsQueue;
@property NSMutableDictionary *notifications;
@end
@implementation SNTPushNotificationsTracker
- (instancetype)init {
self = [super init];
if (self) {
_notifications = [NSMutableDictionary dictionary];
_notificationsQueue =
dispatch_queue_create("com.google.santa.syncservice.notifications", DISPATCH_QUEUE_SERIAL);
}
return self;
}
+ (instancetype)tracker {
static SNTPushNotificationsTracker *tracker;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
tracker = [[SNTPushNotificationsTracker alloc] init];
});
return tracker;
}
- (void)addNotification:(NSDictionary *)notification forHash:(NSString *)hash {
dispatch_async(self.notificationsQueue, ^() {
// Don't let notifications pile up. In most cases there will only be a single entry pending. It
// is possible for notifications to make it here but not be displayed. The TODO below is to
// address this.
// TODO(bur): Add better guaranties for displaying notifications. This will involve checking the
// rules.db to see if the rule associated with the notification has been applied.
if (self.notifications.count > 16) {
LOGE(@"Push notifications are not being processed. Dropping pending notifications.");
[self.notifications removeAllObjects];
}
self.notifications[hash] = notification;
});
}
- (void)removeNotificationsForHashes:(NSArray<NSString *> *)hashes {
dispatch_async(self.notificationsQueue, ^() {
[self.notifications removeObjectsForKeys:hashes];
});
}
- (void)decrementPendingRulesForHash:(NSString *)hash totalRuleCount:(NSNumber *)totalRuleCount {
dispatch_async(self.notificationsQueue, ^() {
NSMutableDictionary *notifier = self.notifications[hash];
if (notifier) {
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining) { // bundle rule with existing count
// If the primary hash already has an associated count field, just decrement it.
notifier[kFileBundleBinaryCount] = @([remaining intValue] - 1);
} else if (totalRuleCount) { // bundle rule seen for first time
// Downloaded rules including count information are associated with bundles.
// The first time we see a rule for a given bundle hash, add a count field with an
// initial value equal to the number of associated rules, then decrement this value by 1
// to account for the rule that we've just downloaded.
notifier[kFileBundleBinaryCount] = @([totalRuleCount intValue] - 1);
} else { // non-bundle binary rule
// Downloaded rule had no count information, meaning it is a singleton non-bundle rule.
// Therefore there are no more rules associated with this hash to download.
notifier[kFileBundleBinaryCount] = @0;
}
}
});
}
- (NSDictionary *)all {
__block NSDictionary *d;
dispatch_sync(self.notificationsQueue, ^() {
d = self.notifications;
});
return d;
}
@end

View File

@@ -0,0 +1,33 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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>
@class MOLXPCConnection;
// A small class to keep track of and send messages to active listeners.
@interface SNTSyncBroadcaster : NSObject
// Retrieve an initialized singleton SNTSyncBroadcaster object.
// Use this instead of init.
+ (instancetype)broadcaster;
- (void)addLogListener:(MOLXPCConnection *)logListener;
- (void)removeLogListener:(MOLXPCConnection *)logListener;
- (void)broadcastToLogListeners:(NSString *)log;
// Blocks until all the currently enqueued (up to this point) logs from -[broadcastToLogListeners:]
// are sent.
- (void)barrier;
@end

View File

@@ -0,0 +1,73 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/santasyncservice/SNTSyncBroadcaster.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTXPCSyncServiceInterface.h"
@interface SNTSyncBroadcaster ()
@property NSMutableArray *logListeners;
@property dispatch_queue_t broadcastQueue;
@end
@implementation SNTSyncBroadcaster
- (instancetype)init {
self = [super init];
if (self) {
_logListeners = [NSMutableArray array];
_broadcastQueue =
dispatch_queue_create("com.google.santa.syncservice.broadcast", DISPATCH_QUEUE_SERIAL);
}
return self;
}
+ (instancetype)broadcaster {
static SNTSyncBroadcaster *sharedBroadcaster;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedBroadcaster = [[SNTSyncBroadcaster alloc] init];
});
return sharedBroadcaster;
}
- (void)addLogListener:(MOLXPCConnection *)logListener {
dispatch_async(self.broadcastQueue, ^() {
[self.logListeners addObject:logListener];
});
}
- (void)removeLogListener:(MOLXPCConnection *)logListener {
dispatch_async(self.broadcastQueue, ^() {
[self.logListeners removeObject:logListener];
});
}
- (void)broadcastToLogListeners:(NSString *)log {
dispatch_async(self.broadcastQueue, ^() {
for (MOLXPCConnection *ll in self.logListeners) {
[[ll remoteObjectProxy] didReceiveLog:log];
}
});
}
- (void)barrier {
dispatch_sync(self.broadcastQueue, ^() {
return;
});
}
@end

View File

@@ -62,10 +62,12 @@ extern NSString *const kDecisionAllowUnknown;
extern NSString *const kDecisionAllowBinary;
extern NSString *const kDecisionAllowCertificate;
extern NSString *const kDecisionAllowScope;
extern NSString *const kDecisionAllowTeamID;
extern NSString *const kDecisionBlockUnknown;
extern NSString *const kDecisionBlockBinary;
extern NSString *const kDecisionBlockCertificate;
extern NSString *const kDecisionBlockScope;
extern NSString *const kDecisionBlockTeamID;
extern NSString *const kDecisionUnknown;
extern NSString *const kDecisionBundleBinary;
extern NSString *const kLoggedInUsers;
@@ -132,5 +134,5 @@ extern const NSUInteger kDefaultEventBatchSize;
/// Are represented in seconds
///
extern const NSUInteger kDefaultFullSyncInterval;
extern const NSUInteger kDefaultFCMFullSyncInterval;
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;
extern const NSUInteger kDefaultPushNotificationsFullSyncInterval;
extern const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline;

View File

@@ -63,10 +63,12 @@ NSString *const kDecisionAllowUnknown = @"ALLOW_UNKNOWN";
NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
NSString *const kDecisionAllowTeamID = @"ALLOW_TEAMID";
NSString *const kDecisionBlockUnknown = @"BLOCK_UNKNOWN";
NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
NSString *const kDecisionBlockTeamID = @"BLOCK_TEAMID";
NSString *const kDecisionUnknown = @"UNKNOWN";
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
NSString *const kLoggedInUsers = @"logged_in_users";
@@ -125,5 +127,5 @@ NSString *const kLogSync = @"log_sync";
const NSUInteger kDefaultEventBatchSize = 50;
const NSUInteger kDefaultFullSyncInterval = 600;
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;
const NSUInteger kDefaultPushNotificationsFullSyncInterval = 14400;
const NSUInteger kDefaultPushNotificationsGlobalRuleSyncDeadline = 600;

View File

@@ -23,6 +23,7 @@
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/NSData+Zlib.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTSyncEventUpload
@@ -60,7 +61,7 @@
// A list of bundle hashes that require their related binary events to be uploaded.
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
LOGI(@"Uploaded %lu events", uploadEvents.count);
SLOGI(@"Uploaded %lu events", uploadEvents.count);
}
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
@@ -98,12 +99,14 @@
ADDKEY(newEvent, kDecision, kDecisionAllowCertificate);
break;
case SNTEventStateAllowScope: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
case SNTEventStateAllowTeamID: ADDKEY(newEvent, kDecision, kDecisionAllowTeamID); break;
case SNTEventStateBlockUnknown: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
case SNTEventStateBlockBinary: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
case SNTEventStateBlockCertificate:
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateBlockTeamID: ADDKEY(newEvent, kDecision, kDecisionBlockTeamID); break;
case SNTEventStateBundleBinary:
ADDKEY(newEvent, kDecision, kDecisionBundleBinary);
[newEvent removeObjectForKey:kExecutionTime];

View File

@@ -0,0 +1,49 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/common/SNTLogging.h"
void logSyncMessage(LogLevel level, NSString *format, ...)
__attribute__((format(__NSString__, 2, 3)));
///
/// Send logs to the standard pipeline AND to any active sync listeners.
/// Intended for use by the syncservice to send logs back to santactl instances.
/// LOG*() and logSyncMessage() both end up using a va_list which is single use. We are calling
/// both routines in this macro so they each get a copy of __VA_ARGS__.
///
/// TODO(bur): SLOGD() is temporarily set to LOG_LEVEL_INFO. Once santactl sync supports the
/// --debug flag, move this back to LOG_LEVEL_DEBUG. These debug logs are helpful when
/// troubleshooting sync issues with users, so let's opt to always log them for now.
///
#define SLOGD(logFormat, ...) \
do { \
LOGD(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_INFO, logFormat, ##__VA_ARGS__); \
} while (0)
#define SLOGI(logFormat, ...) \
do { \
LOGI(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_INFO, logFormat, ##__VA_ARGS__); \
} while (0)
#define SLOGW(logFormat, ...) \
do { \
LOGW(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_WARN, logFormat, ##__VA_ARGS__); \
} while (0)
#define SLOGE(logFormat, ...) \
do { \
LOGE(logFormat, ##__VA_ARGS__); \
logSyncMessage(LOG_LEVEL_ERROR, logFormat, ##__VA_ARGS__); \
} while (0)

View File

@@ -0,0 +1,32 @@
/// Copyright 2022 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/santasyncservice/SNTSyncLogging.h"
#include "Source/common/SNTLogging.h"
#import "Source/santasyncservice/SNTSyncBroadcaster.h"
void logSyncMessage(LogLevel level, NSString *format, ...) {
static LogLevel logLevel = LOG_LEVEL_DEBUG;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
logLevel = EffectiveLogLevel();
});
if (logLevel < level) return;
va_list args;
va_start(args, format);
NSMutableString *s = [[NSMutableString alloc] initWithFormat:format arguments:args];
va_end(args);
[[SNTSyncBroadcaster broadcaster] broadcastToLogListeners:s];
}

View File

@@ -14,16 +14,14 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/common/SNTXPCSyncServiceInterface.h"
@class MOLXPCConnection;
///
/// Handles push notifications and periodic syncing with a sync server.
///
@interface SNTSyncManager : NSObject <SNTSyncdXPC>
@property(readonly, nonatomic) BOOL daemon;
@interface SNTSyncManager : NSObject
///
/// Use the designated initializer initWithDaemonConnection:isDaemon:
@@ -34,22 +32,42 @@
/// Designated initializer.
///
/// @param daemonConn A connection to santad.
/// @param daemon Set to YES if periodic syncing should occur.
/// Set to NO if a single sync should be performed. NO is default.
///
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn
isDaemon:(BOOL)daemon NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn NS_DESIGNATED_INITIALIZER;
///
/// Perform a full sync immediately. Non-blocking.
/// If a full sync is already running new requests will be dropped.
/// Perform a sync immediately. Non-blocking.
/// If a sync is already running new requests will be dropped.
///
- (void)fullSync;
- (void)sync;
///
/// Perform a full sync seconds from now. Non-blocking.
/// If a full sync is already running new requests will be dropped.
/// Perform a sync seconds from now. Non-blocking.
/// If a sync is already running new requests will be dropped.
///
- (void)fullSyncSecondsFromNow:(uint64_t)seconds;
- (void)syncSecondsFromNow:(uint64_t)seconds;
///
/// Perform an out of band sync.
///
/// Syncs are enqueued in order and executed serially. kMaxEnqueuedSyncs limits the number of syncs
/// in the queue. If the queue is full calls to this method will be dropped and
/// SNTSyncStatusTypeTooManySyncsInProgress will be passed into the reply block.
///
/// The SNTSyncStatusTypeSyncStarted will be passed into the reply block when the sync starts. The
/// reply block will be called again with a SNTSyncStatusType when the sync has completed or
/// failed.
///
/// Pass true to isClean to perform a clean sync, defaults to false.
///
- (void)syncAndMakeItClean:(BOOL)clean withReply:(void (^)(SNTSyncStatusType))reply;
///
/// Handle SNTSyncServiceXPC messages forwarded from SNTSyncService.
///
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)isFromBundle;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end

View File

@@ -24,52 +24,35 @@
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/santasyncservice/SNTPushNotifications.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncEventUpload.h"
#import "Source/santasyncservice/SNTSyncFCM.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncPostflight.h"
#import "Source/santasyncservice/SNTSyncPreflight.h"
#import "Source/santasyncservice/SNTSyncRuleDownload.h"
#import "Source/santasyncservice/SNTSyncState.h"
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
static const uint8_t kMaxEnqueuedSyncs = 2;
@interface SNTSyncManager () {
@interface SNTSyncManager () <SNTPushNotificationsDelegate> {
SCNetworkReachabilityRef _reachability;
}
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
// allowlistNotifications dictionary stores info from FCM messages. The binary/bundle hash is used
// as a key mapping to values that are themselves dictionaries. These dictionary values contain the
// name of the binary/bundle and a count of associated binary rules.
@property(nonatomic) NSMutableDictionary *allowlistNotifications;
// allowlistNotificationQueue is used to serialize access to the allowlistNotifications dictionary.
@property(nonatomic) NSOperationQueue *allowlistNotificationQueue;
@property NSUInteger fullSyncInterval;
@property NSUInteger FCMFullSyncInterval;
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property SNTSyncFCM *FCMClient;
@property NSString *FCMToken;
@property(nonatomic, readonly) dispatch_queue_t syncQueue;
@property(nonatomic, readonly) dispatch_semaphore_t syncLimiter;
@property(nonatomic) MOLXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@property(nonatomic) BOOL reachable;
@property SNTPushNotifications *pushNotifications;
@property NSUInteger eventBatchSize;
@end
// Called when the network state changes
@@ -90,45 +73,26 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
#pragma mark init
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
- (instancetype)initWithDaemonConnection:(MOLXPCConnection *)daemonConn {
self = [super init];
if (self) {
_daemonConn = daemonConn;
_daemon = daemon;
_pushNotifications = [[SNTPushNotifications alloc] init];
_pushNotifications.delegate = self;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.FCMFullSyncInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
[self unlockAction:kFullSync];
[self rescheduleTimerQueue:self.fullSyncTimer
secondsFromNow:_pushNotifications.pushNotificationsFullSyncInterval];
[self syncAndMakeItClean:NO withReply:NULL];
}];
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
dispatch_source_set_timer(self.ruleSyncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER,
0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.allowlistNotifications = self.allowlistNotifications;
syncState.allowlistNotificationQueue = self.allowlistNotificationQueue;
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
LOGE(@"Rule download failed");
}
self.targetedRuleSync = NO;
[self unlockAction:kRuleSync];
[self ruleSyncImpl];
}];
_dispatchLock = [[NSCache alloc] init];
_allowlistNotifications = [NSMutableDictionary dictionary];
_allowlistNotificationQueue = [[NSOperationQueue alloc] init];
_allowlistNotificationQueue.maxConcurrentOperationCount = 1; // make this a serial queue
_syncQueue = dispatch_queue_create("com.google.santa.syncservice", DISPATCH_QUEUE_SERIAL);
_syncLimiter = dispatch_semaphore_create(kMaxEnqueuedSyncs);
_fullSyncInterval = kDefaultFullSyncInterval;
_eventBatchSize = kDefaultEventBatchSize;
_FCMFullSyncInterval = kDefaultFCMFullSyncInterval;
_FCMGlobalRuleSyncDeadline = kDefaultFCMGlobalRuleSyncDeadline;
}
return self;
}
@@ -138,10 +102,15 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
[self stopReachability];
}
#pragma mark SNTSyncdXPC protocol methods
#pragma mark SNTSyncServiceXPC methods
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle {
SNTSyncState *syncState = [self createSyncState];
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)isFromBundle {
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
LOGE(@"Events upload failed to create sync state: %ld", status);
return;
}
if (isFromBundle) syncState.eventBatchSize = self.eventBatchSize;
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if (events && [p uploadEvents:events]) {
@@ -159,7 +128,13 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
reply(SNTBundleEventActionDropEvents);
return;
}
SNTSyncState *syncState = [self createSyncState];
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
LOGE(@"Bundle event upload failed to create sync state: %ld", status);
reply(SNTBundleEventActionDropEvents);
return;
}
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if ([p uploadEvents:@[ event ]]) {
if ([syncState.bundleBinaryRequests containsObject:event.fileBundleHash]) {
@@ -180,162 +155,50 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply(self.FCMClient.isConnected);
reply(self.pushNotifications.isConnected);
}
#pragma mark push notification methods
#pragma mark sync control / SNTPushNotificationsDelegate methods
- (void)listenForPushNotificationsWithSyncState:(SNTSyncState *)syncState {
if ([self.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.FCMToken = t;
[self preflightOnly:YES];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.FCMToken = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
[self.FCMClient connect];
- (void)sync {
[self syncSecondsFromNow:0];
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// We assume that the incoming FCM message contains name of binary/bundle and a hash. Rule count
// info for bundles will be sent out later with the rules themselves. If the message is related
// to a bundle, the hash is a bundle hash, otherwise it is just a hash for a single binary.
// For later use, we store a mapping of bundle/binary hash to a dictionary containing the
// binary/bundle name so we can send out relevant notifications once the rules are actually
// downloaded & added to local database. We use a dictionary value so that we can later add a
// count field when we start downloading the rules and receive the count information.
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[self.allowlistNotificationQueue addOperationWithBlock:^{
self.allowlistNotifications[fileHash] = @{kFileName : fileName}.mutableCopy;
}];
}
LOGD(@"Push notification action: %@ received", action);
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds = arc4random_uniform((uint32_t)self.FCMGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
[self fullSync];
} else if ([action isEqualToString:kLogSync]) {
[self fullSync];
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
if (!messageData) {
LOGD(@"Unable to parse push notification message data");
return nil;
}
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
#pragma mark sync timer control
- (void)fullSync {
[self fullSyncSecondsFromNow:0];
}
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kFullSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
return;
}
- (void)syncSecondsFromNow:(uint64_t)seconds {
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)syncAndMakeItClean:(BOOL)clean withReply:(void (^)(SNTSyncStatusType))reply {
if (dispatch_semaphore_wait(self.syncLimiter, DISPATCH_TIME_NOW)) {
if (reply) reply(SNTSyncStatusTypeTooManySyncsInProgress);
return;
}
dispatch_async(self.syncQueue, ^() {
SLOGI(@"Starting sync...");
if (clean) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] setSyncCleanRequired:YES
reply:^() {
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
SLOGE(@"Timeout waiting for daemon");
if (reply) reply(SNTSyncStatusTypeDaemonTimeout);
return;
}
}
if (reply) reply(SNTSyncStatusTypeSyncStarted);
SNTSyncStatusType status = [self preflight];
if (reply) reply(status);
dispatch_semaphore_signal(self.syncLimiter);
});
}
- (void)ruleSync {
[self ruleSyncSecondsFromNow:0];
}
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kRuleSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
return;
}
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
}
@@ -344,86 +207,105 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
}
- (void)ruleSyncImpl {
// Rule only syncs are exclusivly scheduled by self.ruleSyncTimer. We do not need to worry about
// using self.syncLimiter here. However we do want to do the work on self.syncQueue so we do not
// overlap with a full sync.
dispatch_async(self.syncQueue, ^() {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
LOGE(@"Rule sync failed to create sync state: %ld", status);
return;
}
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
BOOL ret = [p sync];
LOGD(@"Rule download %@", ret ? @"complete" : @"failed");
});
}
- (void)preflightSync {
[self preflightOnly:YES];
}
#pragma mark syncing chain
- (void)preflight {
[self preflightOnly:NO];
- (SNTSyncStatusType)preflight {
return [self preflightOnly:NO];
}
- (void)preflightOnly:(BOOL)preflightOnly {
LOGD(@"Preflight starting");
SNTSyncState *syncState = [self createSyncState];
- (SNTSyncStatusType)preflightOnly:(BOOL)preflightOnly {
SNTSyncStatusType status = SNTSyncStatusTypeUnknown;
SNTSyncState *syncState = [self createSyncStateWithStatus:&status];
if (!syncState) {
return status;
}
SLOGD(@"Preflight starting");
SNTSyncPreflight *p = [[SNTSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
SLOGD(@"Preflight complete");
// Clean up reachability if it was started for a non-network error
[self stopReachability];
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && [SNTConfigurator configurator].fcmEnabled) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCM not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.fullSyncInterval = syncState.fullSyncInterval;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.fullSyncInterval];
// Start listening for push notifications with a full sync every
// pushNotificationsFullSyncInterval.
if ([SNTConfigurator configurator].fcmEnabled) {
[self.pushNotifications listenWithSyncState:syncState];
} else {
LOGD(@"Push notifications are not enabled. Sync every %lu min.",
syncState.fullSyncInterval / 60);
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:syncState.fullSyncInterval];
}
if (preflightOnly) return;
if (preflightOnly) return SNTSyncStatusTypeSuccess;
return [self eventUploadWithSyncState:syncState];
} else {
if (!syncState.daemon) {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
SLOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
return SNTSyncStatusTypePreflightFailed;
}
- (void)eventUploadWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Event upload starting");
- (SNTSyncStatusType)eventUploadWithSyncState:(SNTSyncState *)syncState {
SLOGD(@"Event upload starting");
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
SLOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
} else {
LOGE(@"Event upload failed, aborting run");
if (!syncState.daemon) exit(1);
}
SLOGE(@"Event upload failed, aborting run");
return SNTSyncStatusTypeEventUploadFailed;
}
- (void)ruleDownloadWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Rule download starting");
- (SNTSyncStatusType)ruleDownloadWithSyncState:(SNTSyncState *)syncState {
SLOGD(@"Rule download starting");
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
SLOGD(@"Rule download complete");
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
if (!syncState.daemon) exit(1);
}
SLOGE(@"Rule download failed, aborting run");
return SNTSyncStatusTypeRuleDownloadFailed;
}
- (void)postflightWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Postflight starting");
- (SNTSyncStatusType)postflightWithSyncState:(SNTSyncState *)syncState {
SLOGD(@"Postflight starting");
SNTSyncPostflight *p = [[SNTSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
if (!syncState.daemon) exit(0);
} else {
LOGE(@"Postflight failed");
if (!syncState.daemon) exit(1);
SLOGD(@"Postflight complete");
SLOGI(@"Sync completed successfully");
return SNTSyncStatusTypeSuccess;
}
SLOGE(@"Postflight failed");
return SNTSyncStatusTypePostflightFailed;
}
#pragma mark internal helpers
@@ -437,29 +319,31 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
return timerQueue;
}
- (SNTSyncState *)createSyncState {
- (SNTSyncState *)createSyncStateWithStatus:(SNTSyncStatusType *)status {
// Gather some data needed during some sync stages
SNTSyncState *syncState = [[SNTSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
if (syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (!syncState.daemon) exit(1);
SLOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (*status) *status = SNTSyncStatusTypeMissingSyncBaseURL;
return nil;
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
SLOGW(@"SyncBaseURL is not over HTTPS!");
}
syncState.machineID = config.machineID;
if (syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
if (!syncState.daemon) exit(1);
SLOGE(@"Missing Machine ID. Can't sync without it.");
if (*status) *status = SNTSyncStatusTypeMissingMachineID;
return nil;
}
syncState.machineOwner = config.machineOwner;
if (syncState.machineOwner.length == 0) {
syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
SLOGW(@"Missing Machine Owner.");
}
dispatch_group_t group = dispatch_group_create();
@@ -482,7 +366,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
SLOGD(@"%@", line);
};
// Configure server auth
@@ -504,36 +388,23 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
syncState.compressedContentEncoding =
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
syncState.FCMToken = self.FCMToken;
syncState.pushNotificationsToken = self.pushNotifications.token;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}
- (void)lockAction:(NSString *)action {
[self.dispatchLock setObject:@YES forKey:action];
}
- (void)unlockAction:(NSString *)action {
[self.dispatchLock removeObjectForKey:action];
}
- (BOOL)checkLockAction:(NSString *)action {
return ([self.dispatchLock objectForKey:action] == nil);
}
#pragma mark reachability methods
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
[self stopReachability];
[self fullSync];
[self sync];
}
}

View File

@@ -16,7 +16,6 @@
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"

View File

@@ -22,6 +22,7 @@
#import "Source/common/SNTSystemInfo.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTSyncPreflight
@@ -40,7 +41,9 @@
requestDict[kModelIdentifier] = [SNTSystemInfo modelIdentifier];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kPrimaryUser] = self.syncState.machineOwner;
if (self.syncState.FCMToken) requestDict[kFCMToken] = self.syncState.FCMToken;
if (self.syncState.pushNotificationsToken) {
requestDict[kFCMToken] = self.syncState.pushNotificationsToken;
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
@@ -75,8 +78,8 @@
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
// If user requested it or we've never had a successful sync, try from a clean slate.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] || syncClean) {
LOGD(@"Clean sync requested by user");
if (syncClean) {
SLOGD(@"Clean sync requested by user");
requestDict[kRequestCleanSync] = @YES;
}
@@ -107,11 +110,12 @@
// Don't let these go too low
NSUInteger FCMIntervalValue = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
self.syncState.FCMFullSyncInterval =
(FCMIntervalValue < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : FCMIntervalValue;
self.syncState.pushNotificationsFullSyncInterval = (FCMIntervalValue < kDefaultFullSyncInterval)
? kDefaultPushNotificationsFullSyncInterval
: FCMIntervalValue;
FCMIntervalValue = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
self.syncState.FCMGlobalRuleSyncDeadline =
(FCMIntervalValue < 60) ? kDefaultFCMGlobalRuleSyncDeadline : FCMIntervalValue;
self.syncState.pushNotificationsGlobalRuleSyncDeadline =
(FCMIntervalValue < 60) ? kDefaultPushNotificationsGlobalRuleSyncDeadline : FCMIntervalValue;
// Check if our sync interval has changed
NSUInteger intervalValue = [resp[kFullSyncInterval] unsignedIntegerValue];
@@ -144,7 +148,7 @@
}
if ([resp[kCleanSync] boolValue]) {
LOGD(@"Clean sync requested by server");
SLOGD(@"Clean sync requested by server");
self.syncState.cleanSync = YES;
}

View File

@@ -13,13 +13,15 @@
/// limitations under the License.
#import "Source/santasyncservice/SNTSyncRuleDownload.h"
#include "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTPushNotificationsTracker.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTSyncRuleDownload
@@ -46,13 +48,13 @@
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC))) {
LOGE(@"Failed to add rule(s) to database: timeout sending rules to daemon");
SLOGE(@"Failed to add rule(s) to database: timeout sending rules to daemon");
return NO;
}
if (error) {
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
SLOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
SLOGD(@"Failure reason: %@", error.localizedFailureReason);
return NO;
}
@@ -64,14 +66,11 @@
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
LOGI(@"Processed %lu rules", newRules.count);
SLOGI(@"Processed %lu rules", newRules.count);
// Send out push notifications about any newly allowed binaries
// that had been previously blocked by santad.
[self.syncState.allowlistNotificationQueue addOperationWithBlock:^{
[self announceUnblockingRules:newRules];
}];
[self announceUnblockingRules:newRules];
return YES;
}
@@ -99,7 +98,7 @@
count++;
}
}
LOGI(@"Received %u rules", count);
SLOGI(@"Received %u rules", count);
cursor = response[kCursor];
} while (cursor);
return newRules;
@@ -108,28 +107,25 @@
// Send out push notifications for allowed bundles/binaries whose rule download was preceded by
// an associated announcing FCM message.
- (void)announceUnblockingRules:(NSArray<SNTRule *> *)newRules {
if (!self.syncState.targetedRuleSync) return;
NSMutableArray *processed = [NSMutableArray array];
SNTPushNotificationsTracker *tracker = [SNTPushNotificationsTracker tracker];
[[tracker all]
enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *notifier, BOOL *stop) {
// Each notifier object is a dictionary with name and count keys. If the count has been
// decremented to zero, then this means that we have downloaded all of the rules associated
// with this SHA256 hash (which might be a bundle hash or a binary hash), in which case we are
// OK to show a notification that the named bundle/binary can be run.
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining && [remaining intValue] == 0) {
[processed addObject:key];
NSString *message = [NSString stringWithFormat:@"%@ can now be run", notifier[kFileName]];
[[self.daemonConn remoteObjectProxy] postRuleSyncNotificationWithCustomMessage:message
reply:^{
}];
}
}];
for (NSString *key in self.syncState.allowlistNotifications) {
// Each notifier object is a dictionary with name and count keys. If the count has been
// decremented to zero, then this means that we have downloaded all of the rules associated with
// this SHA256 hash (which might be a bundle hash or a binary hash), in which case we are OK to
// show a notification that the named bundle/binary can be run.
NSDictionary *notifier = self.syncState.allowlistNotifications[key];
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining && [remaining intValue] == 0) {
[processed addObject:key];
NSString *message = [NSString stringWithFormat:@"%@ can now be run", notifier[kFileName]];
[[self.daemonConn remoteObjectProxy] postRuleSyncNotificationWithCustomMessage:message
reply:^{
}];
}
}
// Remove all entries from allowlistNotifications dictionary that had zero count.
[self.syncState.allowlistNotifications removeObjectsForKeys:processed];
[tracker removeNotificationsForHashes:processed];
}
// Converts rule information downloaded from the server into a SNTRule. Because any information
@@ -188,30 +184,11 @@
primaryHash = newRule.identifier;
}
// As we read in rules, we update the "remaining count" information stored in
// allowlistNotifications. This count represents the number of rules associated with the primary
// hash that still need to be downloaded and added.
[self.syncState.allowlistNotificationQueue addOperationWithBlock:^{
NSMutableDictionary *notifier = self.syncState.allowlistNotifications[primaryHash];
if (notifier) {
NSNumber *ruleCount = dict[kFileBundleBinaryCount];
NSNumber *remaining = notifier[kFileBundleBinaryCount];
if (remaining) { // bundle rule with existing count
// If the primary hash already has an associated count field, just decrement it.
notifier[kFileBundleBinaryCount] = @([remaining intValue] - 1);
} else if (ruleCount) { // bundle rule seen for first time
// Downloaded rules including count information are associated with bundles.
// The first time we see a rule for a given bundle hash, add a count field with an
// initial value equal to the number of associated rules, then decrement this value by 1
// to account for the rule that we've just downloaded.
notifier[kFileBundleBinaryCount] = @([ruleCount intValue] - 1);
} else { // non-bundle binary rule
// Downloaded rule had no count information, meaning it is a singleton non-bundle rule.
// Therefore there are no more rules associated with this hash to download.
notifier[kFileBundleBinaryCount] = @0;
}
}
}];
// As we read in rules, we update the "remaining count" information. This count represents the
// number of rules associated with the primary hash that still need to be downloaded and added.
[[SNTPushNotificationsTracker tracker]
decrementPendingRulesForHash:primaryHash
totalRuleCount:dict[kFileBundleBinaryCount]];
}
return newRule;

View File

@@ -1,4 +1,4 @@
/// Copyright 2020 Google Inc. All rights reserved.
/// Copyright 2022 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
@@ -12,22 +12,96 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTSyncService.h"
#import "Source/santasyncservice/SNTSyncService.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/SNTSyncBroadcaster.h"
#import "Source/santasyncservice/SNTSyncManager.h"
@interface SNTSyncService ()
@property(nonatomic, readonly) SNTSyncManager *syncManager;
@property(nonatomic, readonly) MOLXPCConnection *daemonConn;
@property(nonatomic, readonly) NSMutableArray *logListeners;
@end
// TODO(bur): Add "santactl sync --daemon" behavior here.
@implementation SNTSyncService
- (instancetype)init {
self = [super init];
if (self) {
_logListeners = [NSMutableArray array];
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^(void) {
// Spindown this process if we can't establish a connection
// or if the daemon is killed or crashes.
// If we are needed we will be re-launched.
[self spindown];
};
[daemonConn resume];
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
exit(1);
}
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil]];
_daemonConn = daemonConn;
_syncManager = [[SNTSyncManager alloc] initWithDaemonConnection:daemonConn];
// This service should only start up if com.google.santa.daemon
// noticed there is sync server configured and established a connection
// with us. Go ahead and start syncing!
[_syncManager syncSecondsFromNow:15];
}
return self;
}
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events fromBundle:(BOOL)fromBundle {
[self.syncManager postEventsToSyncServer:events fromBundle:fromBundle];
}
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
reply:(void (^)(SNTBundleEventAction))reply {
[self.syncManager postBundleEventToSyncServer:event reply:reply];
}
- (void)isFCMListening:(void (^)(BOOL))reply {
[self.syncManager isFCMListening:reply];
}
- (void)performFullSyncWithReply:(SNTFullSyncReplyBlock)reply {
// TODO(bur): Add support for santactl sync --debug to enable debug logging for that sync.
- (void)syncWithLogListener:(NSXPCListenerEndpoint *)logListener
isClean:(BOOL)cleanSync
reply:(void (^)(SNTSyncStatusType))reply {
MOLXPCConnection *ll = [[MOLXPCConnection alloc] initClientWithListener:logListener];
ll.remoteInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncServiceLogReceiverXPC)];
[ll resume];
[self.syncManager syncAndMakeItClean:cleanSync
withReply:^(SNTSyncStatusType status) {
if (status == SNTSyncStatusTypeSyncStarted) {
[[SNTSyncBroadcaster broadcaster] addLogListener:ll];
return;
}
[[SNTSyncBroadcaster broadcaster] barrier];
[[SNTSyncBroadcaster broadcaster] removeLogListener:ll];
reply(status);
}];
}
- (void)spindown {
LOGI(@"Spinning down.");
exit(0);
}
@end

View File

@@ -20,6 +20,7 @@
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santasyncservice/NSData+Zlib.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"
@interface SNTSyncStage ()
@@ -59,7 +60,7 @@
NSError *error;
requestBody = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error];
if (error) {
LOGD(@"Failed to encode JSON request: %@", error);
SLOGD(@"Failed to encode JSON request: %@", error);
return nil;
}
}
@@ -93,7 +94,7 @@
nanosleep(&ts, NULL);
}
LOGD(@"Performing request, attempt %d", attempt);
SLOGD(@"Performing request, attempt %d", attempt);
data = [self performRequest:request timeout:timeout response:&response error:&error];
if (response.statusCode == 200) break;
@@ -130,7 +131,7 @@
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[self stripXssi:data]
options:0
error:&error];
if (error) LOGD(@"Failed to decode JSON response: %@", error);
if (error) SLOGD(@"Failed to decode JSON response: %@", error);
return dict ?: @{};
}
@@ -202,10 +203,10 @@
reply:^{
}];
self.syncState.xsrfToken = headers[kXSRFToken];
LOGD(@"Retrieved new XSRF token");
SLOGD(@"Retrieved new XSRF token");
success = YES;
} else {
LOGD(@"Failed to retrieve XSRF token");
SLOGD(@"Failed to retrieve XSRF token");
}
};
return success;

View File

@@ -35,18 +35,20 @@
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// Full sync interval in seconds without FCM to update kDefaultFullSyncInterval => when FCM
/// is not used, defaults to 10m.
/// Full sync interval in seconds, defaults to kDefaultFullSyncInterval. If push notifications are
/// being used this interval will be ignored in favor of pushNotificationsFullSyncInterval.
@property NSUInteger fullSyncInterval;
/// An FCM token to subscribe to push notifications.
@property(copy) NSString *FCMToken;
/// An token to subscribe to push notifications.
@property(copy) NSString *pushNotificationsToken;
/// Full sync interval in seconds while listening for FCM messages.
@property NSUInteger FCMFullSyncInterval;
/// Full sync interval in seconds while listening for push notifications, defaults to
/// kDefaultPushNotificationsFullSyncInterval.
@property NSUInteger pushNotificationsFullSyncInterval;
/// Leeway time in seconds when receiving a global rule sync message.
@property NSUInteger FCMGlobalRuleSyncDeadline;
/// Leeway time in seconds when receiving a global rule sync push notification, defaults to
/// kDefaultPushNotificationsGlobalRuleSyncDeadline.
@property NSUInteger pushNotificationsGlobalRuleSyncDeadline;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@@ -69,18 +71,6 @@
/// Array of bundle IDs to find binaries for.
@property NSArray *bundleBinaryRequests;
/// Returns YES if the santactl session is running as a daemon, returns NO otherwise.
@property BOOL daemon;
/// Returns YES if the session is targeted for this machine, returns NO otherwise.
@property BOOL targetedRuleSync;
/// Reference to the sync manager's ruleSyncCache. Used to lookup binary names for notifications.
@property(weak) NSMutableDictionary *allowlistNotifications;
/// Reference to the serial operation queue used for accessing allowlistNotifications.
@property(weak) NSOperationQueue *allowlistNotificationQueue;
/// The header value for ContentEncoding when sending compressed content.
/// Either "deflate" (default) or "zlib".
@property(copy) NSString *compressedContentEncoding;

View File

@@ -239,9 +239,7 @@
- (void)testPreflightCleanSync {
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
id processInfoMock = OCMClassMock([NSProcessInfo class]);
OCMStub([processInfoMock processInfo]).andReturn(processInfoMock);
[OCMStub([processInfoMock arguments]) andReturn:@[ @"xctest", @"--clean" ]];
OCMStub([self.daemonConnRop syncCleanRequired:([OCMArg invokeBlockWithArgs:@YES, nil])]);
NSData *respData = [self dataFromDict:@{kCleanSync : @YES}];
[self stubRequestBody:respData

View File

@@ -14,18 +14,6 @@ function cleanup() {
}
trap cleanup EXIT
function randomize_version() {
VERSION_FILE="$GIT_ROOT/version.bzl"
# Create a random version ID for the generated Santa version.
# The system extension won't replace itself if the version string isn't different than the one
# presently installed.
cp $VERSION_FILE $TMP_DIR
RANDOM_VERSION="$RANDOM.$RANDOM"
echo "Setting version to $RANDOM_VERSION"
echo "SANTA_VERSION = \"$RANDOM_VERSION\"" > $VERSION_FILE
}
function build_custom_signed() {
SANTAD_PATH=Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon
SANTA_BIN_PATH=Santa.app/Contents/MacOS
@@ -33,7 +21,14 @@ function build_custom_signed() {
SANTAD_ENTITLEMENTS="$GIT_ROOT/Source/santad/com.google.santa.daemon.systemextension.entitlements"
SIGNING_IDENTITY="localhost"
bazel build --apple_generate_dsym -c opt --define=SANTA_BUILD_TYPE=ci --define=apple.propagate_embedded_extra_outputs=yes --macos_cpus=x86_64,arm64 //:release
bazel build \
--apple_generate_dsym \
-c opt \
--define=SANTA_BUILD_TYPE=ci \
--define=apple.propagate_embedded_extra_outputs=yes \
--macos_cpus=x86_64,arm64 \
--embed_label="santa_${RANDOM}.${RANDOM}.${RANDOM}"
//:release
echo "> Build complete, installing santa"
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
@@ -73,14 +68,6 @@ function install() {
}
function main() {
for i in "$@"; do
case $i in
--randomize_version)
randomize_version
;;
esac
done
build
install
}

View File

@@ -8,7 +8,7 @@ load(
git_repository(
name = "build_bazel_rules_apple",
commit = "4246cfe864953025cdaa105d8105679fcd1fba29", # Latest commit that fixes https://github.com/google/santa/issues/1358
commit = "7115f0188d141d57d64a6875735847c975956dae", # 0.34.0
remote = "https://github.com/bazelbuild/rules_apple.git",
)

View File

@@ -24,8 +24,8 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| ClientMode\* | Integer | 1 = MONITOR, 2 = LOCKDOWN, defaults to MONITOR |
| FailClosed | Bool | If true and the ClientMode is LOCKDOWN: execution will be denied when there is an error reading or processing an executable file. |
| FileChangesRegex\* | String | The regex of paths to log file changes. Regexes are specified in ICU format. |
| AllowedPathRegex\* | String | A regex to allow if the binary or certificate scopes did not allow/block execution. Regexes are specified in ICU format. |
| BlockedPathRegex\* | String | A regex to block if the binary or certificate scopes did not allow/block an execution. Regexes are specified in ICU format. |
| AllowedPathRegex\* | String | A regex to allow if the binary, certificate, or Team ID scopes did not allow/block execution. Regexes are specified in ICU format. |
| BlockedPathRegex\* | String | A regex to block if the binary, certificate, or Team ID scopes did not allow/block an execution. Regexes are specified in ICU format. |
| EnableBadSignatureProtection | Bool | Enable bad signature protection, defaults to NO. If this flag is set to YES, binaries with a bad signing chain will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
| EnablePageZeroProtection | Bool | Enable `__PAGEZERO` protection, defaults to YES. If this flag is set to YES, 32-bit binaries that are missing the `__PAGEZERO` segment will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
| EnableSysxCache | Bool | Enables a secondary cache that ensures better performance when multiple EndpointSecurity system extensions are installed. Defaults to YES in 2021.8, defaults to NO in earlier versions. |

View File

@@ -7,6 +7,9 @@ redirect_from:
# Binary Authorization Overview
NOTE: This doc is out-dated and will be updated soon. We don't rely on a Kernel
Extension anymore.
#### Background
The decision flow starts in the kernel. The macOS kernel is extensible by way of
@@ -14,10 +17,7 @@ a kernel extension (KEXT). macOS makes available kernel programming interfaces
(KPIs) to be used by a KEXT. Santa utilizes the Kernel Authorization (Kauth)
KPI. This is a very powerful and verbose interface that gives Santa the ability
to listen in on most vnode and file systems operations and to take actions,
directly or indirectly, on the operations being performed. Still, there are some
limitations to Kauth which are pointed out in the santa-driver document. For
more information on the santa-driver KEXT see the
[santa-driver.md](../details/santa-driver.md) document.
directly or indirectly, on the operations being performed.
#### Flow of an execve()
@@ -49,9 +49,7 @@ documentation. This flow does not cover the logging component of Santa, see the
`execve()` the same `vnode_id`, santa-driver will have that thread wait
for the in-flight decision from santad. All subsequent `execve()`s for
the same `vnode_id` will use the decision in the cache as explained
in #2, until the cache is invalidated. See the
[santa-driver.md](../details/santa-driver.md) document for more details
on the cache invalidation.
in #2, until the cache is invalidated.
* If the executing file is written to while any of the threads are waiting
for a response the `ACTION_REQUEST_BINARY` entry is removed, forcing the
decision-making process to be restarted.

View File

@@ -1,39 +1,27 @@
#!/bin/sh
GIT_ROOT=$(git rev-parse --show-toplevel)
PROFILE_PATH="$GIT_ROOT/CoverageData"
COV_FILE="$PROFILE_PATH/info.lcov"
function build() {
tests=$(bazel query "tests(//:unit_tests)")
for t in $tests; do
profname=$(echo $t | shasum | awk '{print $1}')
bazel coverage \
--test_env="LLVM_PROFILE_FILE=$PROFILE_PATH/$profname.profraw" \
--experimental_use_llvm_covmap \
--spawn_strategy=standalone \
--cache_test_results=no \
--test_env=LCOV_MERGER=/usr/bin/true \
$t
done
xcrun llvm-profdata merge $PROFILE_PATH/*.profraw -output "$PROFILE_PATH/default.profdata"
}
function generate_lcov() {
object_files=$(find -L $(bazel info bazel-bin) -type f -exec file -L {} \; | grep "Mach-O" | sed 's,:.*,,' | grep -v 'testdata')
bazel_base=$(bazel info execution_root)
true > $COV_FILE
for file in $object_files; do
xcrun llvm-cov export -instr-profile "$PROFILE_PATH/default.profdata" -format=lcov \
--ignore-filename-regex="/Applications/.*|external/.*" \
$file | sed "s,$bazel_base,$GIT_ROOT," >> $COV_FILE
done
}
BAZEL_EXEC_ROOT=$(bazel info execution_root)
COV_FILE="$(bazel info output_path)/_coverage/_coverage_report.dat"
function main() {
mkdir -p $PROFILE_PATH
build
generate_lcov
bazel coverage \
--experimental_use_llvm_covmap \
--instrument_test_targets \
--combined_report=lcov \
--spawn_strategy=standalone \
--test_env=LCOV_MERGER=/usr/bin/true \
//:unit_tests
# The generated file has most of the source files relative to bazel's
# execution_root path, so we strip that off as it prevents files being
# picked up by Coveralls.
sed -i '' "s,${BAZEL_EXEC_ROOT},${GIT_ROOT}," ${COV_FILE}
# We also want to filter out files that aren't ours but which sometimes get
# coverage data created anyway.
sed -i '' '/SF:\/Applications.*/,/end_of_record/d' ${COV_FILE}
sed -i '' '/SF:.*santa\/bazel-out.*/,/end_of_record/d' ${COV_FILE}
}
main

View File

@@ -22,7 +22,7 @@ def santa_unit_test(
srcs = [],
deps = [],
size = "medium",
minimum_os_version = "10.9",
minimum_os_version = "10.15",
resources = [],
structured_resources = [],
copts = [],

View File

@@ -1,3 +0,0 @@
"""The version for all Santa components."""
SANTA_VERSION = "2022.3"