mirror of
https://github.com/google/santa.git
synced 2026-01-17 02:07:58 -05:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3e48aed1b | ||
|
|
e60f9cf6c5 | ||
|
|
c7e309ccb1 | ||
|
|
ad8aafbd07 | ||
|
|
9e671c3dee | ||
|
|
d97abe36f2 | ||
|
|
faa8946056 | ||
|
|
8b2b1f0bfc | ||
|
|
16678cd5a0 | ||
|
|
0bd6a199a3 | ||
|
|
58e2b7e1b8 | ||
|
|
b824a8e3e0 | ||
|
|
25bf2a93e4 | ||
|
|
f1ea1b369f | ||
|
|
5503a88308 | ||
|
|
8cf0f8217d | ||
|
|
22799ffc2a | ||
|
|
cb61d0cc99 | ||
|
|
fb7447ceba | ||
|
|
45e51e9c09 | ||
|
|
b0f0cdd4e6 | ||
|
|
65090d3ef2 | ||
|
|
9c80f79d82 | ||
|
|
93adaea81e | ||
|
|
a125b340a5 | ||
|
|
fbd0de3d48 | ||
|
|
6f2ae62bce | ||
|
|
da29b20473 | ||
|
|
197109a8ee | ||
|
|
91f3168c7a | ||
|
|
a00ec41518 | ||
|
|
c32248aaf7 | ||
|
|
afd97bdf3e | ||
|
|
73c4875b1f | ||
|
|
916fc8c0e6 | ||
|
|
e59e6105f3 | ||
|
|
216ac811eb | ||
|
|
48f92f5913 | ||
|
|
6bb08d0490 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -15,3 +15,6 @@ tulsigen-*
|
||||
*.p12
|
||||
*.keychain
|
||||
*.swp
|
||||
compile_commands.json
|
||||
.cache/
|
||||
.vscode/*
|
||||
|
||||
21
BUILD
21
BUILD
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
27
Source/common/SNTDeviceEvent.h
Normal file
27
Source/common/SNTDeviceEvent.h
Normal 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
|
||||
63
Source/common/SNTDeviceEvent.m
Normal file
63
Source/common/SNTDeviceEvent.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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>
|
||||
|
||||
193
Source/santa/Resources/DeviceMessageWindow.xib
Normal file
193
Source/santa/Resources/DeviceMessageWindow.xib
Normal 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>
|
||||
@@ -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"/>
|
||||
|
||||
65
Source/santa/SNTBinaryMessageWindowController.h
Normal file
65
Source/santa/SNTBinaryMessageWindowController.h
Normal 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
|
||||
176
Source/santa/SNTBinaryMessageWindowController.m
Normal file
176
Source/santa/SNTBinaryMessageWindowController.m
Normal 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
|
||||
38
Source/santa/SNTDeviceMessageWindowController.h
Normal file
38
Source/santa/SNTDeviceMessageWindowController.h
Normal 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
|
||||
57
Source/santa/SNTDeviceMessageWindowController.m
Normal file
57
Source/santa/SNTDeviceMessageWindowController.m
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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:@"--"]) {
|
||||
|
||||
@@ -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"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"rules": [{"rule_type": "CERTIFICATE", "policy": "BLACKLIST", "sha256": "7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142", "custom_msg": "Hi There"}]}
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
84
Source/santad/EventProviders/DiskArbitrationTestUtil.h
Normal file
84
Source/santad/EventProviders/DiskArbitrationTestUtil.h
Normal 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
|
||||
120
Source/santad/EventProviders/DiskArbitrationTestUtil.mm
Normal file
120
Source/santad/EventProviders/DiskArbitrationTestUtil.mm
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
42
Source/santad/EventProviders/SNTDeviceManager.h
Normal file
42
Source/santad/EventProviders/SNTDeviceManager.h
Normal 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
|
||||
315
Source/santad/EventProviders/SNTDeviceManager.mm
Normal file
315
Source/santad/EventProviders/SNTDeviceManager.mm
Normal 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
|
||||
193
Source/santad/EventProviders/SNTDeviceManagerTest.mm
Normal file
193
Source/santad/EventProviders/SNTDeviceManagerTest.mm
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
":SNTMetricWriter",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"@MOLAuthenticatingURLSession",
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "SNTCommandSyncStage.h"
|
||||
#import "SNTSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
|
||||
@interface SNTSyncEventUpload : SNTSyncStage
|
||||
|
||||
- (BOOL)uploadEvents:(NSArray *)events;
|
||||
|
||||
@@ -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];
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "SNTCommandSyncStage.h"
|
||||
#import "SNTSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncPreflight : SNTCommandSyncStage
|
||||
@interface SNTSyncPostflight : SNTSyncStage
|
||||
@end
|
||||
@@ -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];
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "SNTCommandSyncStage.h"
|
||||
#import "SNTSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncPostflight : SNTCommandSyncStage
|
||||
@interface SNTSyncPreflight : SNTSyncStage
|
||||
@end
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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]);
|
||||
1
Source/santasyncservice/testdata/sync_preflight_toggle_blockusb.json
vendored
Normal file
1
Source/santasyncservice/testdata/sync_preflight_toggle_blockusb.json
vendored
Normal 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"]}
|
||||
1
Source/santasyncservice/testdata/sync_ruledownload_batch2.json
vendored
Normal file
1
Source/santasyncservice/testdata/sync_ruledownload_batch2.json
vendored
Normal 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"}]}
|
||||
15
Testing/clang_analyzer/run_clang_analyzer.sh
Executable file
15
Testing/clang_analyzer/run_clang_analyzer.sh
Executable 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 $?
|
||||
@@ -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
Reference in New Issue
Block a user