Compare commits

...

39 Commits

Author SHA1 Message Date
Russell Hancox
e3e48aed1b Packaging: Keep package versions simple (#737) 2022-03-02 10:36:39 -05:00
Russell Hancox
e60f9cf6c5 Project: Add build version (#736) 2022-02-28 14:18:18 -05:00
Kent Ma
c7e309ccb1 Add a USB device blocking popup. (#728)
* Add a USB device blocking popup.
* Refactor SNTNotificationManager and SNTMessageWindowController to make
  for generalized notification logic
* Add the configuration keys for custom block messages and resize window
2022-02-28 13:30:56 -05:00
Russell Hancox
ad8aafbd07 Project: Bump version to 2022.2 (#734) 2022-02-17 11:36:21 -05:00
Russell Hancox
9e671c3dee Project: Add arm64 to hostArchitectures for productbuild (#733)
This should avoid prompts to install Rosetta when installing the .pkg
2022-02-16 20:02:12 -05:00
Russell Hancox
d97abe36f2 santad: Fix fail open tests in SNTExecutionControllerTest (#730) 2022-02-14 20:13:08 -05:00
Pete Markowsky
faa8946056 Fix: remediate a crash in santametricservice (#729)
* Fix issue with task cancelation.

* Make export timeouts configurable.

This allows an export timeout to be set via configuration and eases testing.
2022-02-14 13:51:29 -05:00
Kent Ma
8b2b1f0bfc Report USB blocking status with santactl status (#727)
* Report USB blocking status with santactl status
2022-02-10 16:02:40 -05:00
Kent Ma
16678cd5a0 Update version of bazel rules_apple to fix broken 12.3 builds (#726) 2022-02-10 14:32:43 -05:00
Matt W
0bd6a199a3 Fix additional strlcpy issue, simplify call paths (#723)
* Fix additional strlcpy issue, simplify call paths

* Remove unused interface from header
2022-02-03 12:53:22 -05:00
Russell Hancox
58e2b7e1b8 santad: Add fail-closed mode (#722) 2022-01-28 18:29:18 -05:00
Tom Burgin
b824a8e3e0 santad: only store events if there is a sync server configured (#721)
* santad: only store events if there is a sync server configured

* SNTExecutionControllerTest stub sync server

Co-authored-by: Tom Burgin <bur@chromium.org>
2022-01-27 15:55:51 -05:00
Kent Ma
25bf2a93e4 Add DiskArbitrationTestUtil to shim out DiskArbitration for unit testing (#720) 2022-01-25 13:45:03 -05:00
Russell Hancox
f1ea1b369f santactl/fileinfo: Switch certIndex to an NSNumber (#719)
* santactl/fileinfo: Switch certIndex to an NSNumber
2022-01-25 12:50:04 -05:00
Tom Burgin
5503a88308 rule download: return early on daemon timeout (#718)
* rule download: return early on daemon timeout

* wording update

Co-authored-by: Tom Burgin <bur@chromium.org>
2022-01-21 17:19:44 +00:00
Kent Ma
8cf0f8217d Add clang_analyzer generation (#717)
Fix warnings for unused variables. The other warnings are more
nontrivial & require some light refactoring to fix, and will come in a followup PR.
2022-01-21 17:14:44 +00:00
Russell Hancox
22799ffc2a Conf: Delete and clean-up ASL conf, enable signaling on newsyslog.conf. (#716)
* Conf: Delete and clean-up ASL conf, enable signaling on newsyslog.conf.

The ASL config is a remnant from when Santa did all logging via ASL before Apple deprecated ASL and replaced it with ULS, which doesn't allow redirecting messages to a file. The old config wasn't causing any problems except that it was handling battling newsyslog for rotation and had different parameters.

The signal change in the newsyslog.conf causes newsyslog to fallback on sending a (harmless) SIGHUP to syslogd, which has no effect on Santa except it also triggers a 10s sleep inside newsyslog between renaming the old file and beginning the compression, which is plenty of time for santad to notice the rename and start writing new logs to the newer file.
2022-01-19 11:29:39 -05:00
Pete Markowsky
cb61d0cc99 Create test suites for each component (#702)
Create test suites for each component.
2022-01-18 17:00:44 -05:00
Pete Markowsky
fb7447ceba Fix off-by one error in strlcpy. (#715) 2022-01-18 15:31:30 -05:00
Russell Hancox
45e51e9c09 santactl/fileinfo: Clarify valid index for cert-index (#714) 2022-01-13 14:35:30 -05:00
Russell Hancox
b0f0cdd4e6 santactl/fileinfo: Update --cert-index usage (#713)
* santactl/fileinfo: Update --cert-index usage.

Fixes #710
2022-01-13 13:04:38 -05:00
Kent Ma
65090d3ef2 Support rule downloading of Team ID rules (#709)
* Support syncing Team ID rules and using 'identifier' instead of 'sha256' in sync rules
2022-01-13 10:55:14 -05:00
Russell Hancox
9c80f79d82 Sync: Allow configuring proxies (#708)
* Sync: Allow configuring proxies

Fixes #672
2022-01-13 15:04:11 +00:00
Kent Ma
93adaea81e Add clang annotation for fallthrough (#712) 2022-01-12 13:56:47 -05:00
Russell Hancox
a125b340a5 santad: Don't use proc_pidpath when using ES (#707) 2022-01-11 20:32:29 -05:00
Kent Ma
fbd0de3d48 Add test coverage for syncing USB mounting options (#711) 2022-01-11 17:13:37 -05:00
Russell Hancox
6f2ae62bce Project: Explicitly set calendar on ISO8601 dates (#706) 2022-01-06 09:33:04 -05:00
Christopher Sauer
da29b20473 Update hedron_compile_commands (#704) 2021-12-30 07:59:35 -05:00
Kent Ma
197109a8ee USB mass storage blocking and remounting (#685)
* USB mass storage blocking.

* Add the sync service and config key for enabling mass USB storage blocking
* Update docs with the sync service key
* Add ability to forcibly remount USBs with different flags
* update EndpointSecurityTestUtil and tests that use it to properly handle multiple ES clients
2021-12-16 13:38:48 -05:00
Kent Ma
91f3168c7a Update santactl rule text to have accurate text for team IDs (#701) 2021-12-14 11:42:53 -05:00
Russell Hancox
a00ec41518 Project: Bump version to 2022.1 (#700) 2021-12-13 13:28:16 -05:00
Russell Hancox
c32248aaf7 santad: Fix PrinterProxy workaround for Monterey+ (#698) 2021-12-13 15:24:58 +00:00
Pete Markowsky
afd97bdf3e Removed the check for export metrics in santad. (#697)
Remove the check for export metrics in santad

Metrics are always collected but only exported to a monitoring system when all of the necessary config options are set. Since they're always collected santactl metrics should always return metrics data.
2021-12-13 10:23:05 -05:00
Tom Burgin
73c4875b1f santasyncservice: move sync code to the santasyncservice dir (#696)
* sync: move sync code from santactl dir to santasyncservice dir

* clang-format

* fix tests

* s/SNTCommandSync/SNTSync

* s/SNTCommandSync/SNTSync on content
2021-12-08 18:11:56 -05:00
Pete Markowsky
916fc8c0e6 Add a simple event counter to SNTExecutionController (#694)
Add a simple event counter for events per response.
2021-12-08 17:35:37 -05:00
Kent Ma
e59e6105f3 Update the Santa version number to 2021.9 (#695) 2021-12-08 17:01:43 -05:00
Pete Markowsky
216ac811eb Fix issue with reregistering metrics. (#693) 2021-12-07 15:32:15 -05:00
Pete Markowsky
48f92f5913 Ignore VSCode directories (#692) 2021-12-07 14:23:13 -05:00
Russell Hancox
6bb08d0490 Project: Add bazel commands extractor for VSCode integration (#690) 2021-12-06 13:39:23 -05:00
104 changed files with 2834 additions and 890 deletions

3
.gitignore vendored
View File

@@ -15,3 +15,6 @@ tulsigen-*
*.p12
*.keychain
*.swp
compile_commands.json
.cache/
.vscode/*

21
BUILD
View File

@@ -11,7 +11,12 @@ exports_files(["LICENSE"])
# The version label for mac_* rules.
apple_bundle_version(
name = "version",
build_version = SANTA_VERSION,
build_label_pattern = "{build}",
build_version = SANTA_VERSION + ".{build}",
capture_groups = {
"build": "\\d+",
},
fallback_build_label = "1",
short_version_string = SANTA_VERSION,
)
@@ -99,7 +104,6 @@ genrule(
"Conf/com.google.santa.metricservice.plist",
"Conf/com.google.santad.plist",
"Conf/com.google.santa.plist",
"Conf/com.google.santa.asl.conf",
"Conf/com.google.santa.newsyslog.conf",
"Conf/Package/Distribution.xml",
"Conf/Package/notarization_tool.sh",
@@ -231,18 +235,11 @@ genrule(
test_suite(
name = "unit_tests",
tests = [
"//Source/common:SNTFileInfoTest",
"//Source/common:SNTMetricSetTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/common:SantaCacheTest",
"//Source/common:unit_tests",
"//Source/santactl:unit_tests",
"//Source/santad:SNTApplicationCoreMetricsTest",
"//Source/santad:SNTApplicationTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
"//Source/santad:unit_tests",
"//Source/santametricservice:unit_tests",
"//Source/santasyncservice:unit_tests",
],
)

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<installer-gui-script minSpecVersion="1">
<title>Santa</title>
<options customize="never" allow-external-scripts="no"/>
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
<choices-outline>
<line choice="default" />

View File

@@ -50,7 +50,7 @@ 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 RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleVersion)"
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"

View File

@@ -1,6 +0,0 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
? [= Sender kernel] [S= Message santa-driver:] claim
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/db/santa/santa.log

View File

@@ -1,2 +1,2 @@
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
/var/db/santa/santa.log root:wheel 644 10 25000 * NZ
/var/db/santa/santa.log root:wheel 644 10 25000 * Z

View File

@@ -46,6 +46,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
/bin/rm -rf /Applications/Santa.app 2>&1
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
/bin/rm /etc/asl/com.google.santa.asl.conf
# Copy new files.
/bin/mkdir -p /var/db/santa
@@ -63,7 +64,6 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/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.santad.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
# Reload syslogd to pick up ASL configuration change.

View File

@@ -16,10 +16,10 @@
#include <iostream>
#include <vector>
#include <SNTCommandSyncConstants.h>
#include <SNTCommandSyncRuleDownload.h>
#include <SNTCommandSyncState.h>
#include <SNTRule.h>
#include <SNTSyncConstants.h>
#include <SNTSyncRuleDownload.h>
#include <SNTSyncState.h>
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
@@ -41,12 +41,12 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
return 0;
}
SNTCommandSyncState *state = [[SNTCommandSyncState alloc] init];
SNTSyncState *state = [[SNTSyncState alloc] init];
if (!state) {
return 0;
}
SNTCommandSyncRuleDownload *obj = [[SNTCommandSyncRuleDownload alloc] initWithState:state];
SNTSyncRuleDownload *obj = [[SNTSyncRuleDownload alloc] initWithState:state];
if (!obj) {
return 0;
}

View File

@@ -30,6 +30,15 @@ objc_library(
],
)
objc_library(
name = "SNTDeviceEvent",
srcs = ["SNTDeviceEvent.m"],
hdrs = ["SNTDeviceEvent.h"],
deps = [
":SNTCommonEnums",
],
)
objc_library(
name = "SNTBlockMessage_SantaGUI",
srcs = ["SNTBlockMessage.m"],
@@ -37,6 +46,7 @@ objc_library(
defines = ["SANTAGUI"],
deps = [
":SNTConfigurator",
":SNTDeviceEvent",
":SNTLogging",
":SNTStoredEvent",
],
@@ -277,3 +287,14 @@ santa_unit_test(
srcs = ["SNTMetricSetTest.m"],
deps = [":SNTMetricSet"],
)
test_suite(
name = "unit_tests",
tests = [
":SNTFileInfoTest",
":SNTMetricSetTest",
":SNTPrefixTreeTest",
":SantaCacheTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -24,12 +24,17 @@
///
/// Return a message suitable for presenting to the user.
/// Uses either the configured message depending on the event type or a custom message
/// if the rule that blocked this file included one.
///
/// In SantaGUI this will return an NSAttributedString with links and formatting included
/// while for santad all HTML will be properly stripped.
///
+ (NSAttributedString *)formatMessage:(NSString *)message;
///
/// Uses either the configured message depending on the event type or a custom message
/// if the rule that blocked this file included one, formatted using
/// +[SNTBlockMessage formatMessage].
///
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage;

View File

@@ -21,8 +21,7 @@
@implementation SNTBlockMessage
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
+ (NSAttributedString *)formatMessage:(NSString *)message {
NSString *htmlHeader =
@"<html><head><style>"
@"body {"
@@ -48,6 +47,22 @@
NSString *htmlFooter = @"</body></html>";
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef SANTAGUI
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
}
return [[NSAttributedString alloc] initWithString:strippedHTML];
#endif
}
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
NSString *message;
if (customMessage.length) {
message = customMessage;
@@ -64,19 +79,7 @@
@"because it has been deemed malicious.";
}
}
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef SANTAGUI
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
}
return [[NSAttributedString alloc] initWithString:strippedHTML];
#endif
return [SNTBlockMessage formatMessage:message];
}
+ (NSString *)stringFromHTML:(NSString *)html {

View File

@@ -26,7 +26,7 @@
#pragma mark - Daemon Settings
///
/// The operating mode.
/// The operating mode. Defaults to MONITOR.
///
@property(readonly, nonatomic) SNTClientMode clientMode;
@@ -35,6 +35,17 @@
///
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
///
/// Enable Fail Close mode. Defaults to NO.
/// This controls Santa's behavior when a failure occurs, such as an
/// inability to read a file. By default, to prevent bugs or misconfiguration
/// from rendering a machine inoperable Santa will fail open and allow
/// execution. With this setting enabled, Santa will fail closed if the client
/// is in LOCKDOWN mode, offering a higher level of security but with a higher
/// potential for causing problems.
///
@property(readonly, nonatomic) BOOL failClosed;
///
/// The regex of allowed paths. Regexes are specified in ICU format.
///
@@ -235,6 +246,20 @@
///
@property(readonly, nonatomic) NSString *bannedBlockMessage;
///
/// This is the message shown to the user when a USB storage device's mount is denied
/// from the BlockUSB configuration setting. If not configured, a reasonable
/// default is provided.
///
@property(readonly, nonatomic) NSString *bannedUSBBlockMessage;
///
/// This is the message shown to the user when a USB storage device's mount is forcibly
/// remounted to a different set of permissions from the BlockUSB and RemountUSBMode
/// configuration settings. If not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *remountUSBBlockMessage;
///
/// The notification text to display when the client goes into MONITOR mode.
/// Defaults to "Switching into Monitor mode"
@@ -254,6 +279,14 @@
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// Proxy settings for syncing.
/// This dictionary is passed directly to NSURLSession. The allowed keys
/// are loosely documented at
/// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants.
///
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
///
/// The machine owner.
///
@@ -274,6 +307,23 @@
///
@property(nonatomic) BOOL syncCleanRequired;
///
/// USB Mount Blocking. Defaults to false.
///
@property(nonatomic) BOOL blockUSBMount;
///
/// Comma-seperated `$ mount -o` arguments used for forced remounting of USB devices. Default
/// to fully allow/deny without remounting if unset.
///
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
///
/// When `blockUSBMount` is set, this is the message shown to the user when a device is blocked
/// If this message is not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *usbBlockMessage;
///
/// If set, this over-rides the default machine ID used for syncing.
///
@@ -402,6 +452,11 @@
///
@property(readonly, nonatomic) NSUInteger metricExportInterval;
///
/// Duration in seconds for metrics export timeout. Defaults to 30;
///
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
///
/// Retrieve an initialized singleton configurator object using the default file path.
///

View File

@@ -45,6 +45,7 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The keys managed by a mobileconfig.
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
@@ -65,6 +66,9 @@ static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
static NSString *const kRemountUSBBlockMessage = @"RemountUSBBlockMessage";
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
@@ -95,6 +99,9 @@ static NSString *const kFCMAPIKey = @"FCMAPIKey";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kFailClosedKey = @"FailClosed";
static NSString *const kBlockUSBMountKey = @"BlockUSBMount";
static NSString *const kRemountUSBModeKey = @"RemountUSBMode";
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
static NSString *const kEnableTransitiveRulesKeyDeprecated = @"EnableTransitiveWhitelisting";
static NSString *const kAllowedPathRegexKey = @"AllowedPathRegex";
@@ -106,6 +113,7 @@ static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
static NSString *const kMetricFormat = @"MetricFormat";
static NSString *const kMetricURL = @"MetricURL";
static NSString *const kMetricExportInterval = @"MetricExportInterval";
static NSString *const kMetricExportTimeout = @"MetricExportTimeout";
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
// The keys managed by a sync server.
@@ -131,12 +139,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
kFailClosedKey : number,
kEnableTransitiveRulesKey : number,
kEnableTransitiveRulesKeyDeprecated : number,
kFileChangesRegexKey : re,
@@ -145,6 +156,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kAllowedPathRegexKeyDeprecated : re,
kBlockedPathRegexKey : re,
kBlockedPathRegexKeyDeprecated : re,
kBlockUSBMountKey : number,
kRemountUSBModeKey : array,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
@@ -153,9 +166,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEventDetailTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kBannedUSBBlockMessage : string,
kRemountUSBBlockMessage : string,
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kSyncBaseURLKey : string,
kSyncProxyConfigKey : dictionary,
kClientAuthCertificateFileKey : string,
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
@@ -183,6 +199,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExportTimeout : number,
kMetricExtraLabels : dictionary,
};
_defaults = [NSUserDefaults standardUserDefaults];
@@ -421,6 +438,12 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
- (BOOL)failClosed {
NSNumber *n = self.configState[kFailClosedKey];
if (n) return [n boolValue];
return NO;
}
- (BOOL)enableTransitiveRules {
NSNumber *n = self.syncState[kEnableTransitiveRulesKey];
if (n) return [n boolValue];
@@ -486,6 +509,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return filters;
}
- (void)setRemountUSBMode:(NSArray<NSString *> *)args {
[self updateSyncStateForKey:kRemountUSBModeKey value:args];
}
- (NSArray<NSString *> *)remountUSBMode {
NSArray<NSString *> *args = self.configState[kRemountUSBModeKey];
for (id arg in args) {
if (![arg isKindOfClass:[NSString class]]) {
return nil;
}
}
return args;
}
- (NSURL *)syncBaseURL {
NSString *urlString = self.configState[kSyncBaseURLKey];
if (![urlString hasSuffix:@"/"]) urlString = [urlString stringByAppendingString:@"/"];
@@ -493,6 +530,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return url;
}
- (NSDictionary *)syncProxyConfig {
return self.configState[kSyncProxyConfigKey];
}
- (BOOL)enablePageZeroProtection {
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
return number ? [number boolValue] : YES;
@@ -527,6 +568,21 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return self.configState[kBannedBlockMessage];
}
- (NSString *)bannedUSBBlockMessage {
if (!self.configState[kBannedUSBBlockMessage]) {
return @"The following device has been blocked from mounting.";
}
return self.configState[kBannedUSBBlockMessage];
}
- (NSString *)remountUSBBlockMessage {
if (!self.configState[kRemountUSBBlockMessage]) {
return @"The following device has been remounted with reduced permissions.";
}
return self.configState[kRemountUSBBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configState[kModeNotificationMonitor];
}
@@ -679,6 +735,15 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}
- (void)setBlockUSBMount:(BOOL)enabled {
[self updateSyncStateForKey:kBlockUSBMountKey value:@(enabled)];
}
- (BOOL)blockUSBMount {
NSNumber *number = self.configState[kBlockUSBMountKey];
return number ? [number boolValue] : NO;
}
///
/// Returns YES if all of the necessary options are set to export metrics, NO
/// otherwise.
@@ -716,6 +781,16 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [configuredInterval unsignedIntegerValue];
}
// Returns a default value of 30 (for 30 seconds).
- (NSUInteger)metricExportTimeout {
NSNumber *configuredInterval = self.configState[kMetricExportTimeout];
if (configuredInterval == nil) {
return 30;
}
return [configuredInterval unsignedIntegerValue];
}
- (NSDictionary *)extraMetricLabels {
return self.configState[kMetricExtraLabels];
}

View File

@@ -0,0 +1,27 @@
/// 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>
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname;
@property NSString *mntonname;
@property NSString *mntfromname;
@property NSArray<NSString *> *remountArgs;
- (NSString *)readableRemountArgs;
@end

View File

@@ -0,0 +1,63 @@
#import "Source/common/SNTDeviceEvent.h"
@implementation SNTDeviceEvent
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
#define ENCODE(obj, key) \
if (obj) [coder encodeObject:obj forKey:key]
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
#define DECODEARRAY(cls, key) \
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
forKey:key]
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname {
self = [super init];
if (self) {
_mntonname = mntonname;
_mntfromname = mntfromname;
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.mntonname, @"mntonname");
ENCODE(self.mntfromname, @"mntfromname");
ENCODE(self.remountArgs, @"remountArgs");
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_mntonname = DECODE(NSString, @"mntonname");
_mntfromname = DECODE(NSString, @"mntfromname");
_remountArgs = DECODEARRAY(NSString, @"remountArgs");
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTDeviceEvent '%@' -> '%@' (with permissions: [%@]",
self.mntfromname, self.mntonname,
[self.remountArgs componentsJoinedByString:@", "]];
}
- (NSString *)readableRemountArgs {
NSMutableArray<NSString *> *readable = [NSMutableArray array];
for (NSString *arg in self.remountArgs) {
if ([arg isEqualToString:@"rdonly"]) {
[readable addObject:@"read-only"];
} else if ([arg isEqualToString:@"noexec"]) {
[readable addObject:@"block executables"];
} else {
[readable addObject:arg];
}
}
return [readable componentsJoinedByString:@", "];
}
@end

View File

@@ -521,8 +521,7 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
SNTMetricCounter *c = [[SNTMetricCounter alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:c];
return c;
return (SNTMetricCounter *)[self registerMetric:c];
}
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
@@ -531,8 +530,7 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
SNTMetricInt64Gauge *g = [[SNTMetricInt64Gauge alloc] initWithName:name
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:g];
return g;
return (SNTMetricInt64Gauge *)[self registerMetric:g];
}
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
@@ -542,8 +540,7 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:g];
return g;
return (SNTMetricDoubleGauge *)[self registerMetric:g];
}
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
@@ -553,8 +550,7 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:s];
return s;
return (SNTMetricStringGauge *)[self registerMetric:s];
}
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
@@ -564,8 +560,7 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
fieldNames:fieldNames
helpText:helpText];
[self registerMetric:b];
return b;
return (SNTMetricBooleanGauge *)[self registerMetric:b];
}
- (void)addConstantStringWithName:(NSString *)name

View File

@@ -76,6 +76,19 @@
XCTAssertEqualObjects([c export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricCounter *a = [metricSet counterWithName:@"/santa/counter"
fieldNames:@[]
helpText:@"Test counter."];
SNTMetricCounter *b = [metricSet counterWithName:@"/santa/counter"
fieldNames:@[]
helpText:@"Test counter."];
XCTAssertEqual(a, b, @"Unexpected new counter returned.");
}
@end
@implementation SNTMetricBooleanGaugeTest
@@ -114,6 +127,20 @@
NSDictionary *output = [b export];
XCTAssertEqualObjects(output, expected);
}
- (void)testAddingBooleanWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricBooleanGauge *a = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
fieldNames:@[]
helpText:@"Is the daemon connected."];
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
fieldNames:@[]
helpText:@"Is the daemon connected."];
XCTAssertEqual(a, b, @"Unexpected new boolean gauge returned.");
}
@end
@implementation SNTMetricGaugeInt64Test
@@ -168,6 +195,20 @@
XCTAssertEqualObjects([g export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricInt64Gauge *a = [metricSet int64GaugeWithName:@"/santa/int64gauge"
fieldNames:@[]
helpText:@"Test gauge."];
SNTMetricInt64Gauge *b = [metricSet int64GaugeWithName:@"/santa/int64gauge"
fieldNames:@[]
helpText:@"Test gauge."];
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
}
@end
@implementation SNTMetricDoubleGaugeTest
@@ -227,6 +268,19 @@
};
XCTAssertEqualObjects([g export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricDoubleGauge *a = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
fieldNames:@[]
helpText:@"Test gauge."];
SNTMetricDoubleGauge *b = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
fieldNames:@[]
helpText:@"Test gauge."];
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
}
@end
@implementation SNTMetricStringGaugeTest
@@ -240,6 +294,7 @@
[s set:@"testValue" forFieldValues:@[]];
XCTAssertEqualObjects([s getStringValueForFieldValues:@[]], @"testValue");
}
- (void)testExportNSDictionary {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
@@ -264,6 +319,20 @@
XCTAssertEqualObjects([s export], expected);
}
- (void)testAddingMetricWithSameSchema {
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
SNTMetricStringGauge *a = [metricSet stringGaugeWithName:@"/santa/stringgauge"
fieldNames:@[]
helpText:@"Test gauge."];
SNTMetricStringGauge *b = [metricSet stringGaugeWithName:@"/santa/stringgauge"
fieldNames:@[]
helpText:@"Test gauge."];
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
}
@end
@implementation SNTMetricSetTest
@@ -550,7 +619,6 @@
XCTAssertEqualObjects([metricSet export], expected);
}
@end
@implementation SNTMetricSetHelperFunctionsTest

View File

@@ -47,6 +47,8 @@
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)(void))reply;
- (void)setAllowedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockedPathRegex:(NSString *)pattern reply:(void (^)(void))reply;
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply;
- (void)setEnableBundles:(BOOL)bundlesEnabled reply:(void (^)(void))reply;
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;

View File

@@ -18,10 +18,12 @@
#import "Source/common/SNTXPCBundleServiceInterface.h"
@class SNTStoredEvent;
@class SNTDeviceEvent;
/// Protocol implemented by SantaGUI and utilized by santad
@protocol SNTNotifierXPC
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message;
- (void)postClientModeNotification:(SNTClientMode)clientmode;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
- (void)updateCountsForEvent:(SNTStoredEvent *)event

View File

@@ -15,6 +15,10 @@ objc_library(
"SNTAccessibleTextField.m",
"SNTAppDelegate.h",
"SNTAppDelegate.m",
"SNTBinaryMessageWindowController.h",
"SNTBinaryMessageWindowController.m",
"SNTDeviceMessageWindowController.h",
"SNTDeviceMessageWindowController.m",
"SNTMessageWindow.h",
"SNTMessageWindow.m",
"SNTMessageWindowController.h",
@@ -25,6 +29,7 @@ objc_library(
],
data = [
"Resources/AboutWindow.xib",
"Resources/DeviceMessageWindow.xib",
"Resources/MessageWindow.xib",
],
sdk_frameworks = [

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<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="3840" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>
@@ -47,7 +47,7 @@ There are no user-configurable settings.</string>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SRu-Kf-vu5">
<rect key="frame" x="130" y="21" width="111" height="32"/>
<rect key="frame" x="129" y="21" width="113" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="JHv-2J-QSe"/>
</constraints>
@@ -60,7 +60,7 @@ There are no user-configurable settings.</string>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Udo-BY-n7e">
<rect key="frame" x="240" y="21" width="111" height="32"/>
<rect key="frame" x="239" y="21" width="113" height="32"/>
<constraints>
<constraint firstAttribute="width" constant="99" id="2Xc-ax-2bV"/>
</constraints>

View File

@@ -0,0 +1,193 @@
<?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">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTDeviceMessageWindowController">
<connections>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<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"/>
<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"/>
<userGuides>
<userLayoutGuide location="344" affinity="minX"/>
</userGuides>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="16" y="290" 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"/>
<font key="font" metaFont="system"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="31" y="210" width="454" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="139" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device Name" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="115" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Device BSD Path" id="adC-be-Beh">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Device Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="139" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device Name" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.mntonname" id="bOu-gv-1Vh"/>
</connections>
</textField>
<textField toolTip="Device BSD Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="115" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Device BSD Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.mntfromname" id="Sry-KY-HDb"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="217" y="248" width="82" height="40"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="i8e-8n-tZS" userLabel="Label: Path">
<rect key="frame" x="8" y="91" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="a3r-ng-zxJ"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Remount Mode" id="4D0-8L-ihK">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Device BSD Path" verticalHuggingPriority="750" ambiguous="YES" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kmz-be-oWf" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="91" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="cbM-i5-bJ9"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Remount Mode" id="qI0-Na-kxN">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.readableRemountArgs" id="bOu-gv-1Vl"/>
</connections>
</textField>
<button verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="202" y="18" width="112" height="25"/>
<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="Ok" bezelStyle="texturedRounded" 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
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="kmz-be-oWf" firstAttribute="height" secondItem="i8e-8n-tZS" secondAttribute="height" id="6Ow-Sl-6Wc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
<constraint firstItem="kmz-be-oWf" firstAttribute="centerY" secondItem="i8e-8n-tZS" secondAttribute="centerY" id="ilC-5x-LNe"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="i8e-8n-tZS" firstAttribute="centerY" secondItem="YNz-ka-cBi" secondAttribute="centerY" constant="24" id="uZs-XT-PuZ"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="firstBaseline" secondItem="d9e-Wv-Y5H" secondAttribute="baseline" constant="24" id="xZu-AY-wX5"/>
<constraint firstItem="i8e-8n-tZS" firstAttribute="top" secondItem="YNz-ka-cBi" secondAttribute="bottom" constant="8" symbolic="YES" id="ylw-3s-9JW"/>
</constraints>
</view>
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="261.5" y="246"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
</objects>
</document>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19455" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19455"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@@ -21,8 +21,8 @@
<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="3840" height="1577"/>
<view key="contentView" id="Iwq-Lx-rLv">
<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"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
@@ -41,7 +41,7 @@
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="43" y="369" width="454" height="17"/>
<rect key="frame" x="43" y="354" width="454" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
@@ -56,7 +56,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="297" width="142" height="17"/>
<rect key="frame" x="8" y="283" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
@@ -70,7 +70,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="272" width="142" height="17"/>
<rect key="frame" x="8" y="259" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
</constraints>
@@ -84,7 +84,7 @@
</textFieldCell>
</textField>
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="297" width="315" height="17"/>
<rect key="frame" x="187" y="283" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
</constraints>
@@ -98,7 +98,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="247" width="142" height="17"/>
<rect key="frame" x="8" y="235" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -109,7 +109,7 @@
</textFieldCell>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="247" width="315" height="17"/>
<rect key="frame" x="187" y="235" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
</constraints>
@@ -127,7 +127,7 @@
</connections>
</textField>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="62" y="248" width="15" height="15"/>
<rect key="frame" x="62" y="235" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
@@ -149,7 +149,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<rect key="frame" x="8" y="222" width="142" height="17"/>
<rect key="frame" x="8" y="212" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -160,7 +160,7 @@
</textFieldCell>
</textField>
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="187" y="222" width="219" height="17"/>
<rect key="frame" x="187" y="214" width="219" height="13"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
@@ -177,7 +177,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
<rect key="frame" x="8" y="197" width="142" height="17"/>
<rect key="frame" x="8" y="191" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -195,7 +195,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="142" height="17"/>
<rect key="frame" x="8" y="156" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -206,7 +206,7 @@
</textFieldCell>
</textField>
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="157" width="294" height="17"/>
<rect key="frame" x="187" y="156" width="294" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
@@ -229,7 +229,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="142" height="17"/>
<rect key="frame" x="8" y="132" width="142" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -240,7 +240,7 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="154" y="34" width="112" height="23"/>
<rect key="frame" x="153" y="35" width="114" height="23"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
@@ -258,7 +258,7 @@ DQ
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="272" width="315" height="17"/>
<rect key="frame" x="187" y="259" width="315" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
@@ -272,7 +272,7 @@ DQ
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="113" y="80" width="315" height="29"/>
<rect key="frame" x="110" y="80" width="319" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
@@ -285,7 +285,7 @@ DQ
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="278" y="34" width="110" height="23"/>
<rect key="frame" x="277" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
@@ -305,7 +305,7 @@ DQ
</connections>
</button>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="132" width="294" height="17"/>
<rect key="frame" x="187" y="132" width="294" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
@@ -322,14 +322,14 @@ DQ
</binding>
</connections>
</textField>
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="199" width="217" height="12"/>
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="193" width="217" height="12"/>
<constraints>
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
</constraints>
</progressIndicator>
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
<rect key="frame" x="187" y="197" width="219" height="17"/>
<rect key="frame" x="187" y="193" width="219" height="13"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
</constraints>
@@ -346,7 +346,7 @@ DQ
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
<rect key="frame" x="187" y="182" width="219" height="17"/>
<rect key="frame" x="187" y="180" width="219" height="13"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
</constraints>
@@ -357,7 +357,7 @@ DQ
</textFieldCell>
</textField>
<box horizontalHuggingPriority="750" boxType="custom" borderType="line" title="Line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="168" y="132" width="1" height="207"/>
<rect key="frame" x="168" y="132" width="1" height="191"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
@@ -366,7 +366,7 @@ DQ
<font key="titleFont" metaFont="system"/>
</box>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="322" width="315" height="17"/>
<rect key="frame" x="187" y="307" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
</constraints>
@@ -384,7 +384,7 @@ DQ
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="322" width="142" height="17"/>
<rect key="frame" x="8" y="307" width="142" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
</constraints>
@@ -405,7 +405,7 @@ DQ
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="229" y="408" width="82" height="41"/>
<rect key="frame" x="229" y="392" width="82" height="40"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>

View File

@@ -0,0 +1,65 @@
/// Copyright 2015 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 <Cocoa/Cocoa.h>
#import "Source/santa/SNTMessageWindowController.h"
@class SNTStoredEvent;
///
/// Controller for a single message window.
///
@interface SNTBinaryMessageWindowController : SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
- (IBAction)showCertInfo:(id)sender;
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
@end

View File

@@ -0,0 +1,176 @@
/// Copyright 2015 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/santa/SNTBinaryMessageWindowController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santa/SNTMessageWindow.h"
@interface SNTBinaryMessageWindowController ()
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// A 'friendly' string representing the certificate information
@property(readonly, nonatomic) NSString *publisherInfo;
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property(weak) IBOutlet NSTextField *applicationNameLabel;
@end
@implementation SNTBinaryMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
if (![[SNTConfigurator configurator] eventDetailURL]) {
[self.openEventButton removeFromSuperview];
} else {
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
if (eventDetailText) {
[self.openEventButton setTitle:eventDetailText];
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
}
- (NSString *)messageHash {
return self.event.fileSHA256;
}
- (IBAction)showCertInfo:(id)sender {
// SFCertificatePanel expects an NSArray of SecCertificateRef's
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
for (MOLCertificate *cert in self.event.signingChain) {
[certArray addObject:(id)cert.certRef];
}
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil
certificates:certArray
showGroup:YES];
}
- (IBAction)openEventDetails:(id)sender {
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
[self closeWindow:sender];
[[NSWorkspace sharedWorkspace] openURL:url];
}
#pragma mark Generated properties
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return [NSSet set];
}
}
- (NSString *)publisherInfo {
MOLCertificate *leafCert = [self.event.signingChain firstObject];
if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
} else if (leafCert.commonName) {
return leafCert.commonName;
} else if (leafCert.orgName) {
return leafCert.orgName;
} else {
return nil;
}
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
customMessage:self.customMessage];
}
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
// UI updates must happen on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.event.idx isEqual:event.idx]) {
if (bundleHash) {
[self.bundleHashLabel setHidden:NO];
} else {
[self.bundleHashLabel removeFromSuperview];
[self.bundleHashTitle removeFromSuperview];
}
self.event.fileBundleHash = bundleHash;
[self.foundFileCountLabel removeFromSuperview];
[self.hashingIndicator setHidden:YES];
[self.openEventButton setEnabled:YES];
}
});
}
@end

View File

@@ -0,0 +1,38 @@
/// Copyright 2021 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 <Cocoa/Cocoa.h>
#import "Source/common/SNTDeviceEvent.h"
#import "Source/santa/SNTMessageWindowController.h"
NS_ASSUME_NONNULL_BEGIN
@class SNTStoredEvent;
///
/// Controller for a single message window.
///
@interface SNTDeviceMessageWindowController : SNTMessageWindowController
@property(weak) IBOutlet NSTextField *remountArgsLabel;
@property(weak) IBOutlet NSTextField *remountArgsTitle;
// The device event this window is for.
@property(readonly) SNTDeviceEvent *event;
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,57 @@
/// Copyright 2015 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/santa/SNTDeviceMessageWindowController.h"
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/santa/SNTMessageWindow.h"
NS_ASSUME_NONNULL_BEGIN
@interface SNTDeviceMessageWindowController ()
@property(copy, nullable) NSString *customMessage;
@end
@implementation SNTDeviceMessageWindowController
- (instancetype)initWithEvent:(SNTDeviceEvent *)event message:(nullable NSString *)message {
self = [super initWithWindowNibName:@"DeviceMessageWindow"];
if (self) {
_event = event;
_customMessage = message;
}
return self;
}
- (void)loadWindow {
[super loadWindow];
if (!self.event.remountArgs || [self.event.remountArgs count] <= 0) {
[self.remountArgsLabel removeFromSuperview];
[self.remountArgsTitle removeFromSuperview];
}
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage formatMessage:self.customMessage];
}
- (NSString *)messageHash {
return self.event.mntonname;
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,73 +1,21 @@
/// Copyright 2015 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 <Cocoa/Cocoa.h>
@class SNTStoredEvent;
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
///
/// Controller for a single message window.
///
@interface SNTMessageWindowController : NSWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message;
- (IBAction)showWindow:(id)sender;
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Generate a distinct key for a given displayed event. This key is used for silencing future
/// notifications.
- (NSString *)messageHash;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
/// Linked to checkbox in UI to prevent future notifications for the given event.
@property BOOL silenceFutureNotifications;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
@end

View File

@@ -1,117 +1,13 @@
/// Copyright 2015 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/santa/SNTMessageWindowController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <SecurityInterface/SFCertificatePanel.h>
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santa/SNTMessageWindow.h"
@interface SNTMessageWindowController ()
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// A 'friendly' string representing the certificate information
@property(readonly, nonatomic) NSString *publisherInfo;
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property(weak) IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@end
@implementation SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
if (![[SNTConfigurator configurator] eventDetailURL]) {
[self.openEventButton removeFromSuperview];
} else {
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
if (eventDetailText) {
[self.openEventButton setTitle:eventDetailText];
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
}
- (IBAction)showWindow:(id)sender {
[(SNTMessageWindow *)self.window fadeIn:sender];
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}
@@ -119,60 +15,20 @@
if (!self.delegate) return;
if (self.silenceFutureNotifications) {
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
[self.delegate windowDidCloseSilenceHash:[self messageHash]];
} else {
[self.delegate windowDidCloseSilenceHash:nil];
}
}
- (IBAction)showCertInfo:(id)sender {
// SFCertificatePanel expects an NSArray of SecCertificateRef's
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
for (MOLCertificate *cert in self.event.signingChain) {
[certArray addObject:(id)cert.certRef];
}
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
modalDelegate:nil
didEndSelector:nil
contextInfo:nil
certificates:certArray
showGroup:YES];
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
}
- (IBAction)openEventDetails:(id)sender {
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
[self closeWindow:sender];
[[NSWorkspace sharedWorkspace] openURL:url];
}
#pragma mark Generated properties
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return [NSSet set];
}
}
- (NSString *)publisherInfo {
MOLCertificate *leafCert = [self.event.signingChain firstObject];
if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
} else if (leafCert.commonName) {
return leafCert.commonName;
} else if (leafCert.orgName) {
return leafCert.orgName;
} else {
return nil;
}
}
- (NSAttributedString *)attributedCustomMessage {
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
customMessage:self.customMessage];
- (NSString *)messageHash {
return @"";
}
@end

View File

@@ -15,6 +15,8 @@
#import <Cocoa/Cocoa.h>
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/santa/SNTBinaryMessageWindowController.h"
#import "Source/santa/SNTDeviceMessageWindowController.h"
#import "Source/santa/SNTMessageWindowController.h"
///

View File

@@ -18,10 +18,12 @@
#import "Source/common/SNTBlockMessage.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santa/SNTMessageWindowController.h"
@interface SNTNotificationManager ()
@@ -57,13 +59,7 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
self.currentWindowController = nil;
if ([self.pendingNotifications count]) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
[self showQueuedWindow];
} else {
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
[bc resume];
@@ -85,6 +81,130 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[ud setObject:d forKey:silencedNotificationsKey];
}
- (BOOL)notificationAlreadyQueued:(SNTMessageWindowController *)pendingMsg {
for (SNTMessageWindowController *msg in self.pendingNotifications) {
if ([msg messageHash] == [pendingMsg messageHash]) {
return YES;
}
}
return NO;
}
- (void)queueMessage:(SNTMessageWindowController *)pendingMsg {
NSString *messageHash = [pendingMsg messageHash];
if ([self notificationAlreadyQueued:pendingMsg]) return;
// See if this message is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:messageHash];
} else {
LOGI(@"Notification silence: dropping notification for %@", messageHash);
return;
}
}
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
if (!self.currentWindowController) {
[self showQueuedWindow];
}
}
- (void)showQueuedWindow {
// Notifications arrive on a background thread but UI updates must happen on the main thread.
// This includes making windows.
dispatch_async(dispatch_get_main_queue(), ^{
// If a notification isn't currently being displayed, display the incoming one.
// This check will generally be redundant, as we'd generally want to check this prior to
// starting work on the main thread.
if (!self.currentWindowController) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
SNTBinaryMessageWindowController *controller =
(SNTBinaryMessageWindowController *)self.currentWindowController;
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:controller.event withController:controller];
});
}
}
});
}
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
withController:(SNTBinaryMessageWindowController *)withController {
withController.foundFileCountLabel.stringValue = @"Searching for files...";
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
bc.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
[bc resume];
// Wait a max of 5 secs for the bundle service
// 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];
return;
}
[[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
[withController.progress becomeCurrentWithPendingUnitCount:100];
// Start hashing. Progress is reported to the root NSProgress
// (currentWindowController.progress).
[[bc remoteObjectProxy]
hashBundleBinariesForEvent:event
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
// Revert to displaying the blockable event if we fail to calculate the
// bundle hash
if (!bh)
return [withController updateBlockNotification:event
withBundleHash:nil];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
event.fileBundleHashMilliseconds = ms;
event.fileBundleExecutableRelPath =
[events.firstObject fileBundleExecutableRelPath];
for (SNTStoredEvent *se in events) {
se.fileBundleHash = bh;
se.fileBundleBinaryCount = @(events.count);
se.fileBundleHashMilliseconds = ms;
}
// Send the results to santad. It will decide if they need to be
// synced.
MOLXPCConnection *daemonConn =
[SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] syncBundleEvent:event
relatedEvents:events];
[daemonConn invalidate];
// Update the UI with the bundle hash. Also make the openEventButton
// available.
[withController updateBlockNotification:event withBundleHash:bh];
[bc invalidate];
}];
[withController.progress resignCurrent];
}
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
@@ -113,52 +233,15 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
}
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
// See if this binary is already in the list of pending notifications.
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// See if this binary is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else {
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
return;
}
}
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
// Notifications arrive on a background thread but UI updates must happen on the main thread.
// This includes making windows.
dispatch_async(dispatch_get_main_queue(), ^{
SNTMessageWindowController *pendingMsg =
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
SNTBinaryMessageWindowController *pendingMsg =
[[SNTBinaryMessageWindowController alloc] initWithEvent:event andMessage:message];
// If a notification isn't currently being displayed, display the incoming one.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
}
});
[self queueMessage:pendingMsg];
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
@@ -169,98 +252,35 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
- (void)postUSBBlockNotification:(SNTDeviceEvent *)event withCustomMessage:(NSString *)message {
if (!event) {
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
SNTDeviceMessageWindowController *pendingMsg =
[[SNTDeviceMessageWindowController alloc] initWithEvent:event message:message];
[self queueMessage:pendingMsg];
}
#pragma mark SNTBundleNotifierXPC protocol methods
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount {
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
self.currentWindowController.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
}
if ([self.currentWindowController isKindOfClass:[SNTBinaryMessageWindowController class]]) {
SNTBinaryMessageWindowController *controller =
(SNTBinaryMessageWindowController *)self.currentWindowController;
#pragma mark SNTBundleNotifierXPC helper methods
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
self.currentWindowController.foundFileCountLabel.stringValue = @"Searching for files...";
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
MOLXPCConnection *bc = [SNTXPCBundleServiceInterface configuredConnection];
bc.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
[bc resume];
// Wait a max of 5 secs for the bundle service
// Otherwise abandon bundle hashing and display the blockable event.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self updateBlockNotification:event withBundleHash:nil];
return;
}
[[bc remoteObjectProxy] setNotificationListener:self.notificationListener];
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
[[bc remoteObjectProxy]
hashBundleBinariesForEvent:event
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
// Revert to displaying the blockable event if we fail to calculate the
// bundle hash
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
event.fileBundleHashMilliseconds = ms;
event.fileBundleExecutableRelPath =
[events.firstObject fileBundleExecutableRelPath];
for (SNTStoredEvent *se in events) {
se.fileBundleHash = bh;
se.fileBundleBinaryCount = @(events.count);
se.fileBundleHashMilliseconds = ms;
}
// Send the results to santad. It will decide if they need to be
// synced.
MOLXPCConnection *daemonConn =
[SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] syncBundleEvent:event
relatedEvents:events];
[daemonConn invalidate];
// Update the UI with the bundle hash. Also make the openEventButton
// available.
[self updateBlockNotification:event withBundleHash:bh];
[bc invalidate];
}];
[self.currentWindowController.progress resignCurrent];
}
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
if (bundleHash) {
[self.currentWindowController.bundleHashLabel setHidden:NO];
} else {
[self.currentWindowController.bundleHashLabel removeFromSuperview];
[self.currentWindowController.bundleHashTitle removeFromSuperview];
}
self.currentWindowController.event.fileBundleHash = bundleHash;
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
[self.currentWindowController.hashingIndicator setHidden:YES];
[self.currentWindowController.openEventButton setEnabled:YES];
if ([controller.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
controller.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu %@", binaryCount,
hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
});
}
}
@end

View File

@@ -19,25 +19,7 @@ objc_library(
"Commands/SNTCommandVersion.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/sync/NSData+Zlib.h",
"Commands/sync/NSData+Zlib.m",
"Commands/sync/SNTCommandSync.m",
"Commands/sync/SNTCommandSyncConstants.h",
"Commands/sync/SNTCommandSyncConstants.m",
"Commands/sync/SNTCommandSyncEventUpload.h",
"Commands/sync/SNTCommandSyncEventUpload.m",
"Commands/sync/SNTCommandSyncManager.h",
"Commands/sync/SNTCommandSyncManager.m",
"Commands/sync/SNTCommandSyncPostflight.h",
"Commands/sync/SNTCommandSyncPostflight.m",
"Commands/sync/SNTCommandSyncPreflight.h",
"Commands/sync/SNTCommandSyncPreflight.m",
"Commands/sync/SNTCommandSyncRuleDownload.h",
"Commands/sync/SNTCommandSyncRuleDownload.m",
"Commands/sync/SNTCommandSyncStage.h",
"Commands/sync/SNTCommandSyncStage.m",
"Commands/sync/SNTCommandSyncState.h",
"Commands/sync/SNTCommandSyncState.m",
"Commands/SNTCommandSync.m",
] + select({
"//:opt_build": [],
"//conditions:default": [
@@ -50,7 +32,6 @@ objc_library(
sdk_dylibs = ["libz"],
sdk_frameworks = ["IOKit"],
deps = [
":FCM_lib",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
@@ -65,25 +46,14 @@ objc_library(
"//Source/common:SNTSystemInfo",
"//Source/common:SNTXPCBundleServiceInterface",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SNTXPCUnprivilegedControlInterface",
"//Source/santasyncservice:sync_lib",
"@FMDB",
"@MOLAuthenticatingURLSession",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
objc_library(
name = "FCM_lib",
srcs = ["Commands/sync/SNTCommandSyncFCM.m"],
hdrs = ["Commands/sync/SNTCommandSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
deps = [
"@MOLAuthenticatingURLSession",
],
)
macos_command_line_application(
name = "santactl",
bundle_id = "com.google.santa.ctl",
@@ -124,56 +94,6 @@ santa_unit_test(
],
)
santa_unit_test(
name = "SNTCommandSyncTest",
srcs = [
"Commands/sync/NSData+Zlib.h",
"Commands/sync/NSData+Zlib.m",
"Commands/sync/SNTCommandSync.m",
"Commands/sync/SNTCommandSyncConstants.h",
"Commands/sync/SNTCommandSyncConstants.m",
"Commands/sync/SNTCommandSyncEventUpload.h",
"Commands/sync/SNTCommandSyncEventUpload.m",
"Commands/sync/SNTCommandSyncManager.h",
"Commands/sync/SNTCommandSyncManager.m",
"Commands/sync/SNTCommandSyncPostflight.h",
"Commands/sync/SNTCommandSyncPostflight.m",
"Commands/sync/SNTCommandSyncPreflight.h",
"Commands/sync/SNTCommandSyncPreflight.m",
"Commands/sync/SNTCommandSyncRuleDownload.h",
"Commands/sync/SNTCommandSyncRuleDownload.m",
"Commands/sync/SNTCommandSyncStage.h",
"Commands/sync/SNTCommandSyncStage.m",
"Commands/sync/SNTCommandSyncState.h",
"Commands/sync/SNTCommandSyncState.m",
"Commands/sync/SNTCommandSyncTest.m",
"SNTCommand.h",
"SNTCommand.m",
"SNTCommandController.h",
"SNTCommandController.m",
],
resources = glob([
"Commands/sync/testdata/*.json",
"Commands/sync/testdata/*.plist",
]),
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTCommandMetricsTest",
srcs = ["Commands/SNTCommandMetricsTest.m"],
@@ -191,7 +111,6 @@ test_suite(
tests = [
":SNTCommandFileInfoTest",
":SNTCommandMetricsTest",
":SNTCommandSyncTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -71,7 +71,7 @@ NSString *formattedStringForKeyArray(NSArray<NSString *> *array) {
// Properties set from commandline flags
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) int certIndex; // 0 means no cert-index specified
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic, copy) NSDictionary<NSString *, NSRegularExpression *> *outputFilters;
@@ -163,9 +163,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
@"%@\n"
@" --cert-index: Supply an integer corresponding to a certificate of the\n"
@" signing chain to show info only for that certificate.\n"
@" 1 for the leaf certificate\n"
@" -1 for the root certificate\n"
@" 2 and up for the intermediates / root\n"
@" 0 up to n for the leaf certificate up to the root\n"
@" -1 down to -n-1 for the root certificate down to the leaf\n"
@"\n"
@" --filter: Use predicates of the form 'key=regex' to filter out which files\n"
@" are displayed. Valid keys are the same as for --key. Value is a\n"
@@ -590,12 +589,24 @@ REGISTER_COMMAND_NAME(@"fileinfo")
// First build up a dictionary containing all the information we want to print out
NSMutableDictionary *outputDict = [NSMutableDictionary dictionary];
if (self.certIndex) {
int index = [self.certIndex intValue];
// --cert-index flag implicitly means that we want only the signing chain. So we find the
// specified certificate in the signing chain, then print out values for all keys in cert.
NSArray *signingChain = self.propertyMap[kSigningChain](self, fileInfo);
if (!signingChain || !signingChain.count) return; // check signing chain isn't empty
int index = (self.certIndex == -1) ? (int)signingChain.count - 1 : self.certIndex - 1;
if (index < 0 || index >= (int)signingChain.count) return; // check that index is valid
if (index < 0) {
index = (int)signingChain.count - -(index);
if (index < 0) {
fprintf(stderr, "Invalid --cert-index: %d\n", index);
return;
}
} else {
if (index >= (int)signingChain.count) {
fprintf(stderr, "Invalid --cert-index: %d\n", index);
return;
}
}
NSDictionary *cert = signingChain[index];
// Check if we should skip over this item based on outputFilters.
@@ -702,14 +713,12 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
int index = 0;
NSScanner *scanner = [NSScanner scannerWithString:arguments[i]];
if (![scanner scanInt:&index] || !scanner.atEnd || index == 0 || index < -1) {
[self
printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid argument for --cert-index\n"
@" --cert-index argument must be one of -1, 1, 2, 3, ...",
arguments[i]]];
if (![scanner scanInt:&index] || !scanner.atEnd) {
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid argument for --cert-index\n",
arguments[i]]];
}
self.certIndex = index;
self.certIndex = @(index);
} else if ([arg caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
i += 1; // advance to next argument and grab the key
if (i >= nargs || [arguments[i] hasPrefix:@"--"]) {

View File

@@ -24,7 +24,7 @@
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(nonatomic) BOOL recursive;
@property(nonatomic) BOOL jsonOutput;
@property(nonatomic) int certIndex;
@property(nonatomic) NSNumber *certIndex;
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
@property(nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
+ (NSArray *)fileInfoKeys;
@@ -71,7 +71,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
- (void)testParseArgumentsCertIndex {
NSArray *filePaths = [self.cfi parseArguments:@[ @"--cert-index", @"1", @"/usr/bin/yes" ]];
XCTAssertEqual(self.cfi.certIndex, 1);
XCTAssertEqual([self.cfi.certIndex intValue], 1);
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
}

View File

@@ -148,7 +148,7 @@ REGISTER_COMMAND_NAME(@"rule")
}
if (check) {
if (!newRule.identifier) return [self printErrorUsageAndExit:@"--check requires --sha256"];
if (!newRule.identifier) return [self printErrorUsageAndExit:@"--check requires --identifier"];
return [self printStateOfRule:newRule daemonConnection:self.daemonConn];
}
@@ -170,7 +170,7 @@ REGISTER_COMMAND_NAME(@"rule")
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.identifier) {
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
[self printErrorUsageAndExit:@"Either SHA-256, team ID, or path to file must be specified"];
}
[[self.daemonConn remoteObjectProxy]
@@ -183,10 +183,25 @@ REGISTER_COMMAND_NAME(@"rule")
LOGD(@"Failure reason: %@", error.localizedFailureReason);
exit(1);
} else {
NSString *ruleType;
switch (newRule.type) {
case SNTRuleTypeCertificate:
case SNTRuleTypeBinary: {
ruleType = @"SHA-256";
break;
}
case SNTRuleTypeTeamID: {
ruleType = @"Team ID";
break;
}
default: ruleType = @"(Unknown type)";
}
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
printf("Removed rule for %s: %s.\n", [ruleType UTF8String],
[newRule.identifier UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
printf("Added rule for %s: %s.\n", [ruleType UTF8String],
[newRule.identifier UTF8String]);
}
exit(0);
}

View File

@@ -188,6 +188,10 @@ REGISTER_COMMAND_NAME(@"status")
@"watchdog_ram_events" : @(ramEvents),
@"watchdog_cpu_peak" : @(cpuPeak),
@"watchdog_ram_peak" : @(ramPeak),
@"block_usb" : @(configurator.blockUSBMount),
@"remount_usb_mode" : (configurator.blockUSBMount && configurator.remountUSBMode.count
? configurator.remountUSBMode
: @""),
},
@"database" : @{
@"binary_rules" : @(binaryRuleCount),
@@ -229,6 +233,14 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %lld\n", "Root cache count", rootCacheCount);
printf(" %-25s | %lld\n", "Non-root cache count", nonRootCacheCount);
}
printf(" %-25s | %s\n", "USB Blocking", (configurator.blockUSBMount ? "Yes" : "No"));
if (configurator.blockUSBMount && configurator.remountUSBMode.count > 0) {
printf(" %-25s | %s\n", "USB Remounting Mode:",
[[configurator.remountUSBMode componentsJoinedByString:@", "] UTF8String]);
}
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);

View File

@@ -19,13 +19,13 @@
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
#import "Source/santasyncservice/SNTSyncManager.h"
@interface SNTCommandSync : SNTCommand <SNTCommandProtocol>
@property MOLXPCConnection *listener;
@property SNTCommandSyncManager *syncManager;
@property SNTSyncManager *syncManager;
@end
@implementation SNTCommandSync
@@ -68,8 +68,8 @@ REGISTER_COMMAND_NAME(@"sync")
}
BOOL daemon = [arguments containsObject:@"--daemon"];
self.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:self.daemonConn
isDaemon:daemon];
self.syncManager = [[SNTSyncManager alloc] initWithDaemonConnection:self.daemonConn
isDaemon:daemon];
// 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.

View File

@@ -90,18 +90,25 @@ REGISTER_COMMAND_NAME(@"version")
return @"not found";
}
- (NSString *)composeVersionsFromDict:(NSDictionary *)dict {
if (!dict[@"CFBundleVersion"]) return @"";
NSString *productVersion = dict[@"CFBundleShortVersionString"];
NSString *buildVersion = [[dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
return [NSString stringWithFormat:@"%@ (build %@)", productVersion, buildVersion];
}
- (NSString *)santadVersion {
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaDPath)];
return daemonInfo.bundleVersion ?: @"";
return [self composeVersionsFromDict:daemonInfo.infoPlist];
}
- (NSString *)santaAppVersion {
SNTFileInfo *guiInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaAppPath)];
return guiInfo.bundleVersion ?: @"";
return [self composeVersionsFromDict:guiInfo.infoPlist];
}
- (NSString *)santactlVersion {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] ?: @"";
return [self composeVersionsFromDict:[[NSBundle mainBundle] infoDictionary]];
}
@end

View File

@@ -1 +0,0 @@
{"rules": [{"rule_type": "CERTIFICATE", "policy": "BLACKLIST", "sha256": "7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142", "custom_msg": "Hi There"}]}

View File

@@ -14,6 +14,8 @@ objc_library(
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTDeviceManager.h",
"EventProviders/SNTDeviceManager.mm",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
@@ -56,6 +58,7 @@ objc_library(
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDeviceEvent",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
@@ -99,6 +102,27 @@ objc_library(
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)
objc_library(
name = "DiskArbitrationTestLib",
testonly = 1,
srcs = [
"EventProviders/DiskArbitrationTestUtil.h",
"EventProviders/DiskArbitrationTestUtil.mm",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
sdk_frameworks = [
"DiskArbitration",
"IOKit",
],
)
macos_bundle(
@@ -140,6 +164,7 @@ santa_unit_test(
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTXPCNotifierInterface",
@@ -199,6 +224,7 @@ santa_unit_test(
"EventProviders/SNTEndpointSecurityManagerTest.mm",
"EventProviders/SNTEventProvider.h",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
@@ -211,6 +237,27 @@ santa_unit_test(
],
)
santa_unit_test(
name = "SNTDeviceManagerTest",
srcs = [
"EventProviders/SNTDeviceManagerTest.mm",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":DiskArbitrationTestLib",
":EndpointSecurityTestLib",
":santad_lib",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationTest",
srcs = [
@@ -270,3 +317,16 @@ santa_unit_test(
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTApplicationCoreMetricsTest",
":SNTApplicationTest",
":SNTEndpointSecurityManagerTest",
":SNTEventTableTest",
":SNTExecutionControllerTest",
":SNTRuleTableTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -0,0 +1,84 @@
/// Copyright 2021 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.
#include <CoreFoundation/CFDictionary.h>
#include <CoreFoundation/CoreFoundation.h>
#include <DiskArbitration/DiskArbitration.h>
#include <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// Mock object to point the opaque DADiskRefs to instead.
// Note that this will have undefined behavior for DA functions that aren't
// shimmed out by this utility, as the original DADiskRef refers to a completely
// different struct managed by the CFRuntime.
// https://opensource.apple.com/source/DiskArbitration/DiskArbitration-297.70.1/DiskArbitration/DADisk.c.auto.html
@interface MockDADisk : NSObject
@property(nonatomic) NSDictionary *diskDescription;
@property(nonatomic, readwrite) NSString *name;
@end
typedef void (^MockDADiskAppearedCallback)(DADiskRef ref);
// Singleton mock fixture around all of the DiskArbitration framework functions
@interface MockDiskArbitration : NSObject
@property(nonatomic, readwrite, nonnull)
NSMutableDictionary<NSString *, MockDADisk *> *insertedDevices;
@property(nonatomic, readwrite, nonnull)
NSMutableArray<MockDADiskAppearedCallback> *diskAppearedCallbacks;
@property(nonatomic) BOOL wasRemounted;
@property(nonatomic, nullable) dispatch_queue_t sessionQueue;
- (instancetype _Nonnull)init;
- (void)reset;
// Also triggers DADiskRegisterDiskAppearedCallback
- (void)insert:(MockDADisk *)ref bsdName:(NSString *)bsdName;
// Retrieve an initialized singleton MockDiskArbitration object
+ (instancetype _Nonnull)mockDiskArbitration;
@end
//
// All DiskArbitration functions used in SNTDeviceManager and shimmed out accordingly.
//
CF_EXTERN_C_BEGIN
void DADiskMountWithArguments(DADiskRef _Nonnull disk, CFURLRef __nullable path,
DADiskMountOptions options, DADiskMountCallback __nullable callback,
void *__nullable context,
CFStringRef __nullable arguments[_Nullable]);
DADiskRef __nullable DADiskCreateFromBSDName(CFAllocatorRef __nullable allocator,
DASessionRef session, const char *name);
CFDictionaryRef __nullable DADiskCopyDescription(DADiskRef disk);
void DARegisterDiskAppearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskAppearedCallback callback, void *__nullable context);
void DARegisterDiskDisappearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskDisappearedCallback callback,
void *__nullable context);
void DARegisterDiskDescriptionChangedCallback(DASessionRef session,
CFDictionaryRef __nullable match,
CFArrayRef __nullable watch,
DADiskDescriptionChangedCallback callback,
void *__nullable context);
void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue);
DASessionRef __nullable DASessionCreate(CFAllocatorRef __nullable allocator);
CF_EXTERN_C_END
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,120 @@
/// Copyright 2021 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>
#include <stdlib.h>
#import "Source/santad/EventProviders/DiskArbitrationTestUtil.h"
NS_ASSUME_NONNULL_BEGIN
@implementation MockDADisk
@end
@implementation MockDiskArbitration
- (instancetype _Nonnull)init {
self = [super init];
if (self) {
_insertedDevices = [NSMutableDictionary dictionary];
_diskAppearedCallbacks = [NSMutableArray array];
}
return self;
}
- (void)reset {
[self.insertedDevices removeAllObjects];
[self.diskAppearedCallbacks removeAllObjects];
self.sessionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.wasRemounted = NO;
}
- (void)insert:(MockDADisk *)ref bsdName:(NSString *)bsdName {
self.insertedDevices[bsdName] = ref;
for (MockDADiskAppearedCallback callback in self.diskAppearedCallbacks) {
dispatch_sync(self.sessionQueue, ^{
callback((__bridge DADiskRef)ref);
});
}
}
// Retrieve an initialized singleton MockDiskArbitration object
+ (instancetype _Nonnull)mockDiskArbitration {
static MockDiskArbitration *sharedES;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedES = [[MockDiskArbitration alloc] init];
});
return sharedES;
};
@end
void DADiskMountWithArguments(DADiskRef _Nonnull disk, CFURLRef __nullable path,
DADiskMountOptions options, DADiskMountCallback __nullable callback,
void *__nullable context,
CFStringRef __nullable arguments[_Nullable]) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
mockDA.wasRemounted = YES;
}
DADiskRef __nullable DADiskCreateFromBSDName(CFAllocatorRef __nullable allocator,
DASessionRef session, const char *name) {
NSString *nsName = [NSString stringWithUTF8String:name];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
MockDADisk *got = mockDA.insertedDevices[nsName];
DADiskRef ref = (__bridge DADiskRef)got;
CFRetain(ref);
return ref;
}
CFDictionaryRef __nullable DADiskCopyDescription(DADiskRef disk) {
CFDictionaryRef description = NULL;
if (disk) {
MockDADisk *mockDisk = (__bridge MockDADisk *)disk;
description = (__bridge_retained CFDictionaryRef)mockDisk.diskDescription;
}
return description;
}
void DARegisterDiskAppearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskAppearedCallback callback, void *__nullable context) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA.diskAppearedCallbacks addObject:^(DADiskRef ref) {
callback(ref, context);
}];
}
void DARegisterDiskDisappearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
DADiskDisappearedCallback callback,
void *__nullable context){};
void DARegisterDiskDescriptionChangedCallback(DASessionRef session,
CFDictionaryRef __nullable match,
CFArrayRef __nullable watch,
DADiskDescriptionChangedCallback callback,
void *__nullable context){};
void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue) {
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
mockDA.sessionQueue = queue;
};
DASessionRef __nullable DASessionCreate(CFAllocatorRef __nullable allocator) {
return (__bridge DASessionRef)[MockDiskArbitration mockDiskArbitration];
};
NS_ASSUME_NONNULL_END

View File

@@ -46,7 +46,7 @@ typedef void (^ESCallback)(ESResponse *_Nonnull);
@interface MockEndpointSecurity : NSObject
@property NSMutableArray *_Nonnull subscriptions;
- (void)reset;
- (void)registerResponseCallback:(ESCallback _Nonnull)callback;
- (void)registerResponseCallback:(es_event_type_t)t withCallback:(ESCallback _Nonnull)callback;
- (void)triggerHandler:(es_message_t *_Nonnull)msg;
/// Retrieve an initialized singleton MockEndpointSecurity object

View File

@@ -89,19 +89,22 @@ CF_EXTERN_C_END
@implementation ESResponse
@end
@interface MockEndpointSecurity ()
@property NSMutableArray<ESCallback> *responseCallbacks;
@property NSObject *client;
@interface MockESClient : NSObject
@property NSMutableArray *_Nonnull subscriptions;
@property es_handler_block_t handler;
@end
@implementation MockEndpointSecurity
@implementation MockESClient
- (instancetype)init {
self = [super init];
if (self) {
_responseCallbacks = [NSMutableArray array];
_subscriptions = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
[self resetSubscriptions];
@synchronized(self) {
_subscriptions = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
for (size_t i = 0; i < ES_EVENT_TYPE_LAST; i++) {
[self.subscriptions addObject:@NO];
}
}
}
return self;
};
@@ -112,31 +115,78 @@ CF_EXTERN_C_END
}
}
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
self.handler((__bridge es_client_t *_Nullable)self, msg);
}
- (void)dealloc {
@synchronized(self) {
[self.subscriptions removeAllObjects];
}
}
@end
@interface MockEndpointSecurity ()
@property NSMutableArray<MockESClient *> *clients;
// Array of collections of ESCallback blocks
// This should be of size ES_EVENT_TYPE_LAST, allowing for indexing by ES_EVENT_TYPE_xxx members.
@property NSMutableArray<NSMutableArray<ESCallback> *> *responseCallbacks;
@end
@implementation MockEndpointSecurity
- (instancetype)init {
self = [super init];
if (self) {
@synchronized(self) {
_clients = [NSMutableArray array];
_responseCallbacks = [NSMutableArray arrayWithCapacity:ES_EVENT_TYPE_LAST];
for (size_t i = 0; i < ES_EVENT_TYPE_LAST; i++) {
[self.responseCallbacks addObject:[NSMutableArray array]];
}
[self reset];
}
}
return self;
};
- (void)resetResponseCallbacks {
for (NSMutableArray *callback in self.responseCallbacks) {
if (callback != nil) {
[callback removeAllObjects];
}
}
}
- (void)reset {
@synchronized(self) {
[self.responseCallbacks removeAllObjects];
self.handler = nil;
self.client = nil;
[self.clients removeAllObjects];
[self resetResponseCallbacks];
}
};
- (void)newClient:(es_client_t *_Nullable *_Nonnull)client
handler:(es_handler_block_t __strong)handler {
// es_client_t is generally used as a pointer to an opaque struct (secretly a mach port).
// We just want to set it to something nonnull for passing initialization checks. It shouldn't
// ever be directly dereferenced.
self.client = [[NSObject alloc] init];
*client = (__bridge es_client_t *)self.client;
self.handler = handler;
// There is also a few nonnull initialization checks on it.
MockESClient *mockClient = [[MockESClient alloc] init];
*client = (__bridge es_client_t *)mockClient;
mockClient.handler = handler;
[self.clients addObject:mockClient];
}
- (void)triggerHandler:(es_message_t *_Nonnull)msg {
self.handler((__bridge es_client_t *_Nullable)self.client, msg);
for (MockESClient *client in self.clients) {
if (client.subscriptions[msg->event_type]) {
[client triggerHandler:msg];
}
}
}
- (void)registerResponseCallback:(ESCallback _Nonnull)callback {
- (void)registerResponseCallback:(es_event_type_t)t withCallback:(ESCallback _Nonnull)callback {
@synchronized(self) {
[self.responseCallbacks addObject:callback];
[self.responseCallbacks[t] addObject:callback];
}
}
@@ -147,7 +197,7 @@ CF_EXTERN_C_END
ESResponse *response = [[ESResponse alloc] init];
response.result = result;
response.shouldCache = cache;
for (void (^callback)(ESResponse *) in self.responseCallbacks) {
for (void (^callback)(ESResponse *) in self.responseCallbacks[msg->event_type]) {
callback(response);
}
}
@@ -156,10 +206,22 @@ CF_EXTERN_C_END
- (void)setSubscriptions:(const es_event_type_t *_Nonnull)events
event_count:(uint32_t)event_count
value:(NSNumber *)value {
value:(NSNumber *)value
client:(es_client_t *)client {
@synchronized(self) {
MockESClient *toUpdate = nil;
for (MockESClient *c in self.clients) {
if (client == (__bridge es_client_t *)c) {
toUpdate = c;
}
}
if (toUpdate == nil) {
NSLog(@"setting subscription for unknown client");
return;
}
for (size_t i = 0; i < event_count; i++) {
self.subscriptions[events[i]] = value;
toUpdate.subscriptions[events[i]] = value;
}
}
}
@@ -212,7 +274,8 @@ es_return_t es_subscribe(es_client_t *_Nonnull client, const es_event_type_t *_N
uint32_t event_count) {
[[MockEndpointSecurity mockEndpointSecurity] setSubscriptions:events
event_count:event_count
value:@YES];
value:@YES
client:client];
return ES_RETURN_SUCCESS;
}
API_AVAILABLE(macos(10.15))
@@ -221,7 +284,8 @@ es_return_t es_unsubscribe(es_client_t *_Nonnull client, const es_event_type_t *
uint32_t event_count) {
[[MockEndpointSecurity mockEndpointSecurity] setSubscriptions:events
event_count:event_count
value:@NO];
value:@NO
client:client];
return ES_RETURN_SUCCESS;
};

View File

@@ -0,0 +1,42 @@
/// Copyright 2021 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 <DiskArbitration/DiskArbitration.h>
#import <Foundation/Foundation.h>
#include <EndpointSecurity/EndpointSecurity.h>
#include "Source/common/SNTDeviceEvent.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^SNTDeviceBlockCallback)(SNTDeviceEvent *event);
/*
* Manages DiskArbitration and EndpointSecurity to monitor/block/remount USB
* storage devices.
*/
@interface SNTDeviceManager : NSObject
@property(nonatomic, readwrite) BOOL subscribed;
@property(nonatomic, readwrite) BOOL blockUSBMount;
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
@property(nonatomic, nullable) SNTDeviceBlockCallback deviceBlockCallback;
- (instancetype)init;
- (void)listen;
- (BOOL)subscribed;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,315 @@
/// Copyright 2021 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/santad/EventProviders/SNTDeviceManager.h"
#import <DiskArbitration/DiskArbitration.h>
#import <Foundation/Foundation.h>
#include <bsm/libbsm.h>
#include <errno.h>
#include <libproc.h>
#include <sys/mount.h>
#include <atomic>
#include <memory>
#import "Source/common/SNTDeviceEvent.h"
#import "Source/common/SNTLogging.h"
#import "Source/santad/Logs/SNTEventLog.h"
void diskMountedCallback(DADiskRef disk, DADissenterRef dissenter, void *context) {
if (dissenter) {
DAReturn status = DADissenterGetStatus(dissenter);
NSString *statusString = (NSString *)DADissenterGetStatusString(dissenter);
IOReturn systemCode = err_get_system(status);
IOReturn subSystemCode = err_get_sub(status);
IOReturn errorCode = err_get_code(status);
LOGE(
@"SNTDeviceManager: dissenter status codes: system: %d, subsystem: %d, err: %d; status: %s",
systemCode, subSystemCode, errorCode, [statusString UTF8String]);
}
}
void diskAppearedCallback(DADiskRef disk, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
SNTEventLog *logger = [SNTEventLog logger];
if (logger) [logger logDiskAppeared:props];
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
if (props[@"DAVolumePath"]) {
SNTEventLog *logger = [SNTEventLog logger];
if (logger) [logger logDiskAppeared:props];
}
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
SNTEventLog *logger = [SNTEventLog logger];
if (logger) [logger logDiskDisappeared:props];
}
NSArray<NSString *> *maskToMountArgs(long remountOpts) {
NSMutableArray<NSString *> *args = [NSMutableArray array];
if (remountOpts & MNT_RDONLY) [args addObject:@"rdonly"];
if (remountOpts & MNT_NOEXEC) [args addObject:@"noexec"];
if (remountOpts & MNT_NOSUID) [args addObject:@"nosuid"];
if (remountOpts & MNT_DONTBROWSE) [args addObject:@"nobrowse"];
if (remountOpts & MNT_UNKNOWNPERMISSIONS) [args addObject:@"noowners"];
if (remountOpts & MNT_NODEV) [args addObject:@"nodev"];
if (remountOpts & MNT_JOURNALED) [args addObject:@"-j"];
if (remountOpts & MNT_ASYNC) [args addObject:@"async"];
return args;
}
long mountArgsToMask(NSArray<NSString *> *args) {
long flags = 0;
for (NSString *i in args) {
NSString *arg = [i lowercaseString];
if ([arg isEqualToString:@"rdonly"])
flags |= MNT_RDONLY;
else if ([arg isEqualToString:@"noexec"])
flags |= MNT_NOEXEC;
else if ([arg isEqualToString:@"nosuid"])
flags |= MNT_NOSUID;
else if ([arg isEqualToString:@"nobrowse"])
flags |= MNT_DONTBROWSE;
else if ([arg isEqualToString:@"noowners"])
flags |= MNT_UNKNOWNPERMISSIONS;
else if ([arg isEqualToString:@"nodev"])
flags |= MNT_NODEV;
else if ([arg isEqualToString:@"-j"])
flags |= MNT_JOURNALED;
else if ([arg isEqualToString:@"async"])
flags |= MNT_ASYNC;
else
LOGE(@"SNTDeviceManager: unexpected mount arg: %@", arg);
}
return flags;
}
NS_ASSUME_NONNULL_BEGIN
@interface SNTDeviceManager ()
@property DASessionRef diskArbSession;
@property(nonatomic, readonly) es_client_t *client;
@property(nonatomic, readonly) dispatch_queue_t esAuthQueue;
@property(nonatomic, readonly) dispatch_queue_t diskQueue;
@end
@implementation SNTDeviceManager
- (instancetype)init API_AVAILABLE(macos(10.15)) {
self = [super init];
if (self) {
_blockUSBMount = false;
_diskQueue = dispatch_queue_create("com.google.santad.disk_queue", DISPATCH_QUEUE_SERIAL);
_esAuthQueue =
dispatch_queue_create("com.google.santa.daemon.es_device_auth", DISPATCH_QUEUE_CONCURRENT);
_diskArbSession = DASessionCreate(NULL);
DASessionSetDispatchQueue(_diskArbSession, _diskQueue);
if (@available(macos 10.15, *)) [self initES];
}
return self;
}
- (void)initES API_AVAILABLE(macos(10.15)) {
while (!self.client) {
es_client_t *client = NULL;
es_new_client_result_t ret = es_new_client(&client, ^(es_client_t *c, const es_message_t *m) {
// Set timeout to 5 seconds before the ES deadline.
[self handleESMessageWithTimeout:m
withClient:c
timeout:dispatch_time(m->deadline, NSEC_PER_SEC * -5)];
});
switch (ret) {
case ES_NEW_CLIENT_RESULT_SUCCESS:
LOGI(@"Connected to EndpointSecurity");
_client = client;
return;
case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED:
LOGE(@"Unable to create EndpointSecurity client, not full-disk access permitted");
LOGE(@"Sleeping for 30s before restarting.");
sleep(30);
exit(ret);
default:
LOGE(@"Unable to create es client: %d. Sleeping for a minute.", ret);
sleep(60);
continue;
}
}
}
- (void)listenES API_AVAILABLE(macos(10.15)) {
while (!self.client)
usleep(100000); // 100ms
es_event_type_t events[] = {
ES_EVENT_TYPE_AUTH_MOUNT,
};
es_return_t sret = es_subscribe(self.client, events, sizeof(events) / sizeof(es_event_type_t));
if (sret != ES_RETURN_SUCCESS)
LOGE(@"SNTDeviceManager: unable to subscribe to auth mount events: %d", sret);
}
- (void)listenDA {
DARegisterDiskAppearedCallback(_diskArbSession, NULL, diskAppearedCallback,
(__bridge void *)self);
DARegisterDiskDescriptionChangedCallback(_diskArbSession, NULL, NULL,
diskDescriptionChangedCallback, (__bridge void *)self);
DARegisterDiskDisappearedCallback(_diskArbSession, NULL, diskDisappearedCallback,
(__bridge void *)self);
}
- (void)listen {
[self listenDA];
if (@available(macos 10.15, *)) [self listenES];
self.subscribed = YES;
}
- (void)handleAuthMount:(const es_message_t *)m
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
if (!self.blockUSBMount) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
long mountMode = m->event.mount.statfs->f_flags;
pid_t pid = audit_token_to_pid(m->process->audit_token);
LOGI(@"SNTDeviceManager: mount syscall arriving from path: %s, pid: %d, fflags: %lu",
m->process->executable->path.data, pid, mountMode);
DADiskRef disk =
DADiskCreateFromBSDName(NULL, self.diskArbSession, m->event.mount.statfs->f_mntfromname);
CFAutorelease(disk);
// TODO(tnek): Log all of the other attributes available in diskInfo into a structured log format.
NSDictionary *diskInfo = CFBridgingRelease(DADiskCopyDescription(disk));
BOOL isRemovable = [diskInfo[(__bridge NSString *)kDADiskDescriptionMediaRemovableKey] boolValue];
BOOL isUSB =
[diskInfo[(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey] isEqualTo:@"USB"];
if (!isRemovable || !isUSB) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
SNTDeviceEvent *event = [[SNTDeviceEvent alloc]
initWithOnName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntonname]
fromName:[NSString stringWithUTF8String:m->event.mount.statfs->f_mntfromname]];
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
if (shouldRemount) {
event.remountArgs = self.remountArgs;
long remountOpts = mountArgsToMask(self.remountArgs);
if (mountMode & remountOpts) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
return;
}
long newMode = mountMode | remountOpts;
LOGI(@"SNTDeviceManager: remounting device '%s'->'%s', flags (%lu) -> (%lu)",
m->event.mount.statfs->f_mntfromname, m->event.mount.statfs->f_mntonname, mountMode,
newMode);
[self remount:disk mountMode:newMode];
}
if (self.deviceBlockCallback) {
self.deviceBlockCallback(event);
}
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
}
- (void)remount:(DADiskRef)disk mountMode:(long)remountMask {
NSArray<NSString *> *args = maskToMountArgs(remountMask);
CFStringRef *argv = (CFStringRef *)calloc(args.count + 1, sizeof(CFStringRef));
CFArrayGetValues((__bridge CFArrayRef)args, CFRangeMake(0, (CFIndex)args.count),
(const void **)argv);
DADiskMountWithArguments(disk, NULL, kDADiskMountOptionDefault, diskMountedCallback,
(__bridge void *)self, (CFStringRef *)argv);
free(argv);
}
// handleESMessage handles an ES message synchronously. This will block all incoming ES events
// until either we serve a response or we hit the auth deadline. Prefer [SNTDeviceManager
// handleESMessageWithTimeout]
// TODO(tnek): generalize this timeout handling logic so that EndpointSecurityManager can use it
// too.
- (void)handleESMessageWithTimeout:(const es_message_t *)m
withClient:(es_client_t *)c
timeout:(dispatch_time_t)timeout API_AVAILABLE(macos(10.15)) {
// ES will kill our whole client if we don't meet the es_message auth deadline, so we try to
// gracefully handle it with a deny-by-default in the worst-case before it can do that.
// This isn't an issue for notify events, so we're in no rush for those.
std::shared_ptr<std::atomic<bool>> responded;
if (m->action_type == ES_ACTION_TYPE_AUTH) {
responded = std::make_shared<std::atomic<bool>>(false);
dispatch_after(timeout, self.esAuthQueue, ^(void) {
if (responded->load()) return;
LOGE(@"SNTDeviceManager: deadline reached: deny pid=%d ret=%d",
audit_token_to_pid(m->process->audit_token),
es_respond_auth_result(c, m, ES_AUTH_RESULT_DENY, false));
});
}
// TODO(tnek): migrate to es_retain_message.
es_message_t *mc = es_copy_message(m);
dispatch_async(self.esAuthQueue, ^{
[self handleESMessage:m withClient:c];
if (m->action_type == ES_ACTION_TYPE_AUTH) {
responded->store(true);
}
es_free_message(mc);
});
}
- (void)handleESMessage:(const es_message_t *)m
withClient:(es_client_t *)c API_AVAILABLE(macos(10.15)) {
switch (m->event_type) {
case ES_EVENT_TYPE_AUTH_MOUNT: {
[self handleAuthMount:m withClient:c];
// Intentional fallthrough
[[fallthrough]];
}
// TODO(tnek): log any extra data here about mounts.
case ES_EVENT_TYPE_NOTIFY_MOUNT: {
break;
}
default: LOGE(@"SNTDeviceManager: unexpected event type: %d", m->event_type);
}
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,193 @@
/// Copyright 2021 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 <DiskArbitration/DiskArbitration.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <bsm/libbsm.h>
#include <sys/mount.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/santad/EventProviders/SNTDeviceManager.h"
#import "Source/santad/EventProviders/DiskArbitrationTestUtil.h"
#import "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
@interface SNTDeviceManagerTest : XCTestCase
@property id mockConfigurator;
@end
@implementation SNTDeviceManagerTest
- (void)setUp {
[super setUp];
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator eventLogType]).andReturn(-1);
fclose(stdout);
}
- (ESResponse *)triggerTestMount:(SNTDeviceManager *)deviceManager
mockES:(MockEndpointSecurity *)mockES
mockDA:(MockDiskArbitration *)mockDA {
if (!deviceManager.subscribed) {
// [deviceManager listen] is synchronous, but we want to asynchronously dispatch it
// with an enforced timeout to ensure that we never run into issues where the client
// never instantiates.
XCTestExpectation *initExpectation =
[self expectationWithDescription:@"Wait for SNTDeviceManager to instantiate"];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
[deviceManager listen];
});
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
while (!deviceManager.subscribed)
;
[initExpectation fulfill];
});
[self waitForExpectations:@[ initExpectation ] timeout:60.0];
}
struct statfs *fs = static_cast<struct statfs *>(calloc(1, sizeof(struct statfs)));
NSString *test_mntfromname = @"/dev/disk2s1";
NSString *test_mntonname = @"/Volumes/KATE'S 4G";
const char *c_mntfromname = [test_mntfromname UTF8String];
const char *c_mntonname = [test_mntonname UTF8String];
strncpy(fs->f_mntfromname, c_mntfromname, MAXPATHLEN);
strncpy(fs->f_mntonname, c_mntonname, MAXPATHLEN);
MockDADisk *disk = [[MockDADisk alloc] init];
disk.diskDescription = @{
(__bridge NSString *)kDADiskDescriptionDeviceProtocolKey : @"USB",
(__bridge NSString *)kDADiskDescriptionMediaRemovableKey : @YES,
@"DAVolumeMountable" : @YES,
@"DAVolumePath" : test_mntonname,
@"DADeviceModel" : @"Some device model",
@"DADevicePath" : test_mntonname,
@"DADeviceVendor" : @"Some vendor",
@"DAAppearanceTime" : @0,
@"DAMediaBSDName" : test_mntfromname,
};
[mockDA insert:disk bsdName:test_mntfromname];
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
m.binaryPath = @"/System/Library/Filesystems/msdos.fs/Contents/Resources/mount_msdos";
m.message->action_type = ES_ACTION_TYPE_AUTH;
m.message->event_type = ES_EVENT_TYPE_AUTH_MOUNT;
m.message->event = (es_events_t){.mount = {.statfs = fs}};
}];
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_MOUNT
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES triggerHandler:m.message];
[self waitForExpectations:@[ expectation ] timeout:60.0];
free(fs);
return got;
}
- (void)testUSBBlockDisabled {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = NO;
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
}
- (void)testRemount {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
deviceManager.remountArgs = @[ @"noexec", @"rdonly" ];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
__block NSString *gotmntonname, *gotmntfromname;
__block NSArray<NSString *> *gotRemountedArgs;
deviceManager.deviceBlockCallbacks = ^(SNTDeviceEvent *event) {
gotRemountedArgs = event.remountArgs;
gotmntonname = event.mntonname;
gotmntfromname = event.mntfromname;
[expectation fulfill];
};
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
XCTAssertEqual(mockDA.wasRemounted, YES);
[self waitForExpectations:@[ expectation ] timeout:60.0];
XCTAssertEqualObjects(gotRemountedArgs, deviceManager.remountArgs);
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
}
- (void)testBlockNoRemount {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
[mockDA reset];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = YES;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for SNTDeviceManager's blockCallback to trigger"];
__block NSString *gotmntonname, *gotmntfromname;
__block NSArray<NSString *> *gotRemountedArgs;
deviceManager.deviceBlockCallbacks = ^(SNTDeviceEvent *event) {
gotRemountedArgs = event.remountArgs;
gotmntonname = event.mntonname;
gotmntfromname = event.mntfromname;
[expectation fulfill];
};
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
[self waitForExpectations:@[ expectation ] timeout:60.0];
XCTAssertNil(gotRemountedArgs);
XCTAssertEqualObjects(gotmntonname, @"/Volumes/KATE'S 4G");
XCTAssertEqualObjects(gotmntfromname, @"/dev/disk2s1");
}
@end

View File

@@ -19,9 +19,6 @@
#include <EndpointSecurity/EndpointSecurity.h>
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
const pid_t PID_MAX = 99999;
@interface SNTEndpointSecurityManager : NSObject <SNTEventProvider>
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file;
@@ -29,6 +26,10 @@ const pid_t PID_MAX = 99999;
- (void)setIsCompilerPID:(pid_t)pid;
- (void)setNotCompilerPID:(pid_t)pid;
// 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;
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
@property(nonatomic, copy) void (^logCallback)(santa_message_t);
@property(readonly, nonatomic) es_client_t *client;

View File

@@ -24,6 +24,9 @@
#include <libproc.h>
#include <atomic>
// Gleaned from https://opensource.apple.com/source/xnu/xnu-4903.241.1/bsd/sys/proc_internal.h
static const pid_t PID_MAX = 99999;
@interface SNTEndpointSecurityManager () {
std::atomic<bool> _compilerPIDs[PID_MAX];
}
@@ -111,9 +114,9 @@
// Create a transitive rule if the file was modified by a running compiler
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [self 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);
@@ -352,6 +355,7 @@
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
return;
}
case ES_EVENT_TYPE_NOTIFY_CLOSE: {
sm.action = ACTION_NOTIFY_WRITE;
targetFile = m->event.close.target;
@@ -372,10 +376,9 @@
targetProcess = m->process;
NSString *p = @(m->event.link.target_dir->path.data);
p = [p stringByAppendingPathComponent:@(m->event.link.target_filename.data)];
[self populateBufferFromString:p.UTF8String
length:p.length
buffer:sm.newpath
size:sizeof(sm.newpath)];
[SNTEndpointSecurityManager populateBufferFromString:p.UTF8String
buffer:sm.newpath
size:sizeof(sm.newpath)];
callback = self.logCallback;
break;
}
@@ -394,7 +397,9 @@
// Deny auth exec events if the path doesn't fit in the santa message.
// TODO(bur/rah): Add support for larger paths.
if ([self populateBufferFromESFile:targetFile buffer:sm.path size:sizeof(sm.path)] &&
if ([SNTEndpointSecurityManager populateBufferFromESFile:targetFile
buffer:sm.path
size:sizeof(sm.path)] &&
m->event_type == ES_EVENT_TYPE_AUTH_EXEC) {
LOGE(@"path is truncated, deny: %s", sm.path);
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
@@ -522,22 +527,17 @@
#pragma mark helpers
// Returns YES if the path was truncated.
// The populated path will be NUL terminated.
- (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
return [self populateBufferFromString:file->path.data
length:file->path.length
buffer:buffer
size:size];
// The populated buffer will be NUL terminated.
+ (BOOL)populateBufferFromESFile:(es_file_t *)file buffer:(char *)buffer size:(size_t)size {
return [SNTEndpointSecurityManager populateBufferFromString:file->path.data
buffer:buffer
size:size];
}
// Returns YES if the path was truncated.
// The populated path will be NUL terminated.
- (BOOL)populateBufferFromString:(const char *)string
length:(size_t)length
buffer:(char *)buffer
size:(size_t)size {
if (length++ > size) length = size;
return strlcpy(buffer, string, length) >= length;
// The populated buffer will be NUL terminated.
+ (BOOL)populateBufferFromString:(const char *)string buffer:(char *)buffer size:(size_t)size {
return strlcpy(buffer, string, size) >= size;
}
- (BOOL)populateRenamedNewPathFromESMessage:(es_event_rename_t)mv
@@ -548,16 +548,15 @@
case ES_DESTINATION_TYPE_NEW_PATH: {
NSString *p = @(mv.destination.new_path.dir->path.data);
p = [p stringByAppendingPathComponent:@(mv.destination.new_path.filename.data)];
truncated = [self populateBufferFromString:p.UTF8String
length:p.length
buffer:buffer
size:size];
truncated = [SNTEndpointSecurityManager populateBufferFromString:p.UTF8String
buffer:buffer
size:size];
break;
}
case ES_DESTINATION_TYPE_EXISTING_FILE: {
truncated = [self populateBufferFromESFile:mv.destination.existing_file
buffer:buffer
size:size];
truncated = [SNTEndpointSecurityManager populateBufferFromESFile:mv.destination.existing_file
buffer:buffer
size:size];
break;
}
}

View File

@@ -49,15 +49,16 @@ const NSString *const kBenignPath = @"/some/other/path";
[self expectationWithDescription:@"Wait for santa's Auth dispatch queue"];
__block NSMutableArray<ESResponse *> *events = [NSMutableArray array];
[mockES registerResponseCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_UNLINK
withCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}
if (events.count >= wantNumResp) {
[expectation fulfill];
}
}];
if (events.count >= wantNumResp) {
[expectation fulfill];
}
}];
__block es_file_t dbFile = {.path = MakeStringToken(kEventsDBPath)};
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
@@ -94,10 +95,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_UNLINK
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t dbFile = {.path = MakeStringToken(testPath)};
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
@@ -125,10 +127,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_UNLINK
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t dbFile = {.path = MakeStringToken(@"/some/other/path")};
ESMessage *m = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
@@ -160,10 +163,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_RENAME
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t otherFile = {.path = MakeStringToken(@"/some/other/path")};
__block es_file_t dbFile = {.path = MakeStringToken(testPath)};
@@ -206,10 +210,11 @@ const NSString *const kBenignPath = @"/some/other/path";
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_RENAME
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
__block es_file_t otherFile = {.path = MakeStringToken(@"/some/other/path")};
__block es_file_t dbFile = {.path = MakeStringToken(testPath)};

View File

@@ -52,6 +52,7 @@
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
_dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
// Grab the system UUID on init

View File

@@ -21,6 +21,7 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
@implementation SNTSyslogEventLog
@@ -62,8 +63,16 @@
if (newpath) {
[outStr appendFormat:@"|newpath=%@", [self sanitizeString:newpath]];
}
char ppath[PATH_MAX] = "(null)";
proc_pidpath(message.pid, ppath, PATH_MAX);
if (message.es_message) {
es_message_t *m = message.es_message;
[SNTEndpointSecurityManager populateBufferFromESFile:m->process->executable
buffer:ppath
size:sizeof(ppath)];
} else {
proc_pidpath(message.pid, ppath, sizeof(ppath));
}
[outStr
appendFormat:

View File

@@ -15,11 +15,11 @@
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import <DiskArbitration/DiskArbitration.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDeviceEvent.h"
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
@@ -30,6 +30,7 @@
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/EventProviders/SNTCachingEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTDeviceManager.h"
#import "Source/santad/EventProviders/SNTDriverManager.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
@@ -42,10 +43,10 @@
#import "Source/santad/SNTSyncdQueue.h"
@interface SNTApplication ()
@property DASessionRef diskArbSession;
@property id<SNTEventProvider> eventProvider;
@property SNTExecutionController *execController;
@property SNTCompilerController *compilerController;
@property SNTDeviceManager *deviceManager;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@@ -92,6 +93,26 @@
return nil;
}
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
deviceManager.blockUSBMount = [configurator blockUSBMount];
if ([configurator remountUSBMode] != nil) {
deviceManager.remountArgs = [configurator remountUSBMode];
}
NSString *deviceBlockMsg = deviceManager.remountArgs != nil
? [configurator remountUSBBlockMessage]
: [configurator bannedUSBBlockMessage];
deviceManager.deviceBlockCallback = ^(SNTDeviceEvent *event) {
[[self.notQueue.notifierConnection remoteObjectProxy]
postUSBBlockNotification:event
withCustomMessage:deviceBlockMsg];
};
_deviceManager = deviceManager;
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
// The filter is reset when santad disconnects from the driver.
// Add the default filters.
@@ -101,7 +122,6 @@
[self.eventProvider fileModificationPrefixFilterAdd:[configurator fileChangesPrefixFilters]];
});
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Restart santactl if it goes down
@@ -135,6 +155,14 @@
forKeyPath:NSStringFromSelector(@selector(metricExportInterval))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(blockUSBMount))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(remountUSBMode))
options:bits
context:NULL];
if (![configurator enableSystemExtension]) {
[configurator addObserver:self
@@ -184,7 +212,7 @@
[self performSelectorInBackground:@selector(beginListeningForDecisionRequests) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForLogRequests) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForDiskMounts) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForMountRequests) withObject:nil];
}
- (void)beginListeningForDecisionRequests {
@@ -239,42 +267,8 @@
}];
}
- (void)beginListeningForDiskMounts {
dispatch_queue_t disk_queue =
dispatch_queue_create("com.google.santad.disk_queue", DISPATCH_QUEUE_SERIAL);
_diskArbSession = DASessionCreate(NULL);
DASessionSetDispatchQueue(_diskArbSession, disk_queue);
DARegisterDiskAppearedCallback(_diskArbSession, NULL, diskAppearedCallback,
(__bridge void *)self);
DARegisterDiskDescriptionChangedCallback(_diskArbSession, NULL, NULL,
diskDescriptionChangedCallback, (__bridge void *)self);
DARegisterDiskDisappearedCallback(_diskArbSession, NULL, diskDisappearedCallback,
(__bridge void *)self);
}
void diskAppearedCallback(DADiskRef disk, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
[[SNTEventLog logger] logDiskAppeared:props];
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
if (props[@"DAVolumePath"]) [[SNTEventLog logger] logDiskAppeared:props];
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
[[SNTEventLog logger] logDiskDisappeared:props];
[app.eventProvider flushCacheNonRootOnly:YES];
- (void)beginListeningForMountRequests {
[self.deviceManager listen];
}
// Taken from Apple's Concurrency Programming Guide.
@@ -415,6 +409,26 @@ dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispat
[self stopMetricsPoll];
[self startMetricsPoll];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(blockUSBMount))]) {
BOOL new = [ change[newKey] boolValue ];
BOOL old = [change[oldKey] boolValue];
if (new != old) {
LOGI(@"BlockUSBMount changed: %d -> %d", old, new);
self.deviceManager.blockUSBMount = new;
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(remountUSBMode))]) {
NSArray<NSString *> *new = [ change[newKey] isKindOfClass : [NSArray class] ]
? (NSArray<NSString *> *)change[newKey]
: nil;
NSArray<NSString *> *old =
[change[oldKey] isKindOfClass:[NSArray class]] ? (NSArray<NSString *> *)change[oldKey] : nil;
if (![old isEqualToArray:new]) {
LOGI(@"RemountArgs changed: %s -> %s", [[old componentsJoinedByString:@","] UTF8String],
[[new componentsJoinedByString:@","] UTF8String]);
self.deviceManager.remountArgs = new;
}
}
}

View File

@@ -107,9 +107,10 @@
}];
__block BOOL complete = NO;
[mockES registerResponseCallback:^(ESResponse *r) {
complete = YES;
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_EXEC
withCallback:^(ESResponse *r) {
complete = YES;
}];
[self startMeasuring];
[mockES triggerHandler:msg.message];

View File

@@ -69,10 +69,11 @@
XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for santa's Auth dispatch queue"];
__block ESResponse *got = nil;
[mockES registerResponseCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
[mockES registerResponseCallback:ES_EVENT_TYPE_AUTH_EXEC
withCallback:^(ESResponse *r) {
got = r;
[expectation fulfill];
}];
NSString *binaryPath = [NSString pathWithComponents:@[ testPath, binaryName ]];
struct stat fileStat;

View File

@@ -225,6 +225,15 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)setBlockUSBMount:(BOOL)enabled reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setBlockUSBMount:enabled];
reply();
}
- (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setRemountUSBMode:remountUSBMode];
reply();
}
- (void)enableBundles:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].enableBundles);
}
@@ -246,11 +255,6 @@ double watchdogRAMPeak = 0;
#pragma mark Metrics Ops
- (void)metrics:(void (^)(NSDictionary *))reply {
// If metrics are not enabled send nil back
if (![[SNTConfigurator configurator] exportMetrics]) {
reply(nil);
}
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
reply([metricSet export]);
}

View File

@@ -18,6 +18,24 @@
#include "Source/common/SNTKernelCommon.h"
#include "Source/santad/EventProviders/SNTEventProvider.h"
const static NSString *kBlockBinary = @"BlockBinary";
const static NSString *kAllowBinary = @"AllowBinary";
const static NSString *kBlockCertificate = @"BlockCertificate";
const static NSString *kAllowCertificate = @"AllowCertificate";
const static NSString *kBlockTeamID = @"BlockTeamID";
const static NSString *kAllowTeamID = @"AllowTeamID";
const static NSString *kBlockScope = @"BlockScope";
const static NSString *kAllowScope = @"AllowScope";
const static NSString *kAllowUnknown = @"AllowUnknown";
const static NSString *kBlockUnknown = @"BlockUnknown";
const static NSString *kAllowCompiler = @"AllowCompiler";
const static NSString *kAllowTransitive = @"AllowTransitive";
const static NSString *kUnknownEventState = @"Unknown";
const static NSString *kBlockPrinterWorkaround = @"BlockPrinterWorkaround";
const static NSString *kAllowNoFileInfo = @"AllowNoFileInfo";
const static NSString *kDenyNoFileInfo = @"DenyNoFileInfo";
const static NSString *kAllowNullVNode = @"AllowNullVNode";
@class MOLCodesignChecker;
@class SNTDriverManager;
@class SNTEventLog;

View File

@@ -19,6 +19,7 @@
#include <utmpx.h>
#include "Source/common/SNTLogging.h"
#include "Source/common/SNTMetricSet.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
@@ -51,12 +52,21 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
@property SNTPolicyProcessor *policyProcessor;
@property SNTRuleTable *ruleTable;
@property SNTSyncdQueue *syncdQueue;
@property SNTMetricCounter *events;
@property dispatch_queue_t eventQueue;
@end
@implementation SNTExecutionController
static NSString *const kPrinterProxyPreMonterey =
(@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
@"Frameworks/Print.framework/Versions/Current/Plugins/PrinterProxy.app/"
@"Contents/MacOS/PrinterProxy");
static NSString *const kPrinterProxyPostMonterey =
(@"/System/Library/PrivateFrameworks/PrintingPrivate.framework/"
@"Versions/Current/Plugins/PrinterProxy.app/Contents/MacOS/PrinterProxy");
#pragma mark Initializers
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider
@@ -78,10 +88,37 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// This establishes the XPC connection between libsecurity and syspolicyd.
// Not doing this causes a deadlock as establishing this link goes through xpcproxy.
(void)[[MOLCodesignChecker alloc] initWithSelf];
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
_events = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"action_response" ]
helpText:@"Events processed by Santa per response"];
}
return self;
}
- (void)incrementEventCounters:(SNTEventState)eventType {
const NSString *eventTypeStr;
switch (eventType) {
case SNTEventStateBlockBinary: eventTypeStr = kBlockBinary; break;
case SNTEventStateAllowBinary: eventTypeStr = kAllowBinary; break;
case SNTEventStateBlockCertificate: eventTypeStr = kBlockCertificate; break;
case SNTEventStateAllowCertificate: eventTypeStr = kAllowCertificate; break;
case SNTEventStateBlockTeamID: eventTypeStr = kBlockTeamID; break;
case SNTEventStateAllowTeamID: eventTypeStr = kAllowTeamID; break;
case SNTEventStateBlockScope: eventTypeStr = kBlockScope; break;
case SNTEventStateAllowScope: eventTypeStr = kAllowScope; break;
case SNTEventStateBlockUnknown: eventTypeStr = kBlockUnknown; break;
case SNTEventStateAllowUnknown: eventTypeStr = kAllowUnknown; break;
case SNTEventStateAllowCompiler: eventTypeStr = kAllowCompiler; break;
case SNTEventStateAllowTransitive: eventTypeStr = kAllowTransitive; break;
default: eventTypeStr = kUnknownEventState; break;
}
[_events incrementForFieldValues:@[ (NSString *)eventTypeStr ]];
}
#pragma mark Binary Validation
- (void)validateBinaryWithMessage:(santa_message_t)message {
@@ -89,20 +126,30 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
if (unlikely(message.path == NULL)) {
LOGE(@"Path for vnode_id is NULL: %llu/%llu", message.vnode_id.fsid, message.vnode_id.fileid);
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kAllowNullVNode ]];
return;
}
SNTConfigurator *config = [SNTConfigurator configurator];
NSError *fileInfoError;
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
if (unlikely(!binInfo)) {
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
if (config.failClosed && config.clientMode == SNTClientModeLockdown) {
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kDenyNoFileInfo ]];
} else {
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kAllowNoFileInfo ]];
}
return;
}
// PrinterProxy workaround, see description above the method for more details.
if ([self printerProxyWorkaround:binInfo]) {
[self.eventProvider postAction:ACTION_RESPOND_DENY forMessage:message];
[self.events incrementForFieldValues:@[ (NSString *)kBlockPrinterWorkaround ]];
return;
}
@@ -111,6 +158,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
LOGD(@"%@ is larger than %zu. Letting santa-driver know we are working on it.", binInfo.path,
kLargeBinarySize);
[self.eventProvider postAction:ACTION_RESPOND_ACK forMessage:message];
// TODO(markowsky): Maybe add a metric here for how many large executables we're seeing.
}
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo];
@@ -139,6 +187,9 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// Send the decision to the kernel.
[self.eventProvider postAction:action forMessage:message];
// Increment counters;
[self incrementEventCounters:cd.decision];
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary && cd.decision != SNTEventStateAllowCompiler &&
cd.decision != SNTEventStateAllowTransitive && cd.decision != SNTEventStateAllowCertificate &&
@@ -179,21 +230,24 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
se.quarantineTimestamp = binInfo.quarantineTimestamp;
se.quarantineAgentBundleID = binInfo.quarantineAgentBundleID;
dispatch_async(_eventQueue, ^{
[self.eventTable addStoredEvent:se];
});
// Only store events if there is a sync server configured.
if (config.syncBaseURL) {
dispatch_async(_eventQueue, ^{
[self.eventTable addStoredEvent:se];
});
}
// If binary was blocked, do the needful
if (action != ACTION_RESPOND_ALLOW && action != ACTION_RESPOND_ALLOW_COMPILER) {
[[SNTEventLog logger] logDeniedExecution:cd withMessage:message];
if ([[SNTConfigurator configurator] enableBundles] && binInfo.bundle) {
if (config.enableBundles && binInfo.bundle) {
// If the binary is part of a bundle, find and hash all the related binaries in the bundle.
// Let the GUI know hashing is needed. Once the hashing is complete the GUI will send a
// message to santad to perform the upload logic for bundles.
// See syncBundleEvent:relatedEvents: for more info.
se.needsBundleHash = YES;
} else if ([[SNTConfigurator configurator] syncBaseURL]) {
} else if (config.syncBaseURL) {
// So the server has something to show the user straight away, initiate an event
// upload for the blocked binary rather than waiting for the next sync.
dispatch_async(_eventQueue, ^{
@@ -243,13 +297,10 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
- (BOOL)printerProxyWorkaround:(SNTFileInfo *)fi {
if ([fi.path hasSuffix:@"/Contents/MacOS/PrinterProxy"] &&
[fi.path containsString:@"Library/Printers"]) {
NSString *proxyPath = (@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
@"Frameworks/Print.framework/Versions/Current/Plugins/PrinterProxy.app/"
@"Contents/MacOS/PrinterProxy");
SNTFileInfo *proxyFi = [[SNTFileInfo alloc] initWithPath:proxyPath];
SNTFileInfo *proxyFi = [self printerProxyFileInfo];
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyPath];
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyFi.path];
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
[outFh writeData:[inFh readDataToEndOfFile]];
[inFh closeFile];
@@ -264,6 +315,15 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
return NO;
}
/**
Returns an SNTFileInfo for the system PrinterProxy path on this system.
*/
- (SNTFileInfo *)printerProxyFileInfo {
SNTFileInfo *proxyInfo = [[SNTFileInfo alloc] initWithPath:kPrinterProxyPostMonterey];
if (!proxyInfo) proxyInfo = [[SNTFileInfo alloc] initWithPath:kPrinterProxyPreMonterey];
return proxyInfo;
}
- (NSString *)ttyPathForPID:(pid_t)pid {
if (pid < 2) return nil; // don't bother even looking for launchd.

View File

@@ -21,6 +21,7 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTRule.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
@@ -52,6 +53,8 @@
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
NSURL *url = [NSURL URLWithString:@"https://localhost/test"];
OCMStub([self.mockConfigurator syncBaseURL]).andReturn(url);
self.mockDriverManager = OCMClassMock([SNTDriverManager class]);
@@ -86,6 +89,24 @@
return (santa_vnode_id_t){.fsid = 1234, .fileid = 5678};
}
- (void)checkMetricCounters:(const NSString *)expectedFieldValueName
expected:(NSNumber *)expectedValue {
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *eventCounter = [metricSet export][@"metrics"][@"/santa/events"];
BOOL foundField;
for (NSDictionary *fieldValue in eventCounter[@"fields"][@"action_response"]) {
if (![expectedFieldValueName isEqualToString:fieldValue[@"value"]]) continue;
XCTAssertEqualObjects(expectedValue, fieldValue[@"data"],
@"%@ counter does not match expected value", expectedFieldValueName);
foundField = YES;
break;
}
if (!foundField) {
XCTFail(@"failed to find %@ field value", expectedFieldValueName);
}
}
- (void)testBinaryAllowRule {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
@@ -99,6 +120,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:@"AllowBinary" expected:@2];
}
- (void)testBinaryBlockRule {
@@ -114,6 +136,9 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
// verify that we're incrementing the binary block
[self checkMetricCounters:@"BlockBinary" expected:@1];
}
- (void)testCertificateAllowRule {
@@ -132,6 +157,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowCertificate expected:@1];
}
- (void)testCertificateBlockRule {
@@ -153,6 +179,7 @@
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:@"BlockCertificate" expected:@1];
}
- (void)testBinaryAllowCompilerRule {
@@ -170,6 +197,7 @@
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW_COMPILER
forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowCompiler expected:@1];
}
- (void)testBinaryAllowCompilerRuleDisabled {
@@ -186,6 +214,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowBinary expected:@1];
}
- (void)testBinaryAllowTransitiveRule {
@@ -202,6 +231,8 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:@"AllowBinary" expected:@2];
}
- (void)testBinaryAllowTransitiveRuleDisabled {
@@ -222,6 +253,8 @@
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:kAllowBinary expected:@2];
[self checkMetricCounters:kAllowTransitive expected:@1];
}
- (void)testDefaultDecision {
@@ -238,6 +271,63 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:kBlockUnknown expected:@2];
[self checkMetricCounters:kAllowUnknown expected:@1];
}
- (void)testUnreadableFailOpenLockdown {
// Undo the default mocks
[self.mockFileInfo stopMocking];
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
OCMStub([self.mockFileInfo alloc]).andReturn(nil);
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY error:[OCMArg setTo:nil]]).andReturn(nil);
// Lockdown mode, no fail-closed
OCMStub([self.mockConfigurator failClosed]).andReturn(NO);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowNoFileInfo expected:@2];
}
- (void)testUnreadableFailClosedLockdown {
// Undo the default mocks
[self.mockFileInfo stopMocking];
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
OCMStub([self.mockFileInfo alloc]).andReturn(nil);
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY error:[OCMArg setTo:nil]]).andReturn(nil);
// Lockdown mode, fail-closed
OCMStub([self.mockConfigurator failClosed]).andReturn(YES);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
[self checkMetricCounters:kDenyNoFileInfo expected:@1];
}
- (void)testUnreadableFailClosedMonitor {
// Undo the default mocks
[self.mockFileInfo stopMocking];
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
OCMStub([self.mockFileInfo alloc]).andReturn(nil);
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY error:[OCMArg setTo:nil]]).andReturn(nil);
// Monitor mode, fail-closed
OCMStub([self.mockConfigurator failClosed]).andReturn(YES);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowNoFileInfo expected:@1];
}
- (void)testMissingShasum {
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowScope expected:@1];
}
- (void)testOutOfScope {
@@ -245,11 +335,7 @@
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
}
- (void)testMissingShasum {
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
[self checkMetricCounters:kAllowScope expected:@2];
}
- (void)testPageZero {
@@ -259,6 +345,7 @@
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
[self checkMetricCounters:kBlockUnknown expected:@3];
}
@end

