Compare commits

...

8 Commits

Author SHA1 Message Date
Tom Burgin
6a6a32c1cf santactl: Update to MOLFCMClient v1.7 (#245) 2018-03-13 13:07:44 -04:00
Tom Burgin
ce03611b52 santabs: Serialize calls to -[SNTBundleService createConnection] (#244) 2018-03-12 17:04:53 -04:00
Tom Burgin
bbe9f83878 Import fixes (#243)
* All: use common import style for cocoapods <PodName/PodName.h>

* All: Update Pods
2018-03-12 16:02:55 -04:00
Tom Burgin
40e6c6aa92 sync-state: perform sync-state operations on a serial q (#242)
* sync-state serial

* delete it
2018-03-07 17:35:02 -05:00
Tom Burgin
9f6ccf092a code cleanup (#241) 2018-02-26 10:51:44 -05:00
Tom Burgin
d4ba4b082f codesign check: verify all architectures (#239)
* fileinfo rule: don't use certs that have codesigning errors

* pods: MOLCodesignChecker --> 1.8
2018-02-22 14:41:47 -05:00
Tom Burgin
cce43829eb use MOLFCMClient v1.5 (#238) 2018-02-16 14:35:52 -05:00
johnl
c1bfbac2fe Various small fixes to README.md (#237)
* Various small fixes to README.md

* Apply changes
2018-02-13 11:06:28 -05:00
22 changed files with 196 additions and 480 deletions

View File

@@ -1,15 +1,15 @@
PODS:
- FMDB (2.6.2):
- FMDB/standard (= 2.6.2)
- FMDB/standard (2.6.2)
- MOLAuthenticatingURLSession (2.2):
- MOLCertificate (~> 1.5)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- MOLFCMClient (1.3):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.4)
- FMDB (2.7.2):
- FMDB/standard (= 2.7.2)
- FMDB/standard (2.7.2)
- MOLAuthenticatingURLSession (2.4):
- MOLCertificate (~> 1.8)
- MOLCertificate (1.8)
- MOLCodesignChecker (1.9):
- MOLCertificate (~> 1.8)
- MOLFCMClient (1.7):
- MOLAuthenticatingURLSession (~> 2.4)
- OCMock (3.4.1)
DEPENDENCIES:
- FMDB
@@ -20,13 +20,13 @@ DEPENDENCIES:
- OCMock
SPEC CHECKSUMS:
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
FMDB: 6198a90e7b6900cfc046e6bc0ef6ebb7be9236aa
MOLAuthenticatingURLSession: c238aa1c9a7b1077eb39a6f40204bfe76a7d204e
MOLCertificate: c999513316d511c69f290fbf313dfe8dca4ad592
MOLCodesignChecker: 303c01755646a0045c97f9f0b0fe5945ead42130
MOLFCMClient: ee45348909351f232e2759c580329072ae7e02d4
OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
COCOAPODS: 1.3.1
COCOAPODS: 1.4.0

View File

@@ -15,7 +15,7 @@ execution decisions based on the contents of a SQLite database, a GUI agent that
notifies the user in case of a block decision and a command-line utility for
managing the system and synchronizing the database with a server.
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
Santa is not yet at 1.0. We're writing more tests, fixing bugs, working on TODOs
and finishing up a security audit.
It is named Santa because it keeps track of binaries that are naughty or nice.
@@ -29,21 +29,21 @@ The Santa docs are stored in the [Docs](https://github.com/google/santa/blob/mas
Admin-Related Features
========
* Multiple modes: In the default MONITOR mode, all binaries except
* Multiple modes: In the default MONITOR mode, all binaries except
those marked as blacklisted will be allowed to run, whilst being logged and recorded in the events database. In LOCKDOWN mode, only whitelisted binaries are
allowed to run.
* Event logging: When the kext is loaded, all binary launches are logged.
When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
* Certificate-based rules, with override levels: Instead of relying on a binaries hash (or 'fingerprint'), executables can be whitelisted/blacklisted by their signing
* Certificate-based rules, with override levels: Instead of relying on a binary's hash (or 'fingerprint'), executables can be whitelisted/blacklisted by their signing
certificate. You can therefore trust/block all binaries by a given publisher that were signed with that cert across version updates. A
binary can only be whitelisted by its certificate if its signature validates
correctly, but a rule for a binaries fingerprint will override a decision for a
correctly, but a rule for a binary's fingerprint will override a decision for a
certificate; i.e. you can whitelist a certificate while blacklisting a binary
signed with that certificate, or vice-versa.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature as Managed Client for OS X's (the precursor to configuration profiles, which used the same implementation mechanism) Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and doesn't rely on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature to that found in Managed Client (the precursor to configuration profiles, which used the same implementation mechanism), Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and not relying on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
* Failsafe cert rules: You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore auto-whitelisted. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct separate cert than other Google apps.
@@ -54,7 +54,7 @@ Santa is written with the intention of helping protect users from themselves.
People often download malware and trust it, giving the malware credentials, or
allowing unknown software to exfiltrate more data about your system. As a
centrally managed component, Santa can help stop the spread of malware among a
larger fleet of machines. Independently, Santa can aid in analyzing what is
large fleet of machines. Independently, Santa can aid in analyzing what is
running on your computer.
Santa is part of a defense-in-depth strategy, and you should continue to protect
@@ -62,7 +62,6 @@ hosts in whatever other ways you see fit.
Get Help
========
If you have questions or otherwise need help getting started, the
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place. Please consult the [wiki](https://github.com/google/santa/wiki) and [issues](https://github.com/google/santa/issues) as well.
@@ -84,7 +83,7 @@ continue to work across OS versions.
Known Issues
============
Santa is not yet a 1.0 and we have some known issues to be aware of:
Santa is not yet at 1.0 and we have some known issues to be aware of:
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been replaced, or

View File

@@ -148,9 +148,6 @@
0DEA5F7B1CF64C9200704398 /* sync_ruledownload_batch2.json in Resources */ = {isa = PBXBuildFile; fileRef = 0DEA5F781CF64C8B00704398 /* sync_ruledownload_batch2.json */; };
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.m */; };
0DEFB7C01ACB28B000B92AAE /* SNTCommandSyncConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */; };
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */; };
0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DF395631AB76A7900CBC520 /* NSData+Zlib.m */; };
0DF395661AB76ABC00CBC520 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DF395651AB76ABC00CBC520 /* libz.dylib */; };
168A4E09A2A5E0B7DF8A2F1A /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C11A10A5D6E112788769CF70 /* libPods-santad.a */; };
@@ -171,8 +168,6 @@
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; };
C78227631E1C3C7D006EB2D6 /* santabs.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = C78227541E1C3C58006EB2D6 /* santabs.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C7943FE92028B855008D4F76 /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
C7943FEA2028C4F7008D4F76 /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C79A23581E23F7E80037AFA8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C79A23561E23F7E80037AFA8 /* main.m */; };
@@ -416,9 +411,6 @@
0DEA5F781CF64C8B00704398 /* sync_ruledownload_batch2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = sync_ruledownload_batch2.json; sourceTree = "<group>"; };
0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncConstants.m; sourceTree = "<group>"; };
0DEFB7C11ACB28BC00B92AAE /* SNTCommandSyncConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncConstants.h; sourceTree = "<group>"; };
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTFileWatcher.h; sourceTree = "<group>"; };
0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileWatcher.m; sourceTree = "<group>"; };
0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileWatcherTest.m; sourceTree = "<group>"; };
0DF395621AB76A7900CBC520 /* NSData+Zlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Zlib.h"; sourceTree = "<group>"; };
0DF395631AB76A7900CBC520 /* NSData+Zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Zlib.m"; sourceTree = "<group>"; };
0DF395651AB76ABC00CBC520 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
@@ -540,7 +532,6 @@
0D41DAD31A7C28C800A890FE /* SNTEventTableTest.m */,
0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */,
0DD0D48E194F78F8005F27EB /* SNTFileInfoTest.m */,
0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */,
0DB537861AFD36EB00487F92 /* SNTRuleTableTest.m */,
0D3AFBE618FB32CB0087BCEE /* SNTXPCConnectionTest.m */,
);
@@ -733,8 +724,6 @@
0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */,
0DCD6040190ACCB8006B445C /* SNTFileInfo.h */,
0DCD6041190ACCB8006B445C /* SNTFileInfo.m */,
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */,
0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */,
0D28E5E31926AFE400280F87 /* SNTKernelCommon.h */,
0D28E5E119269B3600280F87 /* SNTLogging.h */,
0DA73C9E1934F8100056D7C4 /* SNTLogging.m */,
@@ -1466,10 +1455,8 @@
0DCD605819115E57006B445C /* SNTXPCControlInterface.m in Sources */,
0D202D1A1CDD464B00A88F16 /* SNTCommandSyncPreflight.m in Sources */,
0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */,
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */,
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */,
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */,
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */,
0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */,
0DE5B54C1C92722300C00603 /* SNTNotificationQueue.m in Sources */,
0DEA5F651CF6057D00704398 /* SNTCommandSyncEventUpload.m in Sources */,
@@ -1516,7 +1503,6 @@
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */,
0D416401191974F1006A356A /* SNTCommandSyncState.m in Sources */,
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */,
C7943FE92028B855008D4F76 /* SNTFileWatcher.m in Sources */,
0DB77FDA1CD14092004DF060 /* SNTBlockMessage.m in Sources */,
0D35BDA218FD71CE00921A21 /* main.m in Sources */,
0DCD6043190ACCB8006B445C /* SNTFileInfo.m in Sources */,
@@ -1535,7 +1521,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C7943FEA2028C4F7008D4F76 /* SNTFileWatcher.m in Sources */,
0DCA552718C95928002A7DAE /* SNTXPCConnection.m in Sources */,
0D385DF1180DE51600418BC6 /* SNTAppDelegate.m in Sources */,
0D88680A1AC48A1200B86659 /* SNTSystemInfo.m in Sources */,
@@ -1583,7 +1568,6 @@
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 */,
0D7D01871774F93A005DBAB4 /* SNTDriverManager.m in Sources */,
0D8E18CD19107B56000F89B8 /* SNTDaemonControlController.m in Sources */,

