From dacff7669449dc0aeecd22e791fd2fc63c37745b Mon Sep 17 00:00:00 2001 From: Tom Burgin Date: Wed, 16 Nov 2016 14:41:12 -0500 Subject: [PATCH] run santactl as a sync daemon (#129) * run santactl as a sync daemon --- Santa.xcodeproj/project.pbxproj | 18 +++ Source/common/SNTStoredEvent.h | 2 +- Source/common/SNTStoredEvent.m | 8 ++ Source/common/SNTXPCControlInterface.h | 9 +- Source/common/SNTXPCSyncdInterface.h | 33 +++++ Source/common/SNTXPCSyncdInterface.m | 23 ++++ .../santactl/Commands/sync/SNTCommandSync.m | 124 +++++++++++++----- .../Commands/sync/SNTCommandSyncEventUpload.h | 2 +- .../Commands/sync/SNTCommandSyncEventUpload.m | 11 -- .../Commands/sync/SNTCommandSyncPreflight.m | 5 +- Source/santad/DataLayer/SNTEventTable.h | 9 -- Source/santad/DataLayer/SNTEventTable.m | 31 +---- Source/santad/SNTApplication.m | 36 ++++- Source/santad/SNTDaemonControlController.h | 2 + Source/santad/SNTDaemonControlController.m | 70 ++++------ Source/santad/SNTExecutionController.h | 2 + Source/santad/SNTExecutionController.m | 28 +--- Source/santad/SNTSyncdQueue.h | 28 ++++ Source/santad/SNTSyncdQueue.m | 72 ++++++++++ Tests/LogicTests/SNTEventTableTest.m | 6 +- Tests/LogicTests/SNTExecutionControllerTest.m | 1 + 21 files changed, 360 insertions(+), 160 deletions(-) create mode 100644 Source/common/SNTXPCSyncdInterface.h create mode 100644 Source/common/SNTXPCSyncdInterface.m create mode 100644 Source/santad/SNTSyncdQueue.h create mode 100644 Source/santad/SNTSyncdQueue.m diff --git a/Santa.xcodeproj/project.pbxproj b/Santa.xcodeproj/project.pbxproj index cac90991..dfad42fa 100644 --- a/Santa.xcodeproj/project.pbxproj +++ b/Santa.xcodeproj/project.pbxproj @@ -179,9 +179,14 @@ C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */; }; C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; }; C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */; }; + C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; }; + C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; }; C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; }; C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; }; C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; }; + C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; }; + C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; }; + C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; }; EFD8E30D32F6128B9E833D64 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */; }; /* End PBXBuildFile section */ @@ -420,6 +425,10 @@ C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandCheckCache.m; sourceTree = ""; }; C795ED8E1D80A5BE007CFF42 /* SNTPolicyProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTPolicyProcessor.h; sourceTree = ""; }; C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTPolicyProcessor.m; sourceTree = ""; }; + C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCSyncdInterface.h; sourceTree = ""; }; + C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCSyncdInterface.m; sourceTree = ""; }; + C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTSyncdQueue.h; sourceTree = ""; }; + C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTSyncdQueue.m; sourceTree = ""; }; D227889DF327E7D3532FE00B /* Pods-Santa.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -725,6 +734,8 @@ 0DCD605419115D17006B445C /* SNTXPCControlInterface.m */, 0DC8C9E3180CC3BC00FCFB29 /* SNTXPCNotifierInterface.h */, 0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */, + C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */, + C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */, ); path = common; sourceTree = ""; @@ -752,6 +763,8 @@ 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */, 0DE5B5491C926E3300C00603 /* SNTNotificationQueue.h */, 0DE5B54A1C926E3300C00603 /* SNTNotificationQueue.m */, + C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */, + C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */, 0D3AF83118F87CEF0087BCEE /* Resources */, ); path = santad; @@ -1295,6 +1308,7 @@ 0D88680C1AC48A1400B86659 /* SNTSystemInfo.m in Sources */, 0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */, 0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */, + C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */, 0DB77FDB1CD14093004DF060 /* SNTBlockMessage.m in Sources */, 0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */, 0D202D191CDD2EE500A88F16 /* SNTCommandSyncTest.m in Sources */, @@ -1331,6 +1345,7 @@ 0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */, 0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */, 0D202D1B1CDD465400A88F16 /* SNTCommandSyncState.m in Sources */, + C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1350,6 +1365,7 @@ 0D41640519197AD7006A356A /* SNTCommandSyncEventUpload.m in Sources */, 0D42D2B919D2042900955F08 /* SNTConfigurator.m in Sources */, 0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */, + C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */, 0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */, 0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */, 4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */, @@ -1412,10 +1428,12 @@ 0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */, 0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */, 0DCD604B19105433006B445C /* SNTStoredEvent.m in Sources */, + C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */, 0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */, 0D9A7F421759330500035EB5 /* main.m in Sources */, 0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */, 0DE71A751B95F7F900518526 /* SNTCachedDecision.m in Sources */, + C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */, 0DCD6042190ACCB8006B445C /* SNTFileInfo.m in Sources */, 0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */, 0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */, diff --git a/Source/common/SNTStoredEvent.h b/Source/common/SNTStoredEvent.h index e6901f05..adcf9f8c 100644 --- a/Source/common/SNTStoredEvent.h +++ b/Source/common/SNTStoredEvent.h @@ -20,7 +20,7 @@ @interface SNTStoredEvent : NSObject /// -/// An index for this event, empty unless the event came from the database. +/// An index for this event, randomly generated during initialization. /// @property NSNumber *idx; diff --git a/Source/common/SNTStoredEvent.m b/Source/common/SNTStoredEvent.m index 10689c7a..3a9c87d7 100644 --- a/Source/common/SNTStoredEvent.m +++ b/Source/common/SNTStoredEvent.m @@ -57,6 +57,14 @@ ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID"); } +- (instancetype)init { + self = [super init]; + if (self) { + _idx = @(arc4random()); + } + return self; +} + - (instancetype)initWithCoder:(NSCoder *)decoder { self = [super init]; if (self) { diff --git a/Source/common/SNTXPCControlInterface.h b/Source/common/SNTXPCControlInterface.h index b634a944..5a26ff35 100644 --- a/Source/common/SNTXPCControlInterface.h +++ b/Source/common/SNTXPCControlInterface.h @@ -41,9 +41,7 @@ - (void)databaseRuleAddRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate reply:(void (^)(NSError *error))reply; - - (void)databaseEventCount:(void (^)(int64_t count))reply; -- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply; - (void)databaseEventsPending:(void (^)(NSArray *events))reply; - (void)databaseRemoveEventsWithIDs:(NSArray *)ids; - (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256 @@ -74,7 +72,6 @@ - (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply; - (void)xsrfToken:(void (^)(NSString *))reply; - (void)setXsrfToken:(NSString *)token reply:(void (^)())reply; -- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply; - (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply; - (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply; - (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply; @@ -85,6 +82,12 @@ /// - (void)setNotificationListener:(NSXPCListenerEndpoint *)listener; +/// +/// Syncd Ops +/// +- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener; +- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply; + @end @interface SNTXPCControlInterface : NSObject diff --git a/Source/common/SNTXPCSyncdInterface.h b/Source/common/SNTXPCSyncdInterface.h new file mode 100644 index 00000000..39e3f2d9 --- /dev/null +++ b/Source/common/SNTXPCSyncdInterface.h @@ -0,0 +1,33 @@ +/// Copyright 2016 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import "SNTCommonEnums.h" + +@class SNTStoredEvent; + +/// Protocol implemented by santactl and utilized by santad +@protocol SNTSyncdXPC +- (void)postEventToSyncServer:(SNTStoredEvent *)event; +- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds; +@end + +@interface SNTXPCSyncdInterface : NSObject + +/// +/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol. +/// Ensures any methods that accept custom classes as arguments are set-up before returning +/// ++ (NSXPCInterface *)syncdInterface; + +@end diff --git a/Source/common/SNTXPCSyncdInterface.m b/Source/common/SNTXPCSyncdInterface.m new file mode 100644 index 00000000..5f95bfe3 --- /dev/null +++ b/Source/common/SNTXPCSyncdInterface.m @@ -0,0 +1,23 @@ +/// Copyright 2016 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import "SNTXPCSyncdInterface.h" + +@implementation SNTXPCSyncdInterface + ++ (NSXPCInterface *)syncdInterface { + return [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)]; +} + +@end diff --git a/Source/santactl/Commands/sync/SNTCommandSync.m b/Source/santactl/Commands/sync/SNTCommandSync.m index 24245096..d55a08ca 100644 --- a/Source/santactl/Commands/sync/SNTCommandSync.m +++ b/Source/santactl/Commands/sync/SNTCommandSync.m @@ -25,23 +25,30 @@ #import "SNTConfigurator.h" #import "SNTDropRootPrivs.h" #import "SNTLogging.h" +#import "SNTStoredEvent.h" #import "SNTXPCConnection.h" #import "SNTXPCControlInterface.h" +#import "SNTXPCSyncdInterface.h" -@interface SNTCommandSync : NSObject +@interface SNTCommandSync : NSObject @property SNTCommandSyncState *syncState; +@property SNTXPCConnection *listener; +@property dispatch_source_t syncTimer; +@property BOOL isDaemon; @end @implementation SNTCommandSync REGISTER_COMMAND_NAME(@"sync") +#pragma mark SNTCommand protocol methods + + (BOOL)requiresRoot { return NO; } + (BOOL)requiresDaemonConn { - return YES; + return NO; } + (NSString *)shortHelpText { @@ -64,7 +71,6 @@ REGISTER_COMMAND_NAME(@"sync") } SNTConfigurator *config = [SNTConfigurator configurator]; - SNTCommandSync *s = [[self alloc] init]; // Gather some data needed during some sync stages @@ -89,7 +95,8 @@ REGISTER_COMMAND_NAME(@"sync") s.syncState.machineOwner = @""; LOGW(@"Missing Machine Owner."); } - + + [daemonConn resume]; [[daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) { s.syncState.xsrfToken = token; }]; @@ -132,25 +139,85 @@ REGISTER_COMMAND_NAME(@"sync") s.syncState.session = [authURLSession session]; s.syncState.daemonConn = daemonConn; + s.isDaemon = [arguments containsObject:@"--daemon"]; - if ([arguments containsObject:@"singleevent"]) { - NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1; - if (idx >= arguments.count) { - LOGI(@"singleevent takes an argument"); - exit(1); - } - - NSString *obj = arguments[idx]; - if (obj.length != 64) { - LOGI(@"singleevent passed without SHA-256 as next argument"); - exit(1); - } - return [s eventUploadSingleEvent:obj]; + if (s.isDaemon) { + [s syncd]; } else { - return [s preflight]; + [s preflight]; } } +#pragma mark daemon methods + +- (void)syncd { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + + // Create listener for return connection from daemon. + NSXPCListener *listener = [NSXPCListener anonymousListener]; + self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener]; + self.listener.exportedInterface = [SNTXPCSyncdInterface syncdInterface]; + self.listener.exportedObject = self; + self.listener.acceptedHandler = ^{ + LOGD(@"santad <--> santactl connections established"); + dispatch_semaphore_signal(sema); + }; + self.listener.invalidationHandler = ^{ + // If santad is unloaded kill santactl + LOGD(@"exiting"); + exit(0); + }; + [self.listener resume]; + + // Tell daemon to connect back to the above listener. + [[self.syncState.daemonConn remoteObjectProxy] setSyncdListener:listener.endpoint]; + + // Now wait for the connection to come in. + if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) { + [self performSelectorInBackground:@selector(syncd) withObject:nil]; + } + + self.syncTimer = [self createSyncTimer]; + [self rescheduleSyncSecondsFromNow:30]; +} + +- (dispatch_source_t)createSyncTimer { + dispatch_source_t syncTimerQ = dispatch_source_create( + DISPATCH_SOURCE_TYPE_TIMER, 0, 0, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); + + dispatch_source_set_event_handler(syncTimerQ, ^{ + [self rescheduleSyncSecondsFromNow:600]; + + if (![[SNTConfigurator configurator] syncBaseURL]) return; + [[SNTConfigurator configurator] setSyncBackOff:NO]; + [self preflight]; + }); + + dispatch_resume(syncTimerQ); + + return syncTimerQ; +} + +#pragma mark SNTSyncdXPC protocol methods + +- (void)postEventToSyncServer:(SNTStoredEvent *)event { + SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState]; + if (event && [p uploadEvents:@[event]]) { + LOGD(@"Event upload complete"); + } else { + LOGE(@"Event upload failed"); + } +} + +- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds { + uint64_t interval = seconds * NSEC_PER_SEC; + uint64_t leeway = (seconds * 0.05) * NSEC_PER_SEC; + dispatch_source_set_timer(self.syncTimer, dispatch_walltime(NULL, interval), interval, leeway); +} + +#pragma mark sync methods + - (void)preflight { SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState]; if ([p sync]) { @@ -162,7 +229,7 @@ REGISTER_COMMAND_NAME(@"sync") } } else { LOGE(@"Preflight failed, aborting run"); - exit(1); + if (!self.isDaemon) exit(1); } } @@ -183,18 +250,7 @@ REGISTER_COMMAND_NAME(@"sync") return [self ruleDownload]; } else { LOGE(@"Event upload failed, aborting run"); - exit(1); - } -} - -- (void)eventUploadSingleEvent:(NSString *)sha256 { - SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState]; - if ([p syncSingleEventWithSHA256:sha256]) { - LOGD(@"Event upload complete"); - exit(0); - } else { - LOGE(@"Event upload failed"); - exit(1); + if (!self.isDaemon) exit(1); } } @@ -208,7 +264,7 @@ REGISTER_COMMAND_NAME(@"sync") return [self postflight]; } else { LOGE(@"Rule download failed, aborting run"); - exit(1); + if (!self.isDaemon) exit(1); } } @@ -227,10 +283,10 @@ REGISTER_COMMAND_NAME(@"sync") if ([p sync]) { LOGD(@"Postflight complete"); LOGI(@"Sync completed successfully"); - exit(0); + if (!self.isDaemon) exit(0); } else { LOGE(@"Postflight failed"); - exit(1); + if (!self.isDaemon) exit(1); } } diff --git a/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h b/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h index ab9d5122..4b43cc64 100644 --- a/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h +++ b/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h @@ -16,7 +16,7 @@ @interface SNTCommandSyncEventUpload : SNTCommandSyncStage -- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256; +- (BOOL)uploadEvents:(NSArray *)events; - (BOOL)syncBundleEvents; diff --git a/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m b/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m index c7e0c041..9e7e64d3 100644 --- a/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m +++ b/Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m @@ -44,17 +44,6 @@ return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0); } -- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256 { - dispatch_semaphore_t sema = dispatch_semaphore_create(0); - [[self.daemonConn remoteObjectProxy] databaseEventForSHA256:sha256 reply:^(SNTStoredEvent *e) { - if (e) { - [self uploadEvents:@[ e ]]; - } - dispatch_semaphore_signal(sema); - }]; - return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0); -} - - (BOOL)syncBundleEvents { NSMutableArray *newEvents = [NSMutableArray array]; for (NSString *bundlePath in [NSSet setWithArray:self.syncState.bundleBinaryRequests]) { diff --git a/Source/santactl/Commands/sync/SNTCommandSyncPreflight.m b/Source/santactl/Commands/sync/SNTCommandSyncPreflight.m index 125a1451..9673af1f 100644 --- a/Source/santactl/Commands/sync/SNTCommandSyncPreflight.m +++ b/Source/santactl/Commands/sync/SNTCommandSyncPreflight.m @@ -74,10 +74,7 @@ if (!resp) return NO; - self.syncState.eventBatchSize = [resp[kBatchSize] intValue]; - if (self.syncState.eventBatchSize == 0) { - self.syncState.eventBatchSize = 50; - } + self.syncState.eventBatchSize = [resp[kBatchSize] intValue] ?: 50; self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]]; diff --git a/Source/santad/DataLayer/SNTEventTable.h b/Source/santad/DataLayer/SNTEventTable.h index a7fda965..7bfee340 100644 --- a/Source/santad/DataLayer/SNTEventTable.h +++ b/Source/santad/DataLayer/SNTEventTable.h @@ -44,15 +44,6 @@ /// - (NSUInteger)pendingEventsCount; -/// -/// Retrieve an event from the database with a given SHA-256. If multiple events -/// exist for the same SHA-256, just the first is returned. -/// -/// @param sha256 a SHA-256 of the binary to return an event for. -/// @return a single SNTStoredEvent. -/// -- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256; - /// /// Delete a single event from the database using its index. /// diff --git a/Source/santad/DataLayer/SNTEventTable.m b/Source/santad/DataLayer/SNTEventTable.m index 8facba43..adcfca5d 100644 --- a/Source/santad/DataLayer/SNTEventTable.m +++ b/Source/santad/DataLayer/SNTEventTable.m @@ -15,7 +15,6 @@ #import "SNTEventTable.h" #import "MOLCertificate.h" -#import "SNTLogging.h" #import "SNTStoredEvent.h" @implementation SNTEventTable @@ -84,7 +83,8 @@ #pragma mark Loading / Storing - (BOOL)addStoredEvent:(SNTStoredEvent *)event { - if (!event.fileSHA256 || + if (!event.idx || + !event.fileSHA256 || !event.filePath || !event.occurrenceDate || !event.decision) return NO; @@ -98,8 +98,9 @@ __block BOOL success = NO; [self inTransaction:^(FMDatabase *db, BOOL *rollback) { - success = [db executeUpdate:@"INSERT INTO 'events' (filesha256, eventdata) VALUES (?, ?)", - event.fileSHA256, eventData]; + success = [db executeUpdate:@"INSERT INTO 'events' (idx, filesha256, eventdata)" + @"VALUES (?, ?, ?)", + event.idx, event.fileSHA256, eventData]; }]; return success; @@ -115,26 +116,6 @@ return eventsPending; } -- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256 { - __block SNTStoredEvent *storedEvent; - - [self inDatabase:^(FMDatabase *db) { - FMResultSet *rs = - [db executeQuery:@"SELECT * FROM events WHERE filesha256=? LIMIT 1;", sha256]; - - if ([rs next]) { - storedEvent = [self eventFromResultSet:rs]; - if (!storedEvent) { - [db executeUpdate:@"DELETE FROM events WHERE idx=?", [rs objectForColumnName:@"idx"]]; - } - } - - [rs close]; - }]; - - return storedEvent; -} - - (NSArray *)pendingEvents { NSMutableArray *pendingEvents = [[NSMutableArray alloc] init]; @@ -164,7 +145,7 @@ @try { event = [NSKeyedUnarchiver unarchiveObjectWithData:eventData]; - event.idx = @([rs intForColumn:@"idx"]); + event.idx = event.idx ?: @((uint32_t)[rs intForColumn:@"idx"]); } @catch (NSException *exception) { } diff --git a/Source/santad/SNTApplication.m b/Source/santad/SNTApplication.m index 0790d20c..5affdbb2 100644 --- a/Source/santad/SNTApplication.m +++ b/Source/santad/SNTApplication.m @@ -23,6 +23,7 @@ #import "SNTDaemonControlController.h" #import "SNTDatabaseController.h" #import "SNTDriverManager.h" +#import "SNTDropRootPrivs.h" #import "SNTEventLog.h" #import "SNTEventTable.h" #import "SNTExecutionController.h" @@ -30,6 +31,7 @@ #import "SNTLogging.h" #import "SNTNotificationQueue.h" #import "SNTRuleTable.h" +#import "SNTSyncdQueue.h" #import "SNTXPCConnection.h" #import "SNTXPCControlInterface.h" @@ -68,11 +70,18 @@ } SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init]; + SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init]; - // Establish XPC listener for santactl connections + // Restart santactl if it goes down + syncdQueue.invalidationHandler = ^{ + [self startSyncd]; + }; + + // Establish XPC listener for Santa and santactl connections SNTDaemonControlController *dc = [[SNTDaemonControlController alloc] init]; dc.driverManager = _driverManager; dc.notQueue = notQueue; + dc.syncdQueue = syncdQueue; _controlConnection = [[SNTXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]]; @@ -81,6 +90,7 @@ [_controlConnection resume]; __block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode]; + __block NSURL *origSyncURL = [[SNTConfigurator configurator] syncBaseURL]; _configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath handler:^(unsigned long data) { if (data & DISPATCH_VNODE_ATTRIB) { @@ -107,6 +117,14 @@ [self.driverManager flushCache]; } } + + // Start santactl if the syncBaseURL changed from nil --> somthing + NSURL *syncURL = [[SNTConfigurator configurator] syncBaseURL]; + if (!origSyncURL && syncURL) { + origSyncURL = syncURL; + LOGI(@"SyncBaseURL added, starting santactl."); + [self startSyncd]; + } } }]; @@ -117,13 +135,29 @@ ruleTable:ruleTable eventTable:eventTable notifierQueue:notQueue + syncdQueue:syncdQueue eventLog:_eventLog]; + // Start up santactl as a daemon if a sync server exists. + [self startSyncd]; + if (!_execController) return nil; } return self; } +- (void)startSyncd { + if (![[SNTConfigurator configurator] syncBaseURL]) return; + + if (fork() == 0) { + // Ensure we have no privileges + if (!DropRootPrivileges()) { + _exit(EPERM); + } + _exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL)); + } +} + - (void)start { LOGI(@"Connected to driver, activating."); diff --git a/Source/santad/SNTDaemonControlController.h b/Source/santad/SNTDaemonControlController.h index 78da457f..e6eb3608 100644 --- a/Source/santad/SNTDaemonControlController.h +++ b/Source/santad/SNTDaemonControlController.h @@ -16,6 +16,7 @@ @class SNTDriverManager; @class SNTNotificationQueue; +@class SNTSyncdQueue; /// /// SNTDaemonControlController handles all of the RPCs from santactl @@ -24,5 +25,6 @@ @property SNTDriverManager *driverManager; @property SNTNotificationQueue *notQueue; +@property SNTSyncdQueue *syncdQueue; @end diff --git a/Source/santad/SNTDaemonControlController.m b/Source/santad/SNTDaemonControlController.m index 07837a45..3eae60d9 100644 --- a/Source/santad/SNTDaemonControlController.m +++ b/Source/santad/SNTDaemonControlController.m @@ -18,15 +18,16 @@ #import "SNTConfigurator.h" #import "SNTDatabaseController.h" #import "SNTDriverManager.h" -#import "SNTDropRootPrivs.h" #import "SNTEventTable.h" #import "SNTLogging.h" #import "SNTNotificationQueue.h" #import "SNTPolicyProcessor.h" #import "SNTRule.h" #import "SNTRuleTable.h" +#import "SNTSyncdQueue.h" #import "SNTXPCConnection.h" #import "SNTXPCNotifierInterface.h" +#import "SNTXPCSyncdInterface.h" // Globals used by the santad watchdog thread uint64_t watchdogCPUEvents = 0; @@ -36,7 +37,6 @@ double watchdogRAMPeak = 0; @interface SNTDaemonControlController () @property NSString *_syncXsrfToken; -@property dispatch_source_t syncTimer; @property SNTPolicyProcessor *policyProcessor; @end @@ -45,46 +45,12 @@ double watchdogRAMPeak = 0; - (instancetype)init { self = [super init]; if (self) { - _syncTimer = [self createSyncTimer]; - [self rescheduleSyncSecondsFromNow:30]; _policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable: [SNTDatabaseController ruleTable]]; } return self; } -- (dispatch_source_t)createSyncTimer { - dispatch_source_t syncTimerQ = dispatch_source_create( - DISPATCH_SOURCE_TYPE_TIMER, 0, 0, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); - - dispatch_source_set_event_handler(syncTimerQ, ^{ - [self rescheduleSyncSecondsFromNow:600]; - - if (![[SNTConfigurator configurator] syncBaseURL]) return; - [[SNTConfigurator configurator] setSyncBackOff:NO]; - - if (fork() == 0) { - // Ensure we have no privileges - if (!DropRootPrivileges()) { - _exit(EPERM); - } - - _exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", NULL)); - } - }); - - dispatch_resume(syncTimerQ); - - return syncTimerQ; -} - -- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds { - uint64_t interval = seconds * NSEC_PER_SEC; - uint64_t leeway = (seconds * 0.05) * NSEC_PER_SEC; - dispatch_source_set_timer(self.syncTimer, dispatch_walltime(NULL, interval), interval, leeway); -} - #pragma mark Kernel ops - (void)cacheCount:(void (^)(int64_t))reply { @@ -127,10 +93,6 @@ double watchdogRAMPeak = 0; reply([[SNTDatabaseController eventTable] pendingEventsCount]); } -- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply { - reply([[SNTDatabaseController eventTable] pendingEventForSHA256:sha256]); -} - - (void)databaseEventsPending:(void (^)(NSArray *events))reply { reply([[SNTDatabaseController eventTable] pendingEvents]); } @@ -180,12 +142,6 @@ double watchdogRAMPeak = 0; reply(); } -- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply { - [self rescheduleSyncSecondsFromNow:seconds]; - [[SNTConfigurator configurator] setSyncBackOff:YES]; - reply(); -} - - (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply { [[SNTConfigurator configurator] setSyncLastSuccess:date]; reply(); @@ -225,4 +181,26 @@ double watchdogRAMPeak = 0; self.notQueue.notifierConnection = c; } +#pragma mark syncd Ops + +- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener { + SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener]; + c.remoteInterface = [SNTXPCSyncdInterface syncdInterface]; + c.invalidationHandler = ^{ + [self.syncdQueue stopSyncingEvents]; + self.syncdQueue.invalidationHandler(); + }; + c.acceptedHandler = ^{ + [self.syncdQueue startSyncingEvents]; + }; + [c resume]; + self.syncdQueue.syncdConnection = c; +} + +- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply { + [[self.syncdQueue.syncdConnection remoteObjectProxy] rescheduleSyncSecondsFromNow:seconds]; + [[SNTConfigurator configurator] setSyncBackOff:YES]; + reply(); +} + @end diff --git a/Source/santad/SNTExecutionController.h b/Source/santad/SNTExecutionController.h index 1c2c751c..ec873dfb 100644 --- a/Source/santad/SNTExecutionController.h +++ b/Source/santad/SNTExecutionController.h @@ -21,6 +21,7 @@ @class SNTEventTable; @class SNTNotificationQueue; @class SNTRuleTable; +@class SNTSyncdQueue; /// /// SNTExecutionController is responsible for handling binary execution requests: @@ -36,6 +37,7 @@ ruleTable:(SNTRuleTable *)ruleTable eventTable:(SNTEventTable *)eventTable notifierQueue:(SNTNotificationQueue *)notifierQueue + syncdQueue:(SNTSyncdQueue *)syncdQueue eventLog:(SNTEventLog *)eventLog; /// diff --git a/Source/santad/SNTExecutionController.m b/Source/santad/SNTExecutionController.m index 534773af..e31257db 100644 --- a/Source/santad/SNTExecutionController.m +++ b/Source/santad/SNTExecutionController.m @@ -36,6 +36,7 @@ #import "SNTRule.h" #import "SNTRuleTable.h" #import "SNTStoredEvent.h" +#import "SNTSyncdQueue.h" @interface SNTExecutionController () @property SNTDriverManager *driverManager; @@ -44,8 +45,8 @@ @property SNTNotificationQueue *notifierQueue; @property SNTPolicyProcessor *policyProcessor; @property SNTRuleTable *ruleTable; +@property SNTSyncdQueue *syncdQueue; -@property NSCache *uploadBackoff; @property dispatch_queue_t eventQueue; @end @@ -57,6 +58,7 @@ ruleTable:(SNTRuleTable *)ruleTable eventTable:(SNTEventTable *)eventTable notifierQueue:(SNTNotificationQueue *)notifierQueue + syncdQueue:(SNTSyncdQueue *)syncdQueue eventLog:(SNTEventLog *)eventLog { self = [super init]; if (self) { @@ -64,11 +66,10 @@ _ruleTable = ruleTable; _eventTable = eventTable; _notifierQueue = notifierQueue; + _syncdQueue = syncdQueue; _eventLog = eventLog; _policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable]; - _uploadBackoff = [[NSCache alloc] init]; - _uploadBackoff.countLimit = 128; _eventQueue = dispatch_queue_create("com.google.santad.event_upload", DISPATCH_QUEUE_SERIAL); // This establishes the XPC connection between libsecurity and syspolicyd. @@ -245,7 +246,7 @@ } /** - This runs `santactl sync` for the event that was just saved, so that the user + This sends the event that was just saved to santactl for immediate upload, so that the user has something to vote in straight away. This method is always called on a serial queue to keep this low-priority method @@ -259,24 +260,7 @@ ![[SNTConfigurator configurator] syncBaseURL] || [[SNTConfigurator configurator] syncBackOff]) return; - // The event upload is skipped if an event upload has been initiated for it in the - // last 10 minutes. - NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256]; - - NSDate *now = [NSDate date]; - if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return; - - [self.uploadBackoff setObject:now forKey:event.fileSHA256]; - - if (fork() == 0) { - // Ensure we have no privileges - if (!DropRootPrivileges()) { - _exit(EPERM); - } - - _exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", - "singleevent", [event.fileSHA256 UTF8String], NULL)); - } + [self.syncdQueue addEvent:event]; } - (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid { diff --git a/Source/santad/SNTSyncdQueue.h b/Source/santad/SNTSyncdQueue.h new file mode 100644 index 00000000..c4cb7c90 --- /dev/null +++ b/Source/santad/SNTSyncdQueue.h @@ -0,0 +1,28 @@ +/// Copyright 2016 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +@class SNTStoredEvent; +@class SNTXPCConnection; + +@interface SNTSyncdQueue : NSObject + +@property(nonatomic) SNTXPCConnection *syncdConnection; +@property(copy) void (^invalidationHandler)(); +@property(copy) void (^acceptedHandler)(); + +- (void)addEvent:(SNTStoredEvent *)event; +- (void)startSyncingEvents; +- (void)stopSyncingEvents; + +@end diff --git a/Source/santad/SNTSyncdQueue.m b/Source/santad/SNTSyncdQueue.m new file mode 100644 index 00000000..84468526 --- /dev/null +++ b/Source/santad/SNTSyncdQueue.m @@ -0,0 +1,72 @@ +/// Copyright 2016 Google Inc. All rights reserved. +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. + +#import "SNTSyncdQueue.h" + +#import "SNTLogging.h" +#import "SNTStoredEvent.h" +#import "SNTXPCConnection.h" +#import "SNTXPCSyncdInterface.h" + +@interface SNTSyncdQueue () +@property NSCache *uploadBackoff; +@property dispatch_queue_t syncdQueue; +@property dispatch_semaphore_t sema; +@end + +@implementation SNTSyncdQueue + +- (instancetype)init { + self = [super init]; + if (self) { + _uploadBackoff = [[NSCache alloc] init]; + _uploadBackoff.countLimit = 128; + _syncdQueue = dispatch_queue_create("com.google.syncd_queue", DISPATCH_QUEUE_SERIAL); + _sema = dispatch_semaphore_create(0); + } + return self; +} + +- (void)addEvent:(SNTStoredEvent *)event { + // The event upload is skipped if an event upload has been initiated for it in the + // last 10 minutes. + NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256]; + NSDate *now = [NSDate date]; + if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return; + [self.uploadBackoff setObject:now forKey:event.fileSHA256]; + + // Hold events for a few seconds to allow santad and santactl to establish connections. + // If the connections are not established in time drop the event from the queue. + // They will be uploaded during a full sync. + dispatch_async(self.syncdQueue, ^{ + if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) { + [self.syncdConnection.remoteObjectProxy postEventToSyncServer:event]; + + // Let em flow + dispatch_semaphore_signal(self.sema); + } else { + LOGI(@"Dropping event %@ from com.google.syncd_queue", event.fileSHA256); + } + }); +} + +- (void)startSyncingEvents { + dispatch_semaphore_signal(self.sema); +} + +- (void)stopSyncingEvents { + self.sema = dispatch_semaphore_create(0); +} + +@end diff --git a/Tests/LogicTests/SNTEventTableTest.m b/Tests/LogicTests/SNTEventTableTest.m index 47f000d7..6b93eb5a 100644 --- a/Tests/LogicTests/SNTEventTableTest.m +++ b/Tests/LogicTests/SNTEventTableTest.m @@ -46,6 +46,7 @@ MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:@"/usr/bin/false"]; SNTStoredEvent *event; event = [[SNTStoredEvent alloc] init]; + event.idx = @(arc4random()); event.filePath = @"/usr/bin/false"; event.fileSHA256 = [binInfo SHA256]; event.signingChain = [csInfo certificates]; @@ -67,7 +68,7 @@ SNTStoredEvent *event = [self createTestEvent]; [self.sut addStoredEvent:event]; - SNTStoredEvent *storedEvent = [self.sut pendingEventForSHA256:event.fileSHA256]; + SNTStoredEvent *storedEvent = [self.sut pendingEvents].firstObject; XCTAssertNotNil(storedEvent); XCTAssertEqualObjects(event.filePath, storedEvent.filePath); XCTAssertEqualObjects(event.signingChain, storedEvent.signingChain); @@ -81,8 +82,7 @@ [self.sut addStoredEvent:newEvent]; XCTAssertEqual(self.sut.pendingEventsCount, 1); - SNTStoredEvent *storedEvent = [self.sut pendingEventForSHA256:newEvent.fileSHA256]; - [self.sut deleteEventWithId:storedEvent.idx]; + [self.sut deleteEventWithId:newEvent.idx]; XCTAssertEqual(self.sut.pendingEventsCount, 0); } diff --git a/Tests/LogicTests/SNTExecutionControllerTest.m b/Tests/LogicTests/SNTExecutionControllerTest.m index 937849bc..d8d27ec6 100644 --- a/Tests/LogicTests/SNTExecutionControllerTest.m +++ b/Tests/LogicTests/SNTExecutionControllerTest.m @@ -66,6 +66,7 @@ ruleTable:self.mockRuleDatabase eventTable:self.mockEventDatabase notifierQueue:nil + syncdQueue:nil eventLog:nil]; }