View File

@@ -122,12 +122,16 @@ int main(int argc, const char *argv[]) {
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
NSProcessInfo *pi = [NSProcessInfo processInfo];
NSString *productVersion = infoDict[@"CFBundleShortVersionString"];
NSString *buildVersion =
[[infoDict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
if ([pi.arguments containsObject:@"-v"]) {
printf("%s\n", [infoDict[@"CFBundleVersion"] UTF8String]);
printf("%s (build %s)\n", [productVersion UTF8String], [buildVersion UTF8String]);
return 0;
}
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
LOGI(@"Started, version %@ (build %@)", productVersion, buildVersion);
// Handle the case of macOS < 10.15 updating to >= 10.15.
if ([[SNTConfigurator configurator] enableSystemExtension]) {

View File

@@ -55,12 +55,12 @@ macos_command_line_application(
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [

View File

@@ -29,8 +29,9 @@
formatter = isoFormatter;
} else {
NSDateFormatter *localFormatter = [[NSDateFormatter alloc] init];
[localFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[localFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
localFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
localFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
localFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
formatter = localFormatter;
}

View File

@@ -43,8 +43,8 @@
SNTMetricMonarchJSONFormat *formatter = [[SNTMetricMonarchJSONFormat alloc] init];
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
NSArray<NSData *> *output = [formatter convert:validMetricsDict error:nil];
output = [formatter convert:validMetricsDict error:NULL];
[formatter convert:validMetricsDict error:nil];
[formatter convert:validMetricsDict error:NULL];
}
@end

View File

@@ -24,7 +24,8 @@
if (self) {
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
_dateFormatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
_dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
}
return self;
}

View File

@@ -43,8 +43,8 @@
SNTMetricRawJSONFormat *formatter = [[SNTMetricRawJSONFormat alloc] init];
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
NSArray<NSData *> *output = [formatter convert:validMetricsDict error:nil];
output = [formatter convert:validMetricsDict error:NULL];
[formatter convert:validMetricsDict error:nil];
[formatter convert:validMetricsDict error:NULL];
}
@end

View File

@@ -50,9 +50,9 @@ NSDictionary *validMetricsDict = nil;
- (NSDate *)createNSDateFromDateString:(NSString *)dateString {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
formatter.calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierISO8601];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
return [formatter dateFromString:dateString];
}

View File

@@ -39,6 +39,7 @@ objc_library(
],
deps = [
":SNTMetricWriter",
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"@MOLAuthenticatingURLSession",
],

View File

@@ -14,6 +14,7 @@
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#include <dispatch/dispatch.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
@@ -49,37 +50,45 @@
dispatch_group_enter(requests);
request.HTTPBody = (NSData *)value;
[[_session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable err) {
if (err != nil) {
_blockError = err;
*stop = YES;
} else if (response == nil) {
*stop = YES;
} else if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSURLSessionDataTask *task = [_session
dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable err) {
if (err != nil) {
_blockError = err;
*stop = YES;
} else if (response == nil) {
*stop = YES;
} else if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// Check HTTP error codes and create errors for any non-200.
if (httpResponse && httpResponse.statusCode != 200) {
_blockError = [[NSError alloc]
initWithDomain:@"com.google.santa.metricservice.writers.http"
code:httpResponse.statusCode
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"received http status code %ld from %@",
httpResponse.statusCode, url]
}];
*stop = YES;
}
}
dispatch_group_leave(requests);
}] resume];
// Check HTTP error codes and create errors for any non-200.
if (httpResponse && httpResponse.statusCode != 200) {
_blockError = [[NSError alloc]
initWithDomain:@"com.google.santa.metricservice.writers.http"
code:httpResponse.statusCode
userInfo:@{
NSLocalizedDescriptionKey :
[NSString stringWithFormat:@"received http status code %ld from %@",
httpResponse.statusCode, url]
}];
*stop = YES;
}
}
dispatch_group_leave(requests);
}];
// Wait up to 30 seconds for the request to complete.
if (dispatch_group_wait(requests, (int64_t)(30.0 * NSEC_PER_SEC)) != 0) {
[task resume];
SNTConfigurator *config = [SNTConfigurator configurator];
int64_t timeout = (int64_t)config.metricExportTimeout;
// Wait up to timeout seconds for the request to complete.
if (dispatch_group_wait(requests, (timeout * NSEC_PER_SEC)) != 0) {
[task cancel];
NSString *errMsg =
[NSString stringWithFormat:@"HTTP request to %@ timed out after 30 seconds", url];
[NSString stringWithFormat:@"HTTP request to %@ timed out after %lu seconds", url,
(unsigned long)timeout];
_blockError = [[NSError alloc] initWithDomain:@"com.google.santa.metricservice.writers.http"
code:ETIMEDOUT

View File

@@ -4,6 +4,7 @@
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <OCMock/OCMock.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
@interface SNTMetricHTTPWriterTest : XCTestCase
@@ -12,6 +13,7 @@
@property id mockMOLAuthenticatingURLSession;
@property NSMutableArray<NSDictionary *> *mockResponses;
@property SNTMetricHTTPWriter *httpWriter;
@property id mockConfigurator;
@end
@implementation SNTMetricHTTPWriterTest
@@ -28,6 +30,9 @@
self.httpWriter = [[SNTMetricHTTPWriter alloc] init];
self.mockResponses = [[NSMutableArray alloc] init];
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
// This must be marked __unsafe_unretained because we're going to store into
// it using NSInvocation's getArgument:atIndex: method which takes a void*
// to populate. If we don't mark the variable __unsafe_unretained it will
@@ -181,4 +186,18 @@
XCTAssertFalse(result);
}
@end
- (void)testEnsureTimeoutsDoNotCrashWriter {
NSURL *url = [NSURL URLWithString:@"http://localhost:11444"];
// Queue up two responses for nil and NULL.
[self createMockResponseWithURL:url withCode:400 withData:nil withError:nil];
// Set the timeout to 0 second
OCMStub([self.mockConfigurator metricExportTimeout]).andReturn(0);
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [self.httpWriter write:@[ JSONdata ] toURL:url error:nil];
XCTAssertEqual(NO, result);
}
@end

View File

@@ -1,7 +1,100 @@
load("@build_bazel_rules_apple//apple:macos.bzl", "macos_command_line_application")
load("//:helper.bzl", "santa_unit_test")
licenses(["notice"])
package(default_visibility = ["//:santa_package_group"])
objc_library(
name = "FCM_lib",
srcs = ["SNTSyncFCM.m"],
hdrs = ["SNTSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
deps = [
"@MOLAuthenticatingURLSession",
],
)
objc_library(
name = "sync_lib",
srcs = [
"NSData+Zlib.h",
"NSData+Zlib.m",
"SNTSyncConstants.h",
"SNTSyncConstants.m",
"SNTSyncEventUpload.h",
"SNTSyncEventUpload.m",
"SNTSyncManager.m",
"SNTSyncPostflight.h",
"SNTSyncPostflight.m",
"SNTSyncPreflight.h",
"SNTSyncPreflight.m",
"SNTSyncRuleDownload.h",
"SNTSyncRuleDownload.m",
"SNTSyncStage.h",
"SNTSyncStage.m",
"SNTSyncState.h",
"SNTSyncState.m",
],
hdrs = ["SNTSyncManager.h"],
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLXPCConnection",
],
)
# Using :sync_lib breaks the Zlib category hack used in the tests to
# disable compression, in turn failing the tests. Re-compile here to keep
# the "override" category behavior.
santa_unit_test(
name = "SNTSyncTest",
srcs = [
"NSData+Zlib.h",
"NSData+Zlib.m",
"SNTSyncConstants.h",
"SNTSyncConstants.m",
"SNTSyncEventUpload.h",
"SNTSyncEventUpload.m",
"SNTSyncPostflight.h",
"SNTSyncPostflight.m",
"SNTSyncPreflight.h",
"SNTSyncPreflight.m",
"SNTSyncRuleDownload.h",
"SNTSyncRuleDownload.m",
"SNTSyncStage.h",
"SNTSyncStage.m",
"SNTSyncState.h",
"SNTSyncState.m",
"SNTSyncTest.m",
],
resources = glob([
"testdata/*.json",
"testdata/*.plist",
]),
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTFileInfo",
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLXPCConnection",
"@OCMock",
],
)
objc_library(
name = "santass_lib",
srcs = [
@@ -26,3 +119,11 @@ macos_command_line_application(
visibility = ["//:santa_package_group"],
deps = [":santass_lib"],
)
test_suite(
name = "unit_tests",
tests = [
":SNTSyncTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -28,6 +28,8 @@ extern NSString *const kUploadLogsURL;
extern NSString *const kClientMode;
extern NSString *const kClientModeMonitor;
extern NSString *const kClientModeLockdown;
extern NSString *const kBlockUSBMount;
extern NSString *const kRemountUSBMode;
extern NSString *const kCleanSync;
extern NSString *const kAllowedPathRegex;
extern NSString *const kAllowedPathRegexDeprecated;
@@ -94,6 +96,7 @@ extern NSString *const kEventUploadBundleBinaries;
extern NSString *const kRules;
extern NSString *const kRuleSHA256;
extern NSString *const kRuleIdentifier;
extern NSString *const kRulePolicy;
extern NSString *const kRulePolicyAllowlist;
extern NSString *const kRulePolicyAllowlistDeprecated;
@@ -107,6 +110,7 @@ extern NSString *const kRulePolicyRemove;
extern NSString *const kRuleType;
extern NSString *const kRuleTypeBinary;
extern NSString *const kRuleTypeCertificate;
extern NSString *const kRuleTypeTeamID;
extern NSString *const kRuleCustomMsg;
extern NSString *const kCursor;

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCommandSyncConstants.h"
#import "SNTSyncConstants.h"
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
@@ -26,6 +26,8 @@ NSString *const kRequestCleanSync = @"request_clean_sync";
NSString *const kBatchSize = @"batch_size";
NSString *const kUploadLogsURL = @"upload_logs_url";
NSString *const kClientMode = @"client_mode";
NSString *const kBlockUSBMount = @"block_usb_mount";
NSString *const kRemountUSBMode = @"remount_usb_mode";
NSString *const kClientModeMonitor = @"MONITOR";
NSString *const kClientModeLockdown = @"LOCKDOWN";
NSString *const kCleanSync = @"clean_sync";
@@ -95,6 +97,7 @@ NSString *const kEventUploadBundleBinaries = @"event_upload_bundle_binaries";
NSString *const kRules = @"rules";
NSString *const kRuleSHA256 = @"sha256";
NSString *const kRuleIdentifier = @"identifier";
NSString *const kRulePolicy = @"policy";
NSString *const kRulePolicyAllowlist = @"ALLOWLIST";
NSString *const kRulePolicyAllowlistDeprecated = @"WHITELIST";
@@ -108,6 +111,7 @@ NSString *const kRulePolicyRemove = @"REMOVE";
NSString *const kRuleType = @"rule_type";
NSString *const kRuleTypeBinary = @"BINARY";
NSString *const kRuleTypeCertificate = @"CERTIFICATE";
NSString *const kRuleTypeTeamID = @"TEAMID";
NSString *const kRuleCustomMsg = @"custom_msg";
NSString *const kCursor = @"cursor";

View File

@@ -14,9 +14,9 @@
#import <Foundation/Foundation.h>
#import "SNTCommandSyncStage.h"
#import "SNTSyncStage.h"
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
@interface SNTSyncEventUpload : SNTSyncStage
- (BOOL)uploadEvents:(NSArray *)events;

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santasyncservice/SNTSyncEventUpload.h"
#import <MOLCertificate/MOLCertificate.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -21,11 +21,11 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/NSData+Zlib.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/NSData+Zlib.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTCommandSyncEventUpload
@implementation SNTSyncEventUpload
- (NSURL *)stageURL {
NSString *stageName = [@"eventupload" stringByAppendingFormat:@"/%@", self.syncState.machineID];

View File

@@ -15,30 +15,30 @@
#import <Foundation/Foundation.h>
/** A block that takes a NSString object as an argument. */
typedef void (^SNTCommandSyncFCMTokenHandler)(NSString *);
typedef void (^SNTSyncFCMTokenHandler)(NSString *);
/** A block that takes a NSDictionary object as an argument. */
typedef void (^SNTCommandSyncFCMMessageHandler)(NSDictionary *);
typedef void (^SNTSyncFCMMessageHandler)(NSDictionary *);
/** A block that takes a NSHTTPURLResponse and NSError object as an argument. */
typedef void (^SNTCommandSyncFCMConnectionErrorHandler)(NSHTTPURLResponse *, NSError *);
typedef void (^SNTSyncFCMConnectionErrorHandler)(NSHTTPURLResponse *, NSError *);
/** A block that takes a NSDictionary and NSError object as arguments. */
typedef void (^SNTCommandSyncFCMAcknowledgeErrorHandler)(NSDictionary *, NSError *);
typedef void (^SNTSyncFCMAcknowledgeErrorHandler)(NSDictionary *, NSError *);
@interface SNTCommandSyncFCM : NSObject
@interface SNTSyncFCM : NSObject
/** Returns YES if connected to FCM. */
@property(readonly, nonatomic) BOOL isConnected;
/** A block to be executed when the FCM token changes */
@property(copy) SNTCommandSyncFCMTokenHandler tokenHandler;
@property(copy) SNTSyncFCMTokenHandler tokenHandler;
/** A block to be executed when there is an issue with acknowledging a message. */
@property(copy) SNTCommandSyncFCMAcknowledgeErrorHandler acknowledgeErrorHandler;
@property(copy) SNTSyncFCMAcknowledgeErrorHandler acknowledgeErrorHandler;
/** A block to be executed when there is a non-recoverable issue with the FCM Connection. */
@property(copy) SNTCommandSyncFCMConnectionErrorHandler connectionErrorHandler;
@property(copy) SNTSyncFCMConnectionErrorHandler connectionErrorHandler;
- (instancetype)init NS_UNAVAILABLE;
@@ -60,7 +60,7 @@ typedef void (^SNTCommandSyncFCMAcknowledgeErrorHandler)(NSDictionary *, NSError
* @note If the fatalCodes argument is nil, @[@302, @400, @403] will be used.
* @note If the sessionConfiguration argument is nil, defaultSessionConfiguration will be used.
*
* @return An initialized SNTCommandSyncFCM object
* @return An initialized SNTSyncFCM object
*/
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
@@ -69,21 +69,20 @@ typedef void (^SNTCommandSyncFCMAcknowledgeErrorHandler)(NSDictionary *, NSError
backoffMax:(uint32_t)backoffMax
fatalCodes:(NSArray<NSNumber *> *)fatalCodes
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler
NS_DESIGNATED_INITIALIZER;
messageHandler:(SNTSyncFCMMessageHandler)messageHandler NS_DESIGNATED_INITIALIZER;
/** A convenience initializer. Optional args will use their zero values. */
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler;
messageHandler:(SNTSyncFCMMessageHandler)messageHandler;
/** A convenience initializer. Optional args will use their zero values. */
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler;
messageHandler:(SNTSyncFCMMessageHandler)messageHandler;
/**
* Opens a connection to FCM and starts listening for messages.

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import "Source/santasyncservice/SNTSyncFCM.h"
#import <SystemConfiguration/SystemConfiguration.h>
@@ -53,7 +53,7 @@ static const uint32_t kDefaultConnectDelayMaxSeconds = 10;
#pragma mark MOLFCMClient Extension
@interface SNTCommandSyncFCM () {
@interface SNTSyncFCM () {
/** URL components for client registration, receiving and acknowledging messages. */
NSURLComponents *_checkinComponents;
NSURLComponents *_registerComponents;
@@ -77,7 +77,7 @@ static const uint32_t kDefaultConnectDelayMaxSeconds = 10;
@property(nonatomic) MOLAuthenticatingURLSession *authSession;
/** The block to be called for every message. */
@property(copy, nonatomic) SNTCommandSyncFCMMessageHandler messageHandler;
@property(copy, nonatomic) SNTSyncFCMMessageHandler messageHandler;
/** Is used throughout the class to reconnect to FCM after a connection loss. */
@property SCNetworkReachabilityRef reachability;
@@ -104,7 +104,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
void *info) {
dispatch_async(dispatch_get_main_queue(), ^{
if (flags & kSCNetworkReachabilityFlagsReachable) {
SNTCommandSyncFCM *FCMClient = (__bridge SNTCommandSyncFCM *)info;
SNTSyncFCM *FCMClient = (__bridge SNTSyncFCM *)info;
SEL s = @selector(reachabilityRestored);
[NSObject cancelPreviousPerformRequestsWithTarget:FCMClient selector:s object:nil];
[FCMClient performSelector:s withObject:nil afterDelay:1];
@@ -112,7 +112,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
});
}
@implementation SNTCommandSyncFCM
@implementation SNTSyncFCM
#pragma mark init/dealloc methods
@@ -123,7 +123,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
backoffMax:(uint32_t)backoffMax
fatalCodes:(NSArray<NSNumber *> *)fatalCodes
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
messageHandler:(SNTSyncFCMMessageHandler)messageHandler {
self = [super init];
if (self) {
_project = project;
@@ -159,7 +159,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
entity:(NSString *)entity
apiKey:(NSString *)apiKey
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
messageHandler:(SNTSyncFCMMessageHandler)messageHandler {
return [self initWithProject:project
entity:entity
apiKey:apiKey
@@ -173,7 +173,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
messageHandler:(SNTSyncFCMMessageHandler)messageHandler {
return [self initWithProject:project
entity:entity
apiKey:apiKey

View File

@@ -21,7 +21,7 @@
///
/// Handles push notifications and periodic syncing with a sync server.
///
@interface SNTCommandSyncManager : NSObject <SNTSyncdXPC>
@interface SNTSyncManager : NSObject <SNTSyncdXPC>
@property(readonly, nonatomic) BOOL daemon;

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
#import "Source/santasyncservice/SNTSyncManager.h"
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -25,20 +25,20 @@
#import "Source/common/SNTStrengthify.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncEventUpload.h"
#import "Source/santasyncservice/SNTSyncFCM.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";
@interface SNTCommandSyncManager () {
@interface SNTSyncManager () {
SCNetworkReachabilityRef _reachability;
}
@@ -61,7 +61,7 @@ static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property SNTCommandSyncFCM *FCMClient;
@property SNTSyncFCM *FCMClient;
@property NSString *FCMToken;
@property(nonatomic) MOLXPCConnection *daemonConn;
@@ -77,7 +77,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
void *info) {
// Put this check and set on the main thread to ensure serial access.
dispatch_async(dispatch_get_main_queue(), ^{
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
SNTSyncManager *commandSyncManager = (__bridge SNTSyncManager *)info;
// Only call the setter when there is a change. This will filter out the redundant calls to this
// callback whenever the network interface states change.
if (commandSyncManager.reachable != (flags & kSCNetworkReachabilityFlagsReachable)) {
@@ -86,7 +86,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
});
}
@implementation SNTCommandSyncManager
@implementation SNTSyncManager
#pragma mark init
@@ -107,11 +107,11 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTCommandSyncState *syncState = [self createSyncState];
SNTSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.allowlistNotifications = self.allowlistNotifications;
syncState.allowlistNotificationQueue = self.allowlistNotificationQueue;
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
@@ -141,9 +141,9 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
#pragma mark SNTSyncdXPC protocol methods
- (void)postEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events isFromBundle:(BOOL)isFromBundle {
SNTCommandSyncState *syncState = [self createSyncState];
SNTSyncState *syncState = [self createSyncState];
if (isFromBundle) syncState.eventBatchSize = self.eventBatchSize;
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if (events && [p uploadEvents:events]) {
LOGD(@"Events upload complete");
} else {
@@ -159,8 +159,8 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
reply(SNTBundleEventActionDropEvents);
return;
}
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
SNTSyncState *syncState = [self createSyncState];
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if ([p uploadEvents:@[ event ]]) {
if ([syncState.bundleBinaryRequests containsObject:event.fileBundleHash]) {
reply(SNTBundleEventActionSendEvents);
@@ -185,7 +185,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
- (void)listenForPushNotificationsWithSyncState:(SNTSyncState *)syncState {
if ([self.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Already listening for push notifications");
return;
@@ -197,18 +197,17 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTCommandSyncFCM 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 = [[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);
@@ -354,8 +353,8 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
- (void)preflightOnly:(BOOL)preflightOnly {
LOGD(@"Preflight starting");
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
SNTSyncState *syncState = [self createSyncState];
SNTSyncPreflight *p = [[SNTSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
@@ -390,9 +389,9 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
}
}
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
- (void)eventUploadWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Event upload starting");
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
SNTSyncEventUpload *p = [[SNTSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
@@ -402,9 +401,9 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
}
}
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
- (void)ruleDownloadWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Rule download starting");
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
SNTSyncRuleDownload *p = [[SNTSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
return [self postflightWithSyncState:syncState];
@@ -414,9 +413,9 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
}
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
- (void)postflightWithSyncState:(SNTSyncState *)syncState {
LOGD(@"Postflight starting");
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
SNTSyncPostflight *p = [[SNTSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
@@ -438,9 +437,9 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
return timerQueue;
}
- (SNTCommandSyncState *)createSyncState {
- (SNTSyncState *)createSyncState {
// Gather some data needed during some sync stages
SNTCommandSyncState *syncState = [[SNTCommandSyncState alloc] init];
SNTSyncState *syncState = [[SNTSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
@@ -470,7 +469,11 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
dispatch_group_leave(group);
}];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
NSURLSessionConfiguration *sessConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessConfig.connectionProxyDictionary = [[SNTConfigurator configurator] syncProxyConfig];
MOLAuthenticatingURLSession *authURLSession =
[[MOLAuthenticatingURLSession alloc] initWithSessionConfiguration:sessConfig];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {

View File

@@ -14,7 +14,7 @@
#import <Foundation/Foundation.h>
#import "SNTCommandSyncStage.h"
#import "SNTSyncStage.h"
@interface SNTCommandSyncPreflight : SNTCommandSyncStage
@interface SNTSyncPostflight : SNTSyncStage
@end

View File

@@ -12,16 +12,16 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santasyncservice/SNTSyncPostflight.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTCommandSyncPostflight
@implementation SNTSyncPostflight
- (NSURL *)stageURL {
NSString *stageName = [@"postflight" stringByAppendingFormat:@"/%@", self.syncState.machineID];
@@ -60,6 +60,17 @@
reply:replyBlock];
}
if (self.syncState.blockUSBMount) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setBlockUSBMount:self.syncState.blockUSBMount
reply:replyBlock];
}
if (self.syncState.remountUSBMode) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setRemountUSBMode:self.syncState.remountUSBMode
reply:replyBlock];
}
// Update last sync success
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setFullSyncLastSuccess:[NSDate date] reply:replyBlock];

View File

@@ -14,7 +14,7 @@
#import <Foundation/Foundation.h>
#import "SNTCommandSyncStage.h"
#import "SNTSyncStage.h"
@interface SNTCommandSyncPostflight : SNTCommandSyncStage
@interface SNTSyncPreflight : SNTSyncStage
@end

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santasyncservice/SNTSyncPreflight.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -21,10 +21,10 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTSystemInfo.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTCommandSyncPreflight
@implementation SNTSyncPreflight
- (NSURL *)stageURL {
NSString *stageName = [@"preflight" stringByAppendingFormat:@"/%@", self.syncState.machineID];
@@ -134,6 +134,14 @@
self.syncState.blocklistRegex = resp[kBlockedPathRegexDeprecated];
}
if ([resp[kBlockUSBMount] boolValue]) {
self.syncState.blockUSBMount = YES;
}
if ([resp[kRemountUSBMode] isKindOfClass:[NSArray class]]) {
self.syncState.remountUSBMode = resp[kRemountUSBMode];
}
if ([resp[kCleanSync] boolValue]) {
LOGD(@"Clean sync requested by server");
self.syncState.cleanSync = YES;

View File

@@ -14,10 +14,10 @@
#import <Foundation/Foundation.h>
#import "Source/santactl/Commands/sync/SNTCommandSyncStage.h"
#import "Source/santasyncservice/SNTSyncStage.h"
@class SNTRule;
@interface SNTCommandSyncRuleDownload : SNTCommandSyncStage
@interface SNTSyncRuleDownload : SNTSyncStage
- (SNTRule *)ruleFromDictionary:(NSDictionary *)dict;
@end

View File

@@ -12,17 +12,17 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
#import "Source/santasyncservice/SNTSyncRuleDownload.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTCommandSyncRuleDownload
@implementation SNTSyncRuleDownload
- (NSURL *)stageURL {
NSString *stageName = [@"ruledownload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
@@ -45,7 +45,10 @@
error = e;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
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");
return NO;
}
if (error) {
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
@@ -136,7 +139,10 @@
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
SNTRule *newRule = [[SNTRule alloc] init];
newRule.identifier = dict[kRuleSHA256];
newRule.identifier = dict[kRuleIdentifier];
if (newRule.identifier == nil) {
newRule.identifier = dict[kRuleSHA256];
}
NSString *policyString = dict[kRulePolicy];
if ([policyString isEqual:kRulePolicyAllowlist] ||
@@ -162,6 +168,8 @@
newRule.type = SNTRuleTypeBinary;
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
newRule.type = SNTRuleTypeCertificate;
} else if ([ruleTypeString isEqual:kRuleTypeTeamID]) {
newRule.type = SNTRuleTypeTeamID;
} else {
return nil;
}

View File

@@ -14,13 +14,13 @@
#import <Foundation/Foundation.h>
@class SNTCommandSyncState;
@class SNTSyncState;
@class MOLXPCConnection;
@interface SNTCommandSyncStage : NSObject
@interface SNTSyncStage : NSObject
@property(readonly, nonnull) NSURLSession *urlSession;
@property(readonly, nonnull) SNTCommandSyncState *syncState;
@property(readonly, nonnull) SNTSyncState *syncState;
@property(readonly, nonnull) MOLXPCConnection *daemonConn;
/**
@@ -28,8 +28,7 @@
@param state A holder for state used across requests
*/
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)state
NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithState:(nonnull SNTSyncState *)state NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)init NS_UNAVAILABLE;

View File

@@ -12,28 +12,28 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncStage.h"
#import "Source/santasyncservice/SNTSyncStage.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/NSData+Zlib.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/NSData+Zlib.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncState.h"
@interface SNTCommandSyncStage ()
@interface SNTSyncStage ()
@property(readwrite) NSURLSession *urlSession;
@property(readwrite) SNTCommandSyncState *syncState;
@property(readwrite) SNTSyncState *syncState;
@property(readwrite) MOLXPCConnection *daemonConn;
@property BOOL xsrfFetched;
@end
@implementation SNTCommandSyncStage
@implementation SNTSyncStage
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)syncState {
- (nullable instancetype)initWithState:(nonnull SNTSyncState *)syncState {
self = [super init];
if (self) {
_syncState = syncState;

View File

@@ -16,12 +16,12 @@
#import "Source/common/SNTCommonEnums.h"
@class SNTCommandSyncManager;
@class SNTSyncManager;
@class MOLXPCConnection;
/// An instance of this class is passed to each stage of the sync process for storing data
/// that might be needed in later stages.
@interface SNTCommandSyncState : NSObject
@interface SNTSyncState : NSObject
/// Configured session to use for requests.
@property NSURLSession *session;
@@ -56,6 +56,9 @@
@property SNTClientMode clientMode;
@property NSString *allowlistRegex;
@property NSString *blocklistRegex;
@property BOOL blockUSBMount;
// Array of mount args for the forced remounting feature.
@property NSArray *remountUSBMode;
/// Clean sync flag, if True, all existing rules should be deleted before inserting any new rules.
@property BOOL cleanSync;

View File

@@ -12,7 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCommandSyncState.h"
#import "Source/santasyncservice/SNTSyncState.h"
@implementation SNTCommandSyncState
@implementation SNTSyncState
@end

View File

@@ -21,13 +21,13 @@
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncStage.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
#import "Source/santasyncservice/SNTSyncConstants.h"
#import "Source/santasyncservice/SNTSyncEventUpload.h"
#import "Source/santasyncservice/SNTSyncPostflight.h"
#import "Source/santasyncservice/SNTSyncPreflight.h"
#import "Source/santasyncservice/SNTSyncRuleDownload.h"
#import "Source/santasyncservice/SNTSyncStage.h"
#import "Source/santasyncservice/SNTSyncState.h"
// Prevent Zlib compression during testing
@implementation NSData (Zlib)
@@ -39,17 +39,17 @@
}
@end
@interface SNTCommandSyncTest : XCTestCase
@property SNTCommandSyncState *syncState;
@interface SNTSyncTest : XCTestCase
@property SNTSyncState *syncState;
@property id<SNTDaemonControlXPC> daemonConnRop;
@end
@implementation SNTCommandSyncTest
@implementation SNTSyncTest
- (void)setUp {
[super setUp];
self.syncState = [[SNTCommandSyncState alloc] init];
self.syncState = [[SNTSyncState alloc] init];
self.syncState.daemonConn = OCMClassMock([MOLXPCConnection class]);
self.daemonConnRop = OCMProtocolMock(@protocol(SNTDaemonControlXPC));
OCMStub([self.syncState.daemonConn remoteObjectProxy]).andReturn(self.daemonConnRop);
@@ -140,7 +140,7 @@
return [NSData dataWithContentsOfFile:path];
}
#pragma mark - SNTCommandSyncStage Tests
#pragma mark - SNTSyncStage Tests
- (void)testBaseFetchXSRFTokenSuccess {
// NOTE: This test only works if the other tests don't return a 403 and run before this test.
@@ -177,16 +177,16 @@
NSString *stageName = [@"a" stringByAppendingFormat:@"/%@", self.syncState.machineID];
NSURL *u1 = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
SNTCommandSyncStage *sut = [[SNTCommandSyncStage alloc] initWithState:self.syncState];
SNTSyncStage *sut = [[SNTSyncStage alloc] initWithState:self.syncState];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:u1];
XCTAssertTrue([sut performRequest:req]);
XCTAssertEqualObjects(self.syncState.xsrfToken, @"my-xsrf-token");
}
#pragma mark - SNTCommandSyncPreflight Tests
#pragma mark - SNTSyncPreflight Tests
- (void)testPreflightBasicResponse {
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_basic.json"];
[self stubRequestBody:respData response:nil error:nil validateBlock:nil];
@@ -198,8 +198,20 @@
XCTAssertNil(self.syncState.blocklistRegex);
}
- (void)testPreflightBlockUSBMount {
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_toggle_blockusb.json"];
[self stubRequestBody:respData response:nil error:nil validateBlock:nil];
XCTAssertTrue([sut sync]);
XCTAssertEqual(self.syncState.blockUSBMount, true);
NSArray<NSString *> *wantRemountUSBMode = @[ @"rdonly", @"noexec" ];
XCTAssertEqualObjects(self.syncState.remountUSBMode, wantRemountUSBMode);
}
- (void)testPreflightDatabaseCounts {
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
int64_t bin = 5, cert = 8, compiler = 2, transitive = 19, teamID = 3;
OCMStub([self.daemonConnRop
@@ -225,7 +237,7 @@
}
- (void)testPreflightCleanSync {
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
id processInfoMock = OCMClassMock([NSProcessInfo class]);
OCMStub([processInfoMock processInfo]).andReturn(processInfoMock);
@@ -247,7 +259,7 @@
}
- (void)testPreflightLockdown {
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
SNTSyncPreflight *sut = [[SNTSyncPreflight alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_preflight_lockdown.json"];
[self stubRequestBody:respData response:nil error:nil validateBlock:nil];
@@ -257,10 +269,10 @@
XCTAssertEqual(self.syncState.clientMode, SNTClientModeLockdown);
}
#pragma mark - SNTCommandSyncEventUpload Tests
#pragma mark - SNTSyncEventUpload Tests
- (void)testEventUploadBasic {
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
SNTSyncEventUpload *sut = [[SNTSyncEventUpload alloc] initWithState:self.syncState];
self.syncState.eventBatchSize = 50;
NSData *eventData = [self dataFromFixture:@"sync_eventupload_input_basic.plist"];
@@ -318,7 +330,7 @@
}
- (void)testEventUploadBundleAndQuarantineData {
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
SNTSyncEventUpload *sut = [[SNTSyncEventUpload alloc] initWithState:self.syncState];
sut = OCMPartialMock(sut);
NSData *eventData = [self dataFromFixture:@"sync_eventupload_input_quarantine.plist"];
@@ -353,7 +365,7 @@
}
- (void)testEventUploadBatching {
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
SNTSyncEventUpload *sut = [[SNTSyncEventUpload alloc] initWithState:self.syncState];
self.syncState.eventBatchSize = 1;
sut = OCMPartialMock(sut);
@@ -376,11 +388,10 @@
XCTAssertEqual(requestCount, 2);
}
#pragma mark - SNTCommandSyncRuleDownload Tests
#pragma mark - SNTSyncRuleDownload Tests
- (void)testRuleDownload {
SNTCommandSyncRuleDownload *sut =
[[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
SNTSyncRuleDownload *sut = [[SNTSyncRuleDownload alloc] initWithState:self.syncState];
NSData *respData = [self dataFromFixture:@"sync_ruledownload_batch1.json"];
[self stubRequestBody:respData
@@ -423,6 +434,10 @@
state:SNTRuleStateBlock
type:SNTRuleTypeCertificate
customMsg:@"Hi There"],
[[SNTRule alloc] initWithIdentifier:@"AAAAAAAAAA"
state:SNTRuleStateBlock
type:SNTRuleTypeTeamID
customMsg:@"Banned team ID"],
];
OCMVerify([self.daemonConnRop databaseRuleAddRules:rules cleanSlate:NO reply:OCMOCK_ANY]);

View File

@@ -0,0 +1 @@
{"allowed_path_regex": null, "client_mode": "LOCKDOWN", "blocked_path_regex": null, "batch_size": 100, "block_usb_mount":true, "remount_usb_mode": ["rdonly", "noexec"]}

View File

@@ -0,0 +1 @@
{"rules": [{"rule_type": "CERTIFICATE", "policy": "BLACKLIST", "sha256": "7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142", "custom_msg": "Hi There"},{"rule_type":"TEAMID", "policy":"BLOCKLIST", "identifier": "AAAAAAAAAA", "custom_msg": "Banned team ID"}]}

View File

@@ -0,0 +1,15 @@
GIT_ROOT=$(git rev-parse --show-toplevel)
ANALYZE_PATH="$GIT_ROOT/Testing/clang_analyzer"
TITLE="Santa Clang Analysis"
EXECUTION_ROOT=`bazel info execution_root`
function main() {
bazel clean
bazel run @hedron_compile_commands//:refresh_all
analyze-build --cdb $GIT_ROOT/compile_commands.json -o $ANALYZE_PATH/analysis --html-title "$TITLE" --use-analyzer=$(which clang)
}
main $@
exit $?

View File

@@ -16,6 +16,13 @@
<dict>
<key>BannedBlockMessage</key>
<string>This application has been blocked from executing because it has been banned.</string>
<key>BlockUSBMount</key>
<true/>
<key>RemountUSBMode</key>
<array>
<string>noexec</string>
<string>rdonly</string>
</array>
<key>EnableForkAndExitLogging</key>
<true/>
<key>EnablePageZeroProtection</key>

Some files were not shown because too many files have changed in this diff Show More