View File

@@ -16,10 +16,10 @@
@import SecurityInterface.SFCertificatePanel;
#import "MOLCertificate.h"
#import <MOLCertificate/MOLCertificate.h>
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTFileInfo.h"
#import "SNTMessageWindow.h"
#import "SNTStoredEvent.h"

View File

@@ -16,7 +16,6 @@
#include <sys/stat.h>
#import "SNTFileWatcher.h"
#import "SNTLogging.h"
#import "SNTStrengthify.h"
#import "SNTSystemInfo.h"
@@ -32,9 +31,6 @@
/// Holds the configurations from a sync server and mobileconfig.
@property NSMutableDictionary *syncState;
@property NSMutableDictionary *configState;
/// Watcher for the sync-state.plist.
@property(nonatomic) SNTFileWatcher *syncStateWatcher;
@end
@implementation SNTConfigurator
@@ -86,40 +82,45 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (instancetype)init {
self = [super init];
if (self) {
Class number = [NSNumber class];
Class re = [NSRegularExpression class];
Class date = [NSDate class];
Class string = [NSString class];
Class data = [NSData class];
_syncServerKeyTypes = @{
kClientModeKey : [NSNumber class],
kWhitelistRegexKey : [NSRegularExpression class],
kBlacklistRegexKey : [NSRegularExpression class],
kFullSyncLastSuccess : [NSDate class],
kRuleSyncLastSuccess : [NSDate class],
kSyncCleanRequired : [NSNumber class]
kClientModeKey : number,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kFullSyncLastSuccess : date,
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number
};
_forcedConfigKeyTypes = @{
kClientModeKey : [NSNumber class],
kFileChangesRegexKey : [NSRegularExpression class],
kWhitelistRegexKey : [NSRegularExpression class],
kBlacklistRegexKey : [NSRegularExpression class],
kEnablePageZeroProtectionKey : [NSNumber class],
kMoreInfoURLKey : [NSString class],
kEventDetailURLKey : [NSString class],
kEventDetailTextKey : [NSString class],
kUnknownBlockMessage : [NSString class],
kBannedBlockMessage : [NSString class],
kModeNotificationMonitor : [NSString class],
kModeNotificationLockdown : [NSString class],
kSyncBaseURLKey : [NSString class],
kClientAuthCertificateFileKey : [NSString class],
kClientAuthCertificatePasswordKey : [NSString class],
kClientAuthCertificateCNKey : [NSString class],
kClientAuthCertificateIssuerKey : [NSString class],
kServerAuthRootsDataKey : [NSData class],
kServerAuthRootsFileKey : [NSString class],
kMachineOwnerKey : [NSString class],
kMachineIDKey : [NSString class],
kMachineOwnerPlistFileKey : [NSString class],
kMachineOwnerPlistKeyKey : [NSString class],
kMachineIDPlistFileKey : [NSString class],
kMachineIDPlistKeyKey : [NSString class],
kClientModeKey : number,
kFileChangesRegexKey : re,
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kEnablePageZeroProtectionKey : number,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
kUnknownBlockMessage : string,
kBannedBlockMessage : string,
kModeNotificationMonitor : string,
kModeNotificationLockdown : string,
kSyncBaseURLKey : string,
kClientAuthCertificateFileKey : string,
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
kClientAuthCertificateIssuerKey : string,
kServerAuthRootsDataKey : data,
kServerAuthRootsFileKey : string,
kMachineOwnerKey : string,
kMachineIDKey : string,
kMachineOwnerPlistFileKey : string,
kMachineOwnerPlistKeyKey : string,
kMachineIDPlistFileKey : string,
kMachineIDPlistKeyKey : string,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -141,94 +142,129 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return sharedConfigurator;
}
+ (NSSet *)syncAndConfigStateSet {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [[self syncStateSet] setByAddingObjectsFromSet:[self configStateSet]];
});
return set;
}
+ (NSSet *)syncStateSet {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(syncState))];
});
return set;
}
+ (NSSet *)configStateSet {
static NSSet *set;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
set = [NSSet setWithObject:NSStringFromSelector(@selector(configState))];
});
return set;
}
#pragma mark KVO Dependencies
+ (NSSet *)keyPathsForValuesAffectingClientMode {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
return [self syncAndConfigStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMachineID {
return [NSSet setWithObject:@"configState"];
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
return [self syncStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingRuleSyncLastSuccess {
return [self syncStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingSyncCleanRequired {
return [self syncStateSet];
}
#pragma mark Public Interface
@@ -344,7 +380,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
self.syncState[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self updateSyncStateForKey:kFullSyncLastSuccess value:fullSyncLastSuccess];
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
@@ -353,8 +389,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
self.syncState[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveSyncStateToDisk];
[self updateSyncStateForKey:kRuleSyncLastSuccess value:ruleSyncLastSuccess];
}
- (BOOL)syncCleanRequired {
@@ -362,8 +397,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
self.syncState[kSyncCleanRequired] = @(syncCleanRequired);
[self saveSyncStateToDisk];
[self updateSyncStateForKey:kSyncCleanRequired value:@(syncCleanRequired)];
}
- (NSString *)machineOwner {
@@ -400,12 +434,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
///
/// Update the syncState. Triggers a KVO event for all dependents.
///
- (BOOL)updateSyncStateForKey:(NSString *)key value:(id)value {
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[key] = value;
self.syncState = syncState;
[self saveSyncStateToDisk];
return YES;
- (void)updateSyncStateForKey:(NSString *)key value:(id)value {
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[key] = value;
self.syncState = syncState;
[self saveSyncStateToDisk];
});
}
///
@@ -427,15 +462,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
continue;
}
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WEAKIFY(self);
self.syncStateWatcher = [[SNTFileWatcher alloc] initWithFilePath:kSyncStateFilePath
handler:^(unsigned long data) {
STRONGIFY(self);
[self syncStateFileChanged:data];
}];
});
return syncState;
}
@@ -456,39 +482,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
ofItemAtPath:kSyncStateFilePath error:NULL];
}
///
/// Ensure permissions are 0644.
/// Revert any out-of-band changes.
///
- (void)syncStateFileChanged:(unsigned long)data {
if (data & DISPATCH_VNODE_ATTRIB) {
const char *cPath = [kSyncStateFilePath fileSystemRepresentation];
struct stat fileStat;
stat(cPath, &fileStat);
int mask = S_IRWXU | S_IRWXG | S_IRWXO;
int desired = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (fileStat.st_uid != 0 || fileStat.st_gid != 0 || (fileStat.st_mode & mask) != desired) {
LOGI(@"Sync state file permissions changed, fixing.");
chown(cPath, 0, 0);
chmod(cPath, desired);
}
} else {
NSDictionary *newSyncState = [self readSyncStateFromDisk];
for (NSString *key in self.syncState) {
if (((self.syncState[key] && !newSyncState[key]) ||
(!self.syncState[key] && newSyncState[key]) ||
(self.syncState[key] && ![self.syncState[key] isEqualTo:newSyncState[key]]))) {
// Ignore sync url and dates
if ([key isEqualToString:kRuleSyncLastSuccess] ||
[key isEqualToString:kFullSyncLastSuccess]) continue;
LOGE(@"Sync state file changed, replacing");
[self saveSyncStateToDisk];
return;
}
}
}
}
- (void)clearSyncState {
self.syncState = [NSMutableDictionary dictionary];
}

