mirror of
https://github.com/google/santa.git
synced 2026-01-17 02:07:58 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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/*
|
||||
|
||||
14
BUILD
14
BUILD
@@ -99,7 +99,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 +230,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,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;
|
||||
}
|
||||
|
||||
@@ -277,3 +277,14 @@ santa_unit_test(
|
||||
srcs = ["SNTMetricSetTest.m"],
|
||||
deps = [":SNTMetricSet"],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":SNTFileInfoTest",
|
||||
":SNTMetricSetTest",
|
||||
":SNTPrefixTreeTest",
|
||||
":SantaCacheTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
@@ -254,6 +254,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 +282,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.
|
||||
///
|
||||
|
||||
@@ -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";
|
||||
@@ -95,6 +96,8 @@ static NSString *const kFCMAPIKey = @"FCMAPIKey";
|
||||
|
||||
// The keys managed by a sync server or mobileconfig.
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
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";
|
||||
@@ -131,6 +134,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kAllowedPathRegexKeyDeprecated : re,
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kBlockUSBMountKey : number,
|
||||
kRemountUSBModeKey : array,
|
||||
kFullSyncLastSuccess : date,
|
||||
kRuleSyncLastSuccess : date,
|
||||
kSyncCleanRequired : number
|
||||
@@ -145,6 +150,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kAllowedPathRegexKeyDeprecated : re,
|
||||
kBlockedPathRegexKey : re,
|
||||
kBlockedPathRegexKeyDeprecated : re,
|
||||
kBlockUSBMountKey : number,
|
||||
kRemountUSBModeKey : array,
|
||||
kEnablePageZeroProtectionKey : number,
|
||||
kEnableBadSignatureProtectionKey : number,
|
||||
kAboutText : string,
|
||||
@@ -156,6 +163,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
kModeNotificationMonitor : string,
|
||||
kModeNotificationLockdown : string,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kClientAuthCertificateFileKey : string,
|
||||
kClientAuthCertificatePasswordKey : string,
|
||||
kClientAuthCertificateCNKey : string,
|
||||
@@ -486,6 +494,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 +515,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;
|
||||
@@ -679,6 +705,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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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",
|
||||
@@ -99,6 +101,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 +163,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 +223,7 @@ santa_unit_test(
|
||||
"EventProviders/SNTEndpointSecurityManagerTest.mm",
|
||||
"EventProviders/SNTEventProvider.h",
|
||||
],
|
||||
minimum_os_version = "10.15",
|
||||
sdk_dylibs = [
|
||||
"EndpointSecurity",
|
||||
"bsm",
|
||||
@@ -211,6 +236,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 +316,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;
|
||||
};
|
||||
|
||||
33
Source/santad/EventProviders/SNTDeviceManager.h
Normal file
33
Source/santad/EventProviders/SNTDeviceManager.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/// 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>
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
- (instancetype _Nonnull)init;
|
||||
- (void)listen;
|
||||
- (BOOL)subscribed;
|
||||
|
||||
@end
|
||||
300
Source/santad/EventProviders/SNTDeviceManager.mm
Normal file
300
Source/santad/EventProviders/SNTDeviceManager.mm
Normal file
@@ -0,0 +1,300 @@
|
||||
/// 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/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;
|
||||
}
|
||||
|
||||
@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 _Nonnull)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;
|
||||
}
|
||||
|
||||
BOOL shouldRemount = self.remountArgs != nil && [self.remountArgs count] > 0;
|
||||
|
||||
if (shouldRemount) {
|
||||
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];
|
||||
}
|
||||
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
|
||||
157
Source/santad/EventProviders/SNTDeviceManagerTest.mm
Normal file
157
Source/santad/EventProviders/SNTDeviceManagerTest.mm
Normal file
@@ -0,0 +1,157 @@
|
||||
/// 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/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" ];
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, YES);
|
||||
}
|
||||
|
||||
- (void)testBlockNoRemount {
|
||||
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
|
||||
[mockES reset];
|
||||
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
[mockDA reset];
|
||||
|
||||
SNTDeviceManager *deviceManager = [[SNTDeviceManager alloc] init];
|
||||
deviceManager.blockUSBMount = YES;
|
||||
|
||||
ESResponse *got = [self triggerTestMount:deviceManager mockES:mockES mockDA:mockDA];
|
||||
|
||||
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY);
|
||||
XCTAssertEqual(mockDA.wasRemounted, NO);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -352,6 +352,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -62,8 +62,15 @@
|
||||
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;
|
||||
es_string_token_t path = m->process->executable->path;
|
||||
strlcpy(ppath, path.data, sizeof(ppath));
|
||||
} else {
|
||||
proc_pidpath(message.pid, ppath, PATH_MAX);
|
||||
}
|
||||
|
||||
[outStr
|
||||
appendFormat:
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#import "Source/santad/SNTApplication.h"
|
||||
#import "Source/santad/SNTApplicationCoreMetrics.h"
|
||||
|
||||
#import <DiskArbitration/DiskArbitration.h>
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
@@ -30,6 +29,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 +42,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 +92,12 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
_deviceManager = [[SNTDeviceManager alloc] init];
|
||||
self.deviceManager.blockUSBMount = [configurator blockUSBMount];
|
||||
if ([configurator remountUSBMode] != nil) {
|
||||
self.deviceManager.remountArgs = [configurator remountUSBMode];
|
||||
}
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
|
||||
// The filter is reset when santad disconnects from the driver.
|
||||
// Add the default filters.
|
||||
@@ -135,6 +141,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 +198,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 +253,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 +395,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,23 @@
|
||||
#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 *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,6 +126,7 @@ 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;
|
||||
}
|
||||
|
||||
@@ -97,12 +135,14 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
|
||||
if (unlikely(!binInfo)) {
|
||||
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
|
||||
[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 +151,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 +180,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,9 +223,12 @@ 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 ([SNTConfigurator configurator].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) {
|
||||
@@ -243,13 +290,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 +308,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,25 @@
|
||||
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"]]) {
|
||||
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 +121,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 +137,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 +158,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 +180,7 @@
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
|
||||
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
|
||||
[self checkMetricCounters:@"BlockCertificate" expected:@1];
|
||||
}
|
||||
|
||||
- (void)testBinaryAllowCompilerRule {
|
||||
@@ -170,6 +198,7 @@
|
||||
|
||||
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW_COMPILER
|
||||
forMessage:[self getMessage]]);
|
||||
[self checkMetricCounters:kAllowCompiler expected:@1];
|
||||
}
|
||||
|
||||
- (void)testBinaryAllowCompilerRuleDisabled {
|
||||
@@ -186,6 +215,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 +232,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 +254,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 +272,15 @@
|
||||
[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)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 +288,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 +298,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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
14
WORKSPACE
14
WORKSPACE
@@ -20,6 +20,20 @@ load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependen
|
||||
|
||||
apple_support_dependencies()
|
||||
|
||||
# Hedron Bazel Compile Commands Extractor
|
||||
# Allows integrating with clangd
|
||||
# https://github.com/hedronvision/bazel-compile-commands-extractor
|
||||
git_repository(
|
||||
name = "hedron_compile_commands",
|
||||
commit = "92db741ee6dee0c4a83a5c58be7747df7b89ed10",
|
||||
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
|
||||
shallow_since = "1638167585 -0800",
|
||||
)
|
||||
|
||||
load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_setup")
|
||||
|
||||
hedron_compile_commands_setup()
|
||||
|
||||
# Macops MOL* dependencies
|
||||
|
||||
git_repository(
|
||||
|
||||
@@ -37,6 +37,7 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
|
||||
| ModeNotificationMonitor | String | The notification text to display when the client goes into Monitor mode. Defaults to "Switching into Monitor mode". |
|
||||
| ModeNotificationLockdown | String | The notification text to display when the client goes into Lockdown mode. Defaults to "Switching into Lockdown mode". |
|
||||
| SyncBaseURL | String | The base URL of the sync server. |
|
||||
| SyncProxyConfiguration | Dictionary | The proxy configuration to use when syncing. See the [Apple Documentation](https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants) for details on the keys that can be used in this dictionary. |
|
||||
| ClientAuthCertificateFile | String | If set, this contains the location of a PKCS#12 certificate to be used for sync authentication. |
|
||||
| ClientAuthCertificatePassword | String | Contains the password for the PKCS#12 certificate. |
|
||||
| ClientAuthCertificateCN | String | If set, this is the Common Name of a certificate in the System keychain to be used for sync authentication. The corresponding private key must also be in the keychain. |
|
||||
@@ -52,10 +53,10 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
|
||||
| EventLogType | String | Defines how event logs are stored. Options are 1) syslog: Sent to ASL or ULS (if built with the 10.12 SDK or later). 2) filelog: Sent to a file on disk. Use EventLogPath to specify a path. Defaults to filelog |
|
||||
| EventLogPath | String | If EventLogType is set to filelog, EventLogPath will provide the path to save logs. Defaults to /var/db/santa/santa.log. If you change this value ensure you also update com.google.santa.newsyslog.conf with the new path. |
|
||||
| EnableMachineIDDecoration | Bool | If YES, this appends the MachineID to the end of each log line. Defaults to NO. |
|
||||
| MetricFormat | String | Format to export metrics as, supported formats are "rawjson" for a single JSON blob and "monarchjson" for a format consumable by Google's Monarch tooling. Defaults to "". |
|
||||
| MetricURL | String | URL describing where monitoring metrics should be exported. |
|
||||
| MetricExportInterval | Integer | Number of seconds to wait between exporting metrics. Defaults to 30.
|
||||
| MetricExtraLabels | Dictionary | A map of key value pairs to add to all metric root labels. (e.g. a=b,c=d) defaults to @{}). If a previously set key (e.g. host_name is set to "" then the key is remove from the metric root labels. Alternatively if a value is set for an existing key then the new value will override the old. |
|
||||
| MetricFormat | String | Format to export metrics as, supported formats are "rawjson" for a single JSON blob and "monarchjson" for a format consumable by Google's Monarch tooling. Defaults to "". |
|
||||
| MetricURL | String | URL describing where monitoring metrics should be exported. |
|
||||
| MetricExportInterval | Integer | Number of seconds to wait between exporting metrics. Defaults to 30.
|
||||
| MetricExtraLabels | Dictionary | A map of key value pairs to add to all metric root labels. (e.g. a=b,c=d) defaults to @{}). If a previously set key (e.g. host_name is set to "" then the key is remove from the metric root labels. Alternatively if a value is set for an existing key then the new value will override the old. |
|
||||
|
||||
|
||||
*overridable by the sync server: run `santactl status` to check the current
|
||||
@@ -187,20 +188,23 @@ ways to install configuration profiles:
|
||||
|
||||
## Sync Server Provided Configuration
|
||||
|
||||
| Key | Value Type | Description |
|
||||
| ------------------------------ | ---------- | ---------------------------------------- |
|
||||
| client_mode | String | MONITOR or LOCKDOWN, defaults to MONITOR. |
|
||||
| clean_sync** | Bool | If set to `True` Santa will clear all local rules and download a fresh copy from the sync-server. Defaults to `False`. |
|
||||
| batch_size | Integer | The number of rules to download or events to upload per request. Multiple requests will be made if there is more work than can fit in single request. Defaults to 50. |
|
||||
| upload_logs_url** | String | If set, the endpoint to send Santa's current logs. No default. |
|
||||
| allowed_path_regex | String | Same as the "Local Configuration" AllowedPathRegex. No default. |
|
||||
| blocked_path_regex | String | Same as the "Local Configuration" BlockedPathRegex. No default. |
|
||||
| full_sync_interval* | Integer | The max time to wait before performing a full sync with the server. Defaults to 600 secs (10 minutes) if not set. |
|
||||
| fcm_token* | String | The FCM token used by Santa to listen for FCM messages. Unique for every machine. No default. |
|
||||
| fcm_full_sync_interval* | Integer | The full sync interval if a fcm_token is set. Defaults to 14400 secs (4 hours). |
|
||||
| fcm_global_rule_sync_deadline* | Integer | The max time to wait before performing a rule sync when a global rule sync FCM message is received. This allows syncing to be staggered for global events to avoid spikes in server load. Defaults to 600 secs (10 min). |
|
||||
| enable_bundles* | Bool | If set to `True` the bundle scanning feature is enabled. Defaults to `False`. |
|
||||
| enable_transitive_rules | Bool | If set to `True` the transitive rule feature is enabled. Defaults to `False`. |
|
||||
| Key | Value Type | Description |
|
||||
| ----------------------------------- | ---------- | ---------------------------------------- |
|
||||
| client\_mode | String | MONITOR or LOCKDOWN, defaults to MONITOR. |
|
||||
| clean\_sync\*\* | Bool | If set to `True` Santa will clear all local rules and download a fresh copy from the sync-server. Defaults to `False`. |
|
||||
| batch\_size | Integer | The number of rules to download or events to upload per request. Multiple requests will be made if there is more work than can fit in single request. Defaults to 50. |
|
||||
| upload\_logs\_url\*\* | String | If set, the endpoint to send Santa's current logs. No default. |
|
||||
| allowed\_path\_regex | String | Same as the "Local Configuration" AllowedPathRegex. No default. |
|
||||
| blocked\_path\_regex | String | Same as the "Local Configuration" BlockedPathRegex. No default. |
|
||||
| full\_sync\_interval\* | Integer | The max time to wait before performing a full sync with the server. Defaults to 600 secs (10 minutes) if not set. |
|
||||
| fcm\_token\* | String | The FCM token used by Santa to listen for FCM messages. Unique for every machine. No default. |
|
||||
| fcm\_full\_sync\_interval\* | Integer | The full sync interval if a fcm\_token is set. Defaults to 14400 secs (4 hours). |
|
||||
| fcm\_global\_rule\_sync\_deadline\* | Integer | The max time to wait before performing a rule sync when a global rule sync FCM message is received. This allows syncing to be staggered for global events to avoid spikes in server load. Defaults to 600 secs (10 min). |
|
||||
| enable\_bundles\* | Bool | If set to `True` the bundle scanning feature is enabled. Defaults to `False`. |
|
||||
| enable\_transitive\_rules | Bool | If set to `True` the transitive rule feature is enabled. Defaults to `False`. |
|
||||
| block\_usb\_mass\_storage | Bool | If set to 'True' blocking USB Mass storage feature is enabled. Defaults to `False`. |
|
||||
| remount\_usb\_mode | Array | Array of strings for arguments to pass to mount -o (any of "rdonly", "noexec", "nosuid", "nobrowse", "noowners", "nodev", "async", "-j"). when forcibly remounting devices. No default. |
|
||||
|
||||
|
||||
*Held only in memory. Not persistent upon process restart.
|
||||
|
||||
@@ -225,4 +229,4 @@ and for allowing the [system extension](https://github.com/google/santa/blob/mai
|
||||
Please note that for release package installers that included the kernel extension
|
||||
as part of the payload (prior to 2021.8) the end user to be prompted to allow it
|
||||
unless explicitly allowed with another MDM-delivered configuration profile to the
|
||||
supervised system.
|
||||
supervised system.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""The version for all Santa components."""
|
||||
|
||||
SANTA_VERSION = "2021.8"
|
||||
SANTA_VERSION = "2022.1"
|
||||
|
||||
Reference in New Issue
Block a user