View File

@@ -1,35 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple file watching class using dispatch sources. Will automatically
/// reload the watch if the file is deleted and continue watching for
/// events until deallocated.
///
@interface SNTFileWatcher : NSObject
///
/// Designated initializer
/// Initializes the watcher and begins watching for modifications.
///
/// @param filePath the file to watch.
/// @param handler the handler to call when changes happen. The argument to the block is the
/// type of change that happened as a bitmask to be compared with DISPATCH_VNODE_* constants.
///
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
handler:(nonnull void (^)(unsigned long))handler;
@end

View File

@@ -1,94 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTFileWatcher.h"
#import "SNTStrengthify.h"
@interface SNTFileWatcher ()
@property NSString *filePath;
@property(copy) void (^handler)(unsigned long);
@property dispatch_source_t source;
@end
@implementation SNTFileWatcher
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (instancetype)initWithFilePath:(nonnull NSString *)filePath
handler:(nonnull void (^)(unsigned long))handler {
self = [super init];
if (self) {
_filePath = filePath;
_handler = handler;
[self startWatchingFile];
}
return self;
}
- (void)dealloc {
[self stopWatchingFile];
}
- (void)startWatchingFile {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME |
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB);
dispatch_async(queue, ^{
int fd = -1;
const char *filePath = [self.filePath fileSystemRepresentation];
while ((fd = open(filePath, O_EVTONLY | O_CLOEXEC)) < 0) {
usleep(200000); // wait 200ms
}
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
WEAKIFY(self);
dispatch_source_set_event_handler(self.source, ^{
STRONGIFY(self);
unsigned long data = dispatch_source_get_data(self.source);
self.handler(data);
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
[self stopWatchingFile];
[self startWatchingFile];
}
sleep(2);
});
dispatch_source_set_registration_handler(self.source, ^{
STRONGIFY(self);
self.handler(0);
});
dispatch_source_set_cancel_handler(self.source, ^{
close(fd);
});
dispatch_resume(self.source);
});
}
- (void)stopWatchingFile {
if (!self.source) return;
dispatch_source_set_event_handler_f(self.source, NULL);
dispatch_source_cancel(self.source);
self.source = nil;
}
@end

View File

@@ -66,7 +66,7 @@ typedef enum {
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,
ACTION_RESPOND_ACK = 23,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
ACTION_NOTIFY_WRITE = 31,

View File

@@ -14,7 +14,7 @@
#import "SNTStoredEvent.h"
#import "MOLCertificate.h"
#import <MOLCertificate/MOLCertificate.h>
@implementation SNTStoredEvent

View File

@@ -14,7 +14,7 @@
#import "SNTXPCConnection.h"
#import "MOLCodesignChecker.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import "SNTStrengthify.h"

View File

@@ -17,8 +17,8 @@
#import <CommonCrypto/CommonDigest.h>
#import <pthread/pthread.h>
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import "SNTFileInfo.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
@@ -72,18 +72,18 @@
}
- (void)attemptReconnection {
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
[self performSelectorOnMainThread:@selector(createConnection) withObject:nil waitUntilDone:NO];
}
#pragma mark SNTBundleServiceXPC Methods
// Connect to the SantaGUI
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
[c resume];
self.notifierConnection = c;
dispatch_async(self.queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
[c resume];
self.notifierConnection = c;
[self createConnection];
});
}

View File

@@ -345,10 +345,11 @@ REGISTER_COMMAND_NAME(@"fileinfo")
dispatch_once(&token, ^{ [cmd.daemonConn resume]; });
__block SNTEventState state;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
[[cmd.daemonConn remoteObjectProxy] decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:csc.leafCertificate.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);

View File

@@ -17,8 +17,9 @@
#import "SNTCommand.h"
#import "SNTCommandController.h"
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import "SNTConfigurator.h"
#import "SNTDropRootPrivs.h"
#import "SNTFileInfo.h"

View File

@@ -178,7 +178,7 @@ static void reachabilityHandler(
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply((self.FCMClient.FCMToken != nil));
reply(self.FCMClient.isConnected);
}
#pragma mark push notification methods
@@ -205,16 +205,17 @@ static void reachabilityHandler(
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.connectionErrorHandler = ^(NSError *error) {
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
LOGE(@"FCM connection error: %@", error);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"%@", log);
LOGD(@"FCMClient: %@", log);
};
[self.FCMClient connect];

View File

@@ -14,7 +14,8 @@
#import "SNTEventTable.h"
#import "MOLCertificate.h"
#import <MOLCertificate/MOLCertificate.h>
#import "SNTStoredEvent.h"
@implementation SNTEventTable

View File

@@ -14,8 +14,9 @@
#import "SNTRuleTable.h"
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTRule.h"

View File

@@ -80,22 +80,23 @@
// Listen for actionable config changes.
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"clientMode"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"syncBaseURL"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"whitelistPathRegex"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"blacklistPathRegex"
options:bits
context:NULL];
SNTConfigurator *configurator = [SNTConfigurator configurator];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(clientMode))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(syncBaseURL))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(whitelistPathRegex))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(blacklistPathRegex))
options:bits
context:NULL];
// Establish XPC listener for Santa and santactl connections
SNTDaemonControlController *dc =
@@ -270,23 +271,25 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"clientMode"]) {
NSString *newKey = NSKeyValueChangeNewKey;
NSString *oldKey = NSKeyValueChangeOldKey;
if ([keyPath isEqualToString:NSStringFromSelector(@selector(clientMode))]) {
SNTClientMode new =
[change[@"new"] isKindOfClass:[NSNumber class]] ? [change[@"new"] longLongValue] : 0;
[change[newKey] isKindOfClass:[NSNumber class]] ? [change[newKey] longLongValue] : 0;
SNTClientMode old =
[change[@"old"] isKindOfClass:[NSNumber class]] ? [change[@"old"] longLongValue] : 0;
[change[oldKey] isKindOfClass:[NSNumber class]] ? [change[oldKey] longLongValue] : 0;
if (new != old) [self clientModeDidChange:new];
} else if ([keyPath isEqualToString:@"syncBaseURL"]) {
NSURL *new = [change[@"new"] isKindOfClass:[NSURL class]] ? change[@"new"] : nil;
NSURL *old = [change[@"old"] isKindOfClass:[NSURL class]] ? change[@"old"] : nil;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(syncBaseURL))]) {
NSURL *new = [change[newKey] isKindOfClass:[NSURL class]] ? change[newKey] : nil;
NSURL *old = [change[oldKey] isKindOfClass:[NSURL class]] ? change[oldKey] : nil;
if (!new && !old) return;
if (![new.absoluteString isEqualToString:old.absoluteString]) [self syncBaseURLDidChange:new];
} else if ([keyPath isEqualToString:@"whitelistPathRegex"] ||
[keyPath isEqualToString:@"blacklistPathRegex"]) {
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(whitelistPathRegex))] ||
[keyPath isEqualToString:NSStringFromSelector(@selector(blacklistPathRegex))]) {
NSRegularExpression *new =
[change[@"new"] isKindOfClass:[NSRegularExpression class]] ? change[@"new"] : nil;
[change[newKey] isKindOfClass:[NSRegularExpression class]] ? change[newKey] : nil;
NSRegularExpression *old =
[change[@"old"] isKindOfClass:[NSRegularExpression class]] ? change[@"old"] : nil;
[change[oldKey] isKindOfClass:[NSRegularExpression class]] ? change[oldKey] : nil;
if (!new && !old) return;
if (![new.pattern isEqualToString:old.pattern]) {
LOGI(@"Changed [white|black]list regex, flushing cache");

View File

@@ -20,7 +20,8 @@
#include <pwd.h>
#include <sys/sysctl.h>
#import "MOLCertificate.h"
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTConfigurator.h"

View File

@@ -20,8 +20,8 @@
#include "SNTLogging.h"
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import "SNTBlockMessage.h"
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"

View File

@@ -17,7 +17,7 @@
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
#import <MOLCertificate.h>
#import <MOLCertificate/MOLCertificate.h>
@class SNTCachedDecision;
@class SNTFileInfo;

View File

@@ -133,7 +133,7 @@
///
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
if (!fi) return nil;
// Determine if file is within a whitelisted path
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
@@ -150,7 +150,7 @@
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
if (!fi) return nil;
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
return @"Blacklist Regex";

View File

@@ -1,140 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import XCTest;
#import "SNTFileWatcher.h"
@interface SNTFileWatcherTest : XCTestCase
@property NSFileManager *fm;
@property NSString *file;
@end
@implementation SNTFileWatcherTest
- (void)setUp {
[super setUp];
self.fm = [NSFileManager defaultManager];
self.file = [NSTemporaryDirectory() stringByAppendingString:@"SNTFileWatcherTest_File"];
[self createFile];
usleep(10000);
}
- (void)tearDown {
[self deleteFile];
usleep(10000);
[super tearDown];
}
- (void)createFile {
[self.fm createFileAtPath:self.file contents:nil attributes:nil];
}
- (void)deleteFile {
[self.fm removeItemAtPath:self.file error:NULL];
}
- (void)testPlainInit {
XCTAssertThrows([[SNTFileWatcher alloc] init]);
}
- (void)testInitFileExists {
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Init: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^(unsigned long data) {
[exp fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testInitNewFile {
[self deleteFile];
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Init: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^(unsigned long data) {
[exp fulfill];
}];
[self createFile];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testFileChanged {
__block BOOL fulfilled = NO;
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Changed: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^(unsigned long data) {
NSString *d = [NSString stringWithContentsOfFile:self.file
encoding:NSUTF8StringEncoding
error:nil];
if (!fulfilled && [d isEqual:@"0x8BADF00D"]) {
fulfilled = YES;
[exp fulfill];
}
}];
sleep(1);
[[@"0x8BADF00D" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:self.file atomically:NO];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testFileReplaced {
__block BOOL fulfilled = NO;
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Replaced: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^(unsigned long data) {
NSString *d = [NSString stringWithContentsOfFile:self.file
encoding:NSUTF8StringEncoding
error:nil];
if (!fulfilled && [d isEqual:@"0xFACEFEED"]) {
fulfilled = YES;
[exp fulfill];
}
}];
[[@"0xFACEFEED" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:self.file atomically:YES];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testFileExtended {
int fd = open(self.file.fileSystemRepresentation, O_WRONLY);
write(fd, "0xDEAD", 6);
__block BOOL fulfilled = NO;
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Extended: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^(unsigned long data) {
int file = open(self.file.fileSystemRepresentation, O_RDONLY);
char fileData[10];
read(file, fileData, 10);
if (!fulfilled && strncmp(fileData, "0xDEADBEEF", 10) == 0) {
fulfilled = YES;
[exp fulfill];
}
}];
write(fd, "BEEF", 4);
close(fd);
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
@end