Compare commits

...

43 Commits
0.8.8 ... 0.8.9

Author SHA1 Message Date
Russell Hancox
0898940d0b santad: Pass santa_message_t straight to SNTExecutionController 2015-07-21 14:52:53 -04:00
Russell Hancox
38b65b0ca4 santad: Move uid->username lookup to where it's actually used 2015-07-21 14:52:53 -04:00
Russell Hancox
d36ce5eefc KernelTests: Update comments, add extra write-to-cached-file check 2015-07-21 14:52:48 -04:00
Russell Hancox
ff99ab9cfe santad: loggedInUsers:sessions: style clean-up 2015-07-21 14:22:42 -04:00
Russell Hancox
64995367c3 santad: Simplify eventStateForDecision:type: 2015-07-21 14:22:42 -04:00
Russell Hancox
c67f0ffc11 santad: Don't initiate event upload if syncing isn't enabled 2015-07-21 14:22:42 -04:00
Russell Hancox
d5403ae112 santa-driver: Move vnode-id and vnode-id-str fetching to avoid duplication 2015-07-21 14:22:42 -04:00
Russell Hancox
d21d64cbfe santad: Don't print log format every startup 2015-07-21 14:22:42 -04:00
Russell Hancox
347ee3c4f5 Merge pull request #19 from samsymons/podfile-fix
Fix an installation error with CocoaPods.
2015-07-21 14:20:41 -04:00
Sam Symons
77ed1cca29 Fix an installation error with CocoaPods.
In the later versions of CocoaPods, the `project` method was replaced
with `pods_project`. This was preventing the post_install hooks from
being run.
2015-07-21 10:41:03 -07:00
Russell Hancox
cfac7dbb37 Logging: Fix syslog logging and file rotation 2015-07-17 17:43:04 -04:00
Russell Hancox
f27d72f3f9 Tests: Update tests for new error argument to SNTFileInfo 2015-07-17 12:59:48 -04:00
Russell Hancox
3cd93b287e santa-driver: Simplify kauth callbacks, moving most of the logic to methods on SDM 2015-07-16 22:33:24 -04:00
Russell Hancox
5e5605881b santa-driver: remove client_proc, use ClientConnected() instead 2015-07-16 22:32:17 -04:00
Russell Hancox
a9b48610df santa-driver: Clear data queue if client crashes. Restore dataqueue lock, the docs lied. 2015-07-16 22:31:31 -04:00
Russell Hancox
3cca09a48c santa-driver: Remove request loops in GetFromDaemon
For large binaries it poses a risk of being overrun and as santad is working pretty reliably it's almost certainly not necessary anymore.
2015-07-15 18:55:05 -04:00
Russell Hancox
3134448eac santad: Close password database after getpwuid 2015-07-15 18:25:26 -04:00
Russell Hancox
663bdf945b KernelTest: Update for EACCES -> EPERM change 2015-07-15 18:24:13 -04:00
Russell Hancox
e94d1175e7 santad: If file can't be hashed, log an error and allow execution. 2015-07-13 11:20:39 -04:00
Russell Hancox
e20b761965 santa-driver: Change rejection errno to EPERM 2015-07-01 18:55:04 -04:00
Russell Hancox
90c64812d0 santad: close stdout before running santactl sync 2015-07-01 17:22:40 -04:00
Russell Hancox
08d368fc49 santad: Rename watchdog thread with reverse-dns name 2015-06-26 16:29:46 -04:00
Russell Hancox
39385f0bff santad: Put an autoreleasepool inside the watchdog thread. 2015-06-26 13:12:46 -04:00
Russell Hancox
8bc3418ce1 santad: Watchdog: only log memory use if it increased since last check. Increase threshold to 250MB 2015-06-25 17:58:17 -04:00
Russell Hancox
a145700398 Rakefile: Properly unload/load GUI during build. 2015-06-25 17:52:59 -04:00
Russell Hancox
409535e617 santactl: Style, indenting. 2015-06-25 11:04:57 -04:00
Russell Hancox
f625016efe santactl/sync: When rejecting a redirect, cancel the task to avoid hanging the task until timeout 2015-06-24 17:32:35 -04:00
Russell Hancox
f4c94ab1d7 santactl/sync: Failed log upload should not fail whole sync 2015-06-24 17:21:54 -04:00
Russell Hancox
8234706dd3 santad: Vacuum event database after removing multiple events. 2015-06-24 11:58:38 -04:00
Russell Hancox
1a31dc870f Merge branch 'whitelistscope'
* whitelistscope:
  Common: Rename kWhitelistDirsKey/WhitelistDirs to kWhitelistRegexKey/WhitelistRegex
  santad: NSRegularExpression doesn't work with XPC.
  santactl/sync: Sync whitelist regex
  santad: In a rule vs scope, rule wins.
  santad: Move whitelisted dirs feature to using regex instead of array. Faster and more flexible.
  santactl: Update help/error wordings
  santad: Add whitelisted directory support
2015-06-23 18:31:03 -04:00
Russell Hancox
a1712858c5 Common: Rename kWhitelistDirsKey/WhitelistDirs to kWhitelistRegexKey/WhitelistRegex 2015-06-23 18:30:42 -04:00
Russell Hancox
0059e768b9 Common: Add __NSString__ attribute to logMessage to catch format string bugs. Fix some warnings that found. 2015-06-23 18:10:46 -04:00
Russell Hancox
4fe1550bd2 santad: NSRegularExpression doesn't work with XPC. 2015-06-23 18:09:35 -04:00
Russell Hancox
0c182c8a7f santactl/sync: Sync whitelist regex 2015-06-23 17:33:43 -04:00
Russell Hancox
bcdf746def santad: In a rule vs scope, rule wins. 2015-06-23 17:33:08 -04:00
Russell Hancox
bc13ac3a98 santad: Move whitelisted dirs feature to using regex instead of array. Faster and more flexible. 2015-06-23 17:22:18 -04:00
Russell Hancox
a894e018cd santactl: Update help/error wordings 2015-06-23 17:21:17 -04:00
Russell Hancox
cbecfd444d santad: Add whitelisted directory support 2015-06-23 17:21:17 -04:00
Russell Hancox
357e5ef963 santactl/sync: NSString doesn't have an unsignedIntegerValue method 2015-06-23 17:14:47 -04:00
Russell Hancox
60594c9f03 santad/santactl-sync: Accept backoff interval from server, disable event uploads if back off is used, re-enable on next sync. 2015-06-23 15:54:30 -04:00
Russell Hancox
44b5bae8da santad: Add sync execution timer to santad 2015-06-23 15:52:39 -04:00
Russell Hancox
2e856196c5 santad: Move SIGCHLD SIG_IGN setting to main(), it doesn't need to be set repeatedly. 2015-06-23 15:36:59 -04:00
Russell Hancox
8672187c02 SantaGUI: Add keepalive to launchagent plist 2015-06-23 11:20:20 -04:00
38 changed files with 557 additions and 387 deletions

View File

@@ -30,7 +30,6 @@ PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/rele
# |-- conf
# | |-- install.sh
# | |-- com.google.santad.plist
# | |-- com.google.santasync.plist
# | |-- com.google.santagui.plist
# | +-- com.google.santa.asl.conf
# +--dsym
@@ -43,7 +42,6 @@ PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKA
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
pack-applications-Santa.app \
pack-Library-LaunchDaemons-com.google.santad.plist \
pack-Library-LaunchDaemons-com.google.santasync.plist \
pack-Library-LaunchAgents-com.google.santagui.plist \
pack-etc-asl-com.google.santa.asl.conf \
pack-script-preinstall \
@@ -53,7 +51,6 @@ santa-driver.kext: download
Santa.app: download
com.google.santad.plist: download
com.google.santagui.plist: download
com.google.santasync.plist: download
com.google.santa.asl.conf: download
download:
@@ -84,5 +81,4 @@ myclean:
@rm -f com.google.santa.asl.conf
@rm -f com.google.santad.plist
@rm -f com.google.santagui.plist
@rm -f com.google.santasync.plist
@rm -f install.sh

View File

@@ -14,7 +14,6 @@
sleep 1
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santasync.plist
sleep 1

View File

@@ -7,15 +7,16 @@
[[ $3 != "/" ]] && exit 0
/bin/launchctl remove com.google.santad
/bin/launchctl remove com.google.santasync
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Remove files from old Santa install locations, if they still exist
# Remove cruft from old Santa versions
/bin/rm /usr/libexec/santad
/bin/rm /usr/sbin/santactl
/bin/launchctl remove com.google.santasync
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist
sleep 1

View File

@@ -1,4 +1,8 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
? [S= Message santa-driver:] claim only
? [S= Message santa-driver:] file /var/log/santa.log format="[$((Time)(utc.3))] $Message"
> /var/log/santa.log mode=0644 rotate=seq compress file_max=5M all_max=100M
> /var/log/santa.log format="[$((Time)(utc.3))] $Message" mode=0644 rotate=seq compress file_max=10M all_max=100M uid=0 gid=0
? [S= Message santa-driver:] claim
? [S= Message santa-driver:] file /var/log/santa.log
? [= Sender santad] claim
? [= Sender santad] file /var/log/santa.log
? [= Sender santactl] claim
? [= Sender santactl] file /var/log/santa.log

View File

@@ -15,10 +15,6 @@
<key>SantaXPCControl</key>
<true/>
</dict>
<key>StandardOutPath</key>
<string>/var/log/santa.log</string>
<key>StandardErrorPath</key>
<string>/var/log/santa.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>

View File

@@ -10,5 +10,7 @@
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santasync</string>
<key>ProgramArguments</key>
<array>
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl</string>
<string>sync</string>
</array>
<key>StandardErrorPath</key>
<string>/var/log/santa.log</string>
<key>ProcessType</key>
<string>Background</string>
<key>StartInterval</key>
<integer>600</integer>
</dict>
</plist>

View File

@@ -19,7 +19,6 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Unload santad and scheduled sync job.
/bin/launchctl remove com.google.santad >/dev/null 2>&1
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
@@ -28,16 +27,18 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
# Delete old files, if they still exist
/bin/rm /usr/libexec/santad
/bin/rm /usr/sbin/santactl
# Cleanup cruft from old versions
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist >/dev/null 2>&1
/bin/rm /usr/libexec/santad >/dev/null 2>&1
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
# Copy new files.
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
/bin/cp ${SOURCE}/conf/com.google.{santad,santasync}.plist /Library/LaunchDaemons
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
@@ -49,7 +50,6 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Load santad and scheduled sync jobs.
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
/bin/launchctl load /Library/LaunchDaemons/com.google.santasync.plist
# Load GUI agent if someone is logged in.
[[ -n "$GUI_USER" ]] && \

View File

@@ -5,12 +5,13 @@ inhibit_all_warnings!
target :santad do
pod 'FMDB'
post_install do |rep|
rep.project.targets.each do |target|
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.name != 'Release' then
break
end
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
end

View File

@@ -14,4 +14,4 @@ SPEC CHECKSUMS:
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
COCOAPODS: 0.36.3
COCOAPODS: 0.38.0

View File

@@ -80,7 +80,6 @@ namespace :install do
task :install, [:configuration] do |t, args|
config = args[:configuration]
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
system 'sudo cp conf/com.google.santasync.plist /Library/LaunchDaemons'
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
Rake::Task['build:build'].invoke(config)
@@ -152,7 +151,7 @@ end
task :unload_gui do
puts "Unloading GUI agent"
system "sudo killall Santa 2>/dev/null"
system "launchctl unload /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
end
desc "Unload"
@@ -170,7 +169,7 @@ end
task :load_gui do
puts "Loading GUI agent"
system "open /Applications/Santa.app"
system "launchctl load /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
end
desc "Load"

View File

@@ -35,6 +35,15 @@ extern NSString * const kDefaultConfigFilePath;
///
@property(nonatomic) BOOL logAllEvents;
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
#pragma mark - GUI Settings
///
@@ -71,6 +80,12 @@ extern NSString * const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// If YES, mid-execution event uploads are skipped.
/// This property is never stored on disk.
///
@property BOOL syncBackOff;
///
/// The machine owner.
///

View File

@@ -20,6 +20,12 @@
@interface SNTConfigurator ()
@property NSString *configFilePath;
@property NSMutableDictionary *configData;
/// Creating NSRegularExpression objects is not fast, so cache it.
@property NSRegularExpression *cachedWhitelistDirRegex;
/// Array of keys that cannot be changed while santad is running if santad didn't make the change.
@property(readonly) NSArray *protectedKeys;
@end
@implementation SNTConfigurator
@@ -29,7 +35,7 @@ NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
/// The keys in the config file
static NSString * const kClientModeKey = @"ClientMode";
static NSString * const kWhitelistRegexKey = @"WhitelistRegex";
static NSString * const kLogAllEventsKey = @"LogAllEvents";
static NSString * const kMoreInfoURLKey = @"MoreInfoURL";
@@ -73,6 +79,12 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return sharedConfigurator;
}
#pragma mark Protected Keys
- (NSArray *)protectedKeys {
return @[ kClientModeKey, kWhitelistRegexKey ];
}
#pragma mark Public Interface
- (santa_clientmode_t)clientMode {
@@ -92,6 +104,27 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
}
}
- (NSRegularExpression *)whitelistPathRegex {
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
NSString *re = self.configData[kWhitelistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:nil];
}
return self.cachedWhitelistDirRegex;
}
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kWhitelistRegexKey];
} else {
self.configData[kWhitelistRegexKey] = [re pattern];
}
self.cachedWhitelistDirRegex = nil;
[self saveConfigToDisk];
}
- (BOOL)logAllEvents {
return [self.configData[kLogAllEventsKey] boolValue];
}
@@ -204,17 +237,20 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return;
}
// Ensure no-one is trying to change the client mode behind Santa's back.
if (self.configData[kClientModeKey] && configData[kClientModeKey] &&
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]] &&
geteuid() == 0) {
NSMutableDictionary *configDataMutable = [configData mutableCopy];
configDataMutable[kClientModeKey] = self.configData[kClientModeKey];
self.configData = configDataMutable;
[self saveConfigToDisk];
} else {
self.configData = [configData mutableCopy];
// Ensure no-one is trying to change protected keys behind our back.
NSMutableDictionary *configDataMutable = [configData mutableCopy];
BOOL changed = NO;
for (NSString *key in self.protectedKeys) {
if (self.configData[key] && configData[key] &&
![self.configData[key] isEqual:configData[key]] && geteuid() == 0) {
NSMutableDictionary *configDataMutable = [configData mutableCopy];
configDataMutable[key] = self.configData[key];
changed = YES;
LOGD(@"Ignoring changed configuration key: %@", key);
}
}
self.configData = configDataMutable;
if (changed) [self saveConfigToDisk];
}
#pragma mark Private

View File

@@ -23,6 +23,16 @@
///
/// @param path The path of the file this instance is to represent. The path will be
/// converted to an absolute, standardized path if it isn't already.
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
/// describing the problem.
///
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
///
/// Convenience initializer.
///
/// @param path The path to the file this instance is to represent. The path will be
/// converted to an absolute, standardized path if it isn't already.
///
- (instancetype)initWithPath:(NSString *)path;

View File

@@ -33,18 +33,34 @@
@implementation SNTFileInfo
- (instancetype)initWithPath:(NSString *)path {
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
self = [super init];
if (self) {
_path = [self resolvePath:path];
if (!_path) return nil;
_fileData = [NSData dataWithContentsOfFile:_path options:NSDataReadingMappedIfSafe error:nil];
if (_path.length == 0) {
if (error) {
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:260
userInfo:@{ NSLocalizedDescriptionKey: errStr }];
}
return nil;
}
_fileData = [NSData dataWithContentsOfFile:_path
options:NSDataReadingMappedIfSafe
error:error];
if (_fileData.length == 0) return nil;
}
return self;
}
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path error:NULL];
}
- (NSString *)SHA1 {
unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(self.fileData.bytes, (unsigned int)self.fileData.length, sha1);

View File

@@ -39,19 +39,20 @@
///
/// Logging function.
///
/// @param level one of the levels defined above
/// @param destination a FILE, generally should be stdout or stderr
/// @param destination a FILE, generally stdout/stderr. If the file is closed, the log
/// will instead be sent to syslog.
/// @param format the printf style format string
/// @param ... the arguments to format.
///
void logMessage(int level, FILE *destination, NSString *format, ...);
void logMessage(int level, FILE *destination, NSString *format, ...)
__attribute__((format(__NSString__, 3, 4)));
/// Simple logging macros
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__);
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__);
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__);
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__);
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__)
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
#endif // KERNEL

View File

@@ -14,6 +14,8 @@
#import "SNTLogging.h"
#import <sys/syslog.h>
#ifdef DEBUG
static int logLevel = LOG_LEVEL_DEBUG;
#else
@@ -21,15 +23,10 @@ static int logLevel = LOG_LEVEL_INFO; // default to info
#endif
void logMessage(int level, FILE *destination, NSString *format, ...) {
static NSDateFormatter *dateFormatter;
static NSString *binaryName;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS'Z"];
binaryName = [[NSProcessInfo processInfo] processName];
// If debug logging is enabled, the process must be restarted.
@@ -50,14 +47,20 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
fprintf(destination, "%s\n", [s UTF8String]);
} else {
NSString *levelName;
int syslogLevel = LOG_DEBUG;
switch (level) {
case LOG_LEVEL_ERROR: levelName = @"E"; break;
case LOG_LEVEL_WARN: levelName = @"W"; break;
case LOG_LEVEL_INFO: levelName = @"I"; break;
case LOG_LEVEL_DEBUG: levelName = @"D"; break;
case LOG_LEVEL_ERROR: levelName = @"E"; syslogLevel = LOG_ERR; break;
case LOG_LEVEL_WARN: levelName = @"W"; syslogLevel = LOG_WARNING; break;
case LOG_LEVEL_INFO: levelName = @"I"; syslogLevel = LOG_INFO; break;
case LOG_LEVEL_DEBUG: levelName = @"D"; syslogLevel = LOG_DEBUG; break;
}
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
if (fileno(destination) == -1) {
syslog(syslogLevel, "%s\n",
[[NSString stringWithFormat:@"%@ %@: %@", levelName, binaryName, s] UTF8String]);
} else {
fprintf(destination, "%s\n",
[[NSString stringWithFormat:@"%@ %@: %@", levelName, binaryName, s] UTF8String]);
}
}
}

View File

@@ -41,10 +41,12 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
///
/// Misc ops
/// Config ops
///
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
@end

View File

@@ -23,45 +23,35 @@ bool SantaDecisionManager::init() {
if (!super::init()) return false;
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", lck_grp_attr_alloc_init());
dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_,
lck_attr_alloc_init());
cached_decisions_ = OSDictionary::withCapacity(1000);
dataqueue_ = IOSharedDataQueue::withCapacity((sizeof(santa_message_t) +
DATA_QUEUE_ENTRY_HEADER_SIZE)
* kMaxQueueEvents);
dataqueue_ = IOSharedDataQueue::withEntries(kMaxQueueEvents,
sizeof(santa_message_t));
if (!dataqueue_) return kIOReturnNoMemory;
shared_memory_ = dataqueue_->getMemoryDescriptor();
if (!shared_memory_) return kIOReturnNoMemory;
client_pid_ = 0;
return true;
}
void SantaDecisionManager::free() {
if (shared_memory_) {
shared_memory_->release();
shared_memory_ = NULL;
}
if (dataqueue_) {
dataqueue_->release();
dataqueue_ = NULL;
}
if (cached_decisions_) {
cached_decisions_->release();
cached_decisions_ = NULL;
}
OSSafeReleaseNULL(dataqueue_);
OSSafeReleaseNULL(cached_decisions_);
if (cached_decisions_lock_) {
lck_rw_free(cached_decisions_lock_, sdm_lock_grp_);
cached_decisions_lock_ = NULL;
}
if (dataqueue_lock_) {
lck_mtx_free(dataqueue_lock_, sdm_lock_grp_);
dataqueue_lock_ = NULL;
}
if (sdm_lock_grp_) {
lck_grp_free(sdm_lock_grp_);
sdm_lock_grp_ = NULL;
@@ -79,10 +69,12 @@ void SantaDecisionManager::ConnectClient(mach_port_t port, pid_t pid) {
// connected should be cleared
ClearCache();
lck_mtx_lock(dataqueue_lock_);
dataqueue_->setNotificationPort(port);
lck_mtx_unlock(dataqueue_lock_);
client_pid_ = pid;
client_proc_ = proc_find(pid);
failed_queue_requests_ = 0;
}
@@ -95,12 +87,17 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
if (!itDied) {
santa_message_t message = {.action = ACTION_REQUEST_SHUTDOWN};
PostToQueue(message);
dataqueue_->setNotificationPort(NULL);
} else {
// If the client died, reset the data queue so when it reconnects
// it doesn't get swamped straight away.
lck_mtx_lock(dataqueue_lock_);
dataqueue_->release();
dataqueue_ = IOSharedDataQueue::withEntries(kMaxQueueEvents,
sizeof(santa_message_t));
lck_mtx_unlock(dataqueue_lock_);
}
dataqueue_->setNotificationPort(NULL);
proc_rele(client_proc_);
client_proc_ = NULL;
}
bool SantaDecisionManager::ClientConnected() {
@@ -108,7 +105,7 @@ bool SantaDecisionManager::ClientConnected() {
}
IOMemoryDescriptor *SantaDecisionManager::GetMemoryDescriptor() {
return shared_memory_;
return dataqueue_->getMemoryDescriptor();
}
#pragma mark Listener Control
@@ -252,7 +249,7 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
}
santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t message, char *vnode_id_str) {
const santa_message_t message, const char *vnode_id_str) {
santa_action_t return_action = ACTION_UNSET;
// Wait for the daemon to respond or die.
@@ -270,15 +267,11 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
return ACTION_ERROR;
}
// ... and wait for it to respond. If after kRequestLoopSleepMilliseconds
// * kMaxRequestLoops it still hasn't responded, send request again.
for (int i = 0; i < kMaxRequestLoops; ++i) {
do {
IOSleep(kRequestLoopSleepMilliseconds);
return_action = GetFromCache(vnode_id_str);
if (CHECKBW_RESPONSE_VALID(return_action)) break;
}
} while (!CHECKBW_RESPONSE_VALID(return_action) &&
proc_exiting(client_proc_) == 0);
} while (return_action == ACTION_REQUEST_CHECKBW && ClientConnected());
} while (!CHECKBW_RESPONSE_VALID(return_action) && ClientConnected());
// If response is still not valid, the daemon exited
if (!CHECKBW_RESPONSE_VALID(return_action)) {
@@ -292,16 +285,12 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
}
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t credential,
const vfs_context_t vfs_context,
const vnode_t vnode) {
const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id,
const char *vnode_id_str) {
santa_action_t return_action = ACTION_UNSET;
// Fetch Vnode ID & string
uint64_t vnode_id = GetVnodeIDForVnode(vfs_context, vnode);
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Check to see if item is in cache
return_action = GetFromCache(vnode_id_str);
@@ -314,14 +303,14 @@ santa_action_t SantaDecisionManager::FetchDecision(
// Get path
char path[MAXPATHLEN];
int name_len = MAXPATHLEN;
if (vn_getpath(vnode, path, &name_len) != 0) {
if (vn_getpath(vp, path, &name_len) != 0) {
path[0] = '\0';
}
// Prepare message to send to daemon.
santa_message_t message = {};
strlcpy(message.path, path, sizeof(message.path));
message.userId = kauth_cred_getuid(credential);
message.userId = kauth_cred_getuid(cred);
message.pid = proc_selfpid();
message.ppid = proc_selfppid();
message.action = ACTION_REQUEST_CHECKBW;
@@ -341,16 +330,18 @@ santa_action_t SantaDecisionManager::FetchDecision(
bool SantaDecisionManager::PostToQueue(santa_message_t message) {
bool kr = false;
lck_mtx_lock(dataqueue_lock_);
kr = dataqueue_->enqueue(&message, sizeof(message));
lck_mtx_unlock(dataqueue_lock_);
return kr;
}
uint64_t SantaDecisionManager::GetVnodeIDForVnode(
const vfs_context_t context, const vnode_t vp) {
const vfs_context_t ctx, const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, context);
vnode_getattr(vp, &vap, ctx);
return vap.va_fileid;
}
@@ -371,6 +362,54 @@ void SantaDecisionManager::DecrementListenerInvocations() {
OSDecrementAtomic(&listener_invocations_);
}
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
const vfs_context_t ctx,
const vnode_t vp,
int *errno) {
// Only operate on regular files (not directories, symlinks, etc.).
vtype vt = vnode_vtype(vp);
if (vt != VREG) return KAUTH_RESULT_DEFER;
// Get ID for the vnode and convert it to a string.
uint64_t vnode_id = GetVnodeIDForVnode(ctx, vp);
char vnode_str[MAX_VNODE_ID_STR];
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Fetch decision
santa_action_t returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
// If file has dirty blocks, remove from cache and deny. This would usually
// be the case if a file has been written to and flushed but not yet
// closed.
if (vnode_hasdirtyblks(vp)) {
CacheCheck(vnode_str);
returnedAction = ACTION_RESPOND_CHECKBW_DENY;
}
switch (returnedAction) {
case ACTION_RESPOND_CHECKBW_ALLOW:
return KAUTH_RESULT_ALLOW;
case ACTION_RESPOND_CHECKBW_DENY:
*errno = EPERM;
return KAUTH_RESULT_DENY;
default:
// NOTE: Any unknown response or error condition causes us to fail open.
// Whilst from a security perspective this is bad, it's important that
// we don't break user's machines.
return KAUTH_RESULT_DEFER;
}
}
int SantaDecisionManager::FileOpCallback(const vnode_t vp) {
vfs_context_t context = vfs_context_create(NULL);
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
GetVnodeIDForVnode(context, vp));
CacheCheck(vnode_id_str);
vfs_context_rele(context);
return KAUTH_RESULT_DEFER;
}
#undef super
#pragma mark Kauth Callbacks
@@ -378,26 +417,17 @@ void SantaDecisionManager::DecrementListenerInvocations() {
extern "C" int fileop_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
if (!(action == KAUTH_FILEOP_CLOSE && arg2 & KAUTH_FILEOP_CLOSE_MODIFIED)) {
return KAUTH_RESULT_DEFER;
}
if (idata == NULL) {
LOGE("FileOp callback established without valid decision manager.");
if (action != KAUTH_FILEOP_CLOSE ||
!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED) ||
idata == NULL) {
return KAUTH_RESULT_DEFER;
}
SantaDecisionManager *sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
sdm->IncrementListenerInvocations();
vfs_context_t context = vfs_context_create(NULL);
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
sdm->GetVnodeIDForVnode(context, (vnode_t)arg0));
sdm->CacheCheck(vnode_id_str);
vfs_context_rele(context);
sdm->FileOpCallback(reinterpret_cast<vnode_t>(arg0));
sdm->DecrementListenerInvocations();
return KAUTH_RESULT_DEFER;
@@ -406,64 +436,20 @@ extern "C" int fileop_scope_callback(
extern "C" int vnode_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
// The default action is to defer
int returnResult = KAUTH_RESULT_DEFER;
// Cast arguments to correct types
if (idata == NULL) {
LOGE("Vnode callback established without valid decision manager.");
return returnResult;
if (action & KAUTH_VNODE_ACCESS ||
!(action & KAUTH_VNODE_EXECUTE) ||
idata == NULL) {
return KAUTH_RESULT_DEFER;
}
SantaDecisionManager *sdm =
OSDynamicCast(SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
vfs_context_t vfs_context = reinterpret_cast<vfs_context_t>(arg0);
vnode_t vnode = reinterpret_cast<vnode_t>(arg1);
// Only operate on regular files (not directories, symlinks, etc.)
vtype vt = vnode_vtype(vnode);
if (vt != VREG) return returnResult;
// Don't operate on ACCESS events, as they're advisory
if (action & KAUTH_VNODE_ACCESS) return returnResult;
// Filter for only EXECUTE actions
if (action & KAUTH_VNODE_EXECUTE) {
sdm->IncrementListenerInvocations();
// Fetch decision
santa_action_t returnedAction =
sdm->FetchDecision(credential, vfs_context, vnode);
// If file has dirty blocks, remove from cache and deny. This would usually
// be the case if a file has been written to and flushed but not yet
// closed.
if (vnode_hasdirtyblks(vnode)) {
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
sdm->GetVnodeIDForVnode(vfs_context, vnode));
sdm->CacheCheck(vnode_id_str);
returnedAction = ACTION_RESPOND_CHECKBW_DENY;
}
switch (returnedAction) {
case ACTION_RESPOND_CHECKBW_ALLOW:
returnResult = KAUTH_RESULT_ALLOW;
break;
case ACTION_RESPOND_CHECKBW_DENY:
*(reinterpret_cast<int *>(arg3)) = EACCES;
returnResult = KAUTH_RESULT_DENY;
break;
default:
// NOTE: Any unknown response or error condition causes us to fail open.
// Whilst from a security perspective this is bad, it's important that
// we don't break user's machines.
break;
}
sdm->DecrementListenerInvocations();
return returnResult;
}
return returnResult;
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
reinterpret_cast<vnode_t>(arg1),
reinterpret_cast<int *>(arg3));
sdm->DecrementListenerInvocations();
return result;
}

View File

@@ -82,36 +82,29 @@ class SantaDecisionManager : public OSObject {
/// Clears the cache.
void ClearCache();
/// Fetches a response from the cache, first checking to see if the
/// entry has expired.
santa_action_t GetFromCache(const char *identifier);
/// Fetches a response from the daemon.
santa_action_t GetFromDaemon(santa_message_t message, char *identifier);
/// Fetches an execution decision for a file, first using the cache and then
/// by sending a message to the daemon and waiting until a response arrives.
/// If a daemon isn't connected, will allow execution and cache, logging
/// the path to the executed file.
santa_action_t FetchDecision(const kauth_cred_t credential,
const vfs_context_t vfs_context,
const vnode_t vnode);
/// Posts the requested message to the client data queue.
bool PostToQueue(santa_message_t);
/// Fetches the vnode_id for a given vnode.
uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp);
/// Returns the current system uptime in microseconds
static uint64_t GetCurrentUptime();
/// Increments the count of active vnode callback's pending.
void IncrementListenerInvocations();
/// Decrements the count of active vnode callback's pending.
void DecrementListenerInvocations();
///
/// Vnode Callback
/// @param cred The kauth credential for this request.
/// @param ctx The VFS context for this request.
/// @param vp The Vnode for this request.
/// @param errno A pointer to return an errno style error.
/// @return int A valid KAUTH_RESULT_*.
///
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
const vnode_t vp, int *errno);
///
/// FileOp Callback
/// @param vp The Vnode for this request.
/// @return int Should always be KAUTH_RESULT_DEFER.
///
int FileOpCallback(const vnode_t vp);
protected:
///
/// The maximum number of milliseconds a cached deny message should be
@@ -131,12 +124,6 @@ class SantaDecisionManager : public OSObject {
///
const int kRequestLoopSleepMilliseconds = 10;
///
/// While waiting for a response from the daemon, this is the maximum number
/// of loops to wait before sending the request again.
///
const int kMaxRequestLoops = 50;
///
/// Maximum number of entries in the in-kernel cache.
///
@@ -153,20 +140,69 @@ class SantaDecisionManager : public OSObject {
///
const int kMaxQueueEvents = 512;
/// Fetches a response from the cache, first checking to see if the
/// entry has expired.
santa_action_t GetFromCache(const char *identifier);
/// Fetches a response from the daemon. Handles both daemon death
/// and failure to post messages to the daemon.
///
/// @param message The message to send to the daemon
/// @param identifier The vnode ID string for this request
/// @return santa_action_t The response for this request
///
santa_action_t GetFromDaemon(const santa_message_t message,
const char *identifier);
///
/// Fetches an execution decision for a file, first using the cache and then
/// by sending a message to the daemon and waiting until a response arrives.
/// If a daemon isn't connected, will allow execution and cache, logging
/// the path to the executed file.
///
/// @param cred The credential for this request.
/// @param vp The Vnode for this request.
/// @param vnode_id The ID for this vnode.
/// @param vnode_id_str A string representation of the above ID.
///
santa_action_t FetchDecision(const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id,
const char *vnode_id_str);
///
/// Posts the requested message to the client data queue.
///
/// @param message The message to send
/// @return bool true if sending was successful.
///
bool PostToQueue(santa_message_t message);
///
/// Fetches the vnode_id for a given vnode.
///
/// @param ctx The VFS context to use.
/// @param vp The Vnode to get the ID for
/// @return uint64_t The Vnode ID as a 64-bit unsigned int.
///
uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp);
/// Returns the current system uptime in microseconds
static uint64_t GetCurrentUptime();
private:
lck_grp_t *sdm_lock_grp_;
lck_rw_t *cached_decisions_lock_;
lck_mtx_t *dataqueue_lock_;
OSDictionary *cached_decisions_;
IOSharedDataQueue *dataqueue_;
IOMemoryDescriptor *shared_memory_;
SInt32 failed_queue_requests_;
SInt32 listener_invocations_;
pid_t client_pid_;
proc_t client_proc_;
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;

View File

@@ -55,7 +55,7 @@ REGISTER_COMMAND_NAME(@"binaryinfo")
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
if (!fileInfo) {
printf("Invalid file\n");
printf("Invalid or empty file\n");
exit(1);
}

View File

@@ -112,6 +112,8 @@
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler {
if (self.refusesRedirects) {
LOGD(@"Rejected redirection to: %@", request.URL);
[task cancel]; // without this, the connection hangs until timeout!?!
completionHandler(NULL);
} else {
completionHandler(request);

View File

@@ -46,7 +46,7 @@ REGISTER_COMMAND_NAME(@"sync")
}
+ (NSString *)shortHelpText {
return @"Synchronizes Santa with the server.";
return @"Synchronizes Santa with a configured server.";
}
+ (NSString *)longHelpText {
@@ -168,11 +168,11 @@ REGISTER_COMMAND_NAME(@"sync")
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Log upload complete");
[self eventUpload];
} else {
LOGE(@"Log upload failed, aborting run");
exit(1);
LOGE(@"Log upload failed, continuing anyway");
}
[self eventUpload];
}];
}
@@ -198,11 +198,11 @@ REGISTER_COMMAND_NAME(@"sync")
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Event upload complete");
exit(0);
LOGI(@"Event upload complete");
exit(0);
} else {
LOGW(@"Event upload failed");
exit(1);
LOGW(@"Event upload failed");
exit(1);
}
}];
}

View File

@@ -30,6 +30,7 @@ extern NSString * const kClientMode;
extern NSString * const kClientModeMonitor;
extern NSString * const kClientModeLockdown;
extern NSString * const kCleanSync;
extern NSString * const kWhitelistRegex;
extern NSString * const kEvents;
extern NSString * const kFileSHA256;
@@ -78,3 +79,5 @@ extern NSString * const kRuleTypeBinary;
extern NSString * const kRuleTypeCertificate;
extern NSString * const kRuleCustomMsg;
extern NSString * const kCursor;
extern NSString * const kBackoffInterval;

View File

@@ -32,6 +32,7 @@ NSString * const kClientMode = @"client_mode";
NSString * const kClientModeMonitor = @"MONITOR";
NSString * const kClientModeLockdown = @"LOCKDOWN";
NSString * const kCleanSync = @"clean_sync";
NSString * const kWhitelistRegex = @"whitelist_regex";
NSString * const kEvents = @"events";
NSString * const kFileSHA256 = @"file_sha256";
@@ -80,3 +81,5 @@ NSString * const kRuleTypeBinary = @"BINARY";
NSString * const kRuleTypeCertificate = @"CERTIFICATE";
NSString * const kRuleCustomMsg = @"custom_msg";
NSString * const kCursor = @"cursor";
NSString * const kBackoffInterval = @"backoff";

View File

@@ -104,12 +104,12 @@
NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
LOGE(@"HTTP Response: %ld %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
LOGI(@"Uploaded %d events", eventIds.count);
LOGI(@"Uploaded %lu events", eventIds.count);
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:eventIds];

View File

@@ -45,12 +45,12 @@
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
LOGE(@"HTTP Response: %ld %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
LOGI(@"Uploaded %d logs", [logsToUpload count]);
LOGI(@"Uploaded %lu logs", [logsToUpload count]);
handler(YES);
}
}] resume];

View File

@@ -18,6 +18,8 @@
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncPostflight
@@ -33,15 +35,22 @@
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
handler(YES);
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %ld %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSString *backoffInterval = r[kBackoffInterval];
if (backoffInterval) {
[[daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue] reply:^{}];
}
handler(YES);
}
}] resume];
}

View File

@@ -57,7 +57,7 @@
NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
LOGE(@"HTTP Response: %ld %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
@@ -73,9 +73,12 @@
[[daemonConn remoteObjectProxy] setClientMode:CLIENTMODE_LOCKDOWN reply:^{}];
}
if ([r[kWhitelistRegex] isKindOfClass:[NSString class]]) {
[[daemonConn remoteObjectProxy] setWhitelistPathRegex:r[kWhitelistRegex] reply:^{}];
}
if ([r[kCleanSync] boolValue]) {
syncState.cleanSync = YES;
LOGD(@"Clean sync requested by server");
}
handler(YES);

View File

@@ -62,7 +62,7 @@
NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
LOGE(@"HTTP Response: %ld %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
@@ -91,7 +91,7 @@
[[daemonConn remoteObjectProxy] databaseRuleAddRules:syncState.downloadedRules
cleanSlate:syncState.cleanSync
reply:^{
LOGI(@"Added %d rule(s)", syncState.downloadedRules.count);
LOGI(@"Added %lu rule(s)", syncState.downloadedRules.count);
handler(YES);
}];
} else {

View File

@@ -12,7 +12,6 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -130,17 +129,7 @@
// Validate the binary aynchronously on a concurrent queue so we don't
// hold up other execution requests in the background.
dispatch_async(q, ^{
struct passwd *user = getpwuid(message.userId);
NSString *userName;
if (user) {
userName = @(user->pw_name);
}
[self.execController validateBinaryWithPath:@(message.path)
userName:userName
pid:@(message.pid)
ppid:@(message.ppid)
vnodeId:message.vnode_id];
[self.execController validateBinaryWithMessage:message];
});
break;
}

View File

@@ -17,21 +17,64 @@
#import "SNTConfigurator.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTDropRootPrivs.h"
#import "SNTEventTable.h"
#import "SNTLogging.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
@interface SNTDaemonControlController ()
@property dispatch_source_t syncTimer;
@end
@implementation SNTDaemonControlController
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager {
self = [super init];
if (self) {
_driverManager = driverManager;
_syncTimer = [self createSyncTimer];
[self rescheduleSyncSecondsFromNow:600];
}
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];
pid_t child = fork();
if (child == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(1);
}
fclose(stdout);
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", 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 {
@@ -94,4 +137,18 @@
reply();
}
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
[self rescheduleSyncSecondsFromNow:seconds];
[[SNTConfigurator configurator] setSyncBackOff:YES];
reply();
}
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply {
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
reply();
}
@end

View File

@@ -126,6 +126,9 @@
for (NSNumber *index in indexes) {
[self deleteEventWithId:index];
}
[self inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"VACUUM"];
}];
}
@end

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#include "SNTCommonEnums.h"
#include "SNTKernelCommon.h"
@class SNTCodesignChecker;
@class SNTDriverManager;
@@ -48,16 +49,8 @@
/// the kernel, logs the event to the log and if necessary stores the event in the database and
/// sends a notification to the GUI agent.
///
/// @param path the binary that's being executed
/// @param userName the user who's executing the binary
/// @param pid the process id being executed
/// @param ppid the parent process id
/// @param vnodeId the id of the vnode being executed
/// @param message The message sent from the kernel.
///
- (void)validateBinaryWithPath:(NSString *)path
userName:(NSString *)userName
pid:(NSNumber *)pid
ppid:(NSNumber *)ppid
vnodeId:(uint64_t)vnodeId;
- (void)validateBinaryWithMessage:(santa_message_t)message;
@end

View File

@@ -15,6 +15,7 @@
#import "SNTExecutionController.h"
#include <libproc.h>
#include <pwd.h>
#include <utmpx.h>
#include "SNTLogging.h"
@@ -47,7 +48,6 @@
_ruleTable = ruleTable;
_eventTable = eventTable;
_notifierConnection = notifier;
LOGI(@"Log format: Decision (A|D), Reason (B|C|S|?), SHA-256, Path, Cert SHA-256, Cert CN");
// Workaround for xpcproxy/libsecurity bug on Yosemite
// This establishes the XPC connection between libsecurity and syspolicyd.
@@ -59,21 +59,29 @@
#pragma mark Binary Validation
- (void)validateBinaryWithPath:(NSString *)path
userName:(NSString *)userName
pid:(NSNumber *)pid
ppid:(NSNumber *)ppid
vnodeId:(uint64_t)vnodeId {
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path];
- (void)validateBinaryWithMessage:(santa_message_t)message {
NSString *path = @(message.path);
uint64_t vnodeId = message.vnode_id;
NSError *fileInfoError;
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:path error:&fileInfoError];
NSString *sha256 = [binInfo SHA256];
// If we can't read the file and hash properly, log an error.
if (!binInfo || !sha256) {
LOGW(@"Failed to read file %@: %@", path, fileInfoError.localizedDescription);
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
[self logDecisionForEventState:EVENTSTATE_ALLOW_UNKNOWN sha256:nil path:path leafCert:nil];
return;
}
// These will be filled in either in later steps
santa_action_t respondedAction = ACTION_UNSET;
SNTRule *rule;
// Get name of parent process. Do this before responding to be sure parent doesn't go away.
char pname[PROC_PIDPATHINFO_MAXSIZE];
proc_name([ppid intValue], pname, PROC_PIDPATHINFO_MAXSIZE);
proc_name(message.ppid, pname, PROC_PIDPATHINFO_MAXSIZE);
// Step 1 - binary rule?
rule = [self.ruleTable binaryRuleForSHA256:sha256];
@@ -94,7 +102,7 @@
}
// Step 3 - in scope?
if (![self fileIsInScope:path]) {
if (!rule && ![self fileIsInScope:path]) {
[self.driverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW forVnodeID:vnodeId];
[self logDecisionForEventState:EVENTSTATE_ALLOW_SCOPE sha256:sha256 path:path leafCert:nil];
return;
@@ -125,13 +133,20 @@
}
se.signingChain = csInfo.certificates;
se.executingUser = userName;
se.occurrenceDate = [[NSDate alloc] init];
se.decision = [self eventStateForDecision:respondedAction type:rule.type];
se.pid = pid;
se.ppid = ppid;
se.pid = @(message.pid);
se.ppid = @(message.ppid);
se.parentName = @(pname);
struct passwd *user = getpwuid(message.userId);
endpwent();
NSString *userName;
if (user) {
userName = @(user->pw_name);
}
se.executingUser = userName;
NSArray *loggedInUsers, *currentSessions;
[self loggedInUsers:&loggedInUsers sessions:&currentSessions];
se.currentSessions = currentSessions;
@@ -144,7 +159,9 @@
// upload for the blocked binary rather than waiting for the next sync.
// The event upload is skipped if the full path is equal to that of santactl so that
/// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
if (![path isEqual:@(kSantaCtlPath)]) {
if (![path isEqual:@(kSantaCtlPath)] &&
[[SNTConfigurator configurator] syncBaseURL]
&& ![[SNTConfigurator configurator] syncBackOff]) {
[self initiateEventUploadForSHA256:sha256];
}
@@ -167,13 +184,14 @@
///
/// Files that are out of scope:
/// + Non Mach-O files that are not part of an installer package.
/// + Files in whitelisted directories.
/// + Files in whitelisted path.
///
/// @return @c YES if file is in scope, @c NO otherwise.
///
- (BOOL)fileIsInScope:(NSString *)path {
// Determine if file is within a whitelisted directory.
if ([self pathIsInWhitelistedDir:path]) {
// Determine if file is within a whitelisted path
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
return NO;
}
@@ -188,30 +206,22 @@
return YES;
}
- (BOOL)pathIsInWhitelistedDir:(NSString *)path {
// TODO(rah): Implement this.
return NO;
}
- (santa_eventstate_t)eventStateForDecision:(santa_action_t)decision type:(santa_ruletype_t)type {
if (decision == ACTION_RESPOND_CHECKBW_ALLOW) {
if (type == RULETYPE_BINARY) {
return EVENTSTATE_ALLOW_BINARY;
} else if (type == RULETYPE_CERT) {
return EVENTSTATE_ALLOW_CERTIFICATE;
} else {
return EVENTSTATE_ALLOW_UNKNOWN;
}
} else if (decision == ACTION_RESPOND_CHECKBW_DENY) {
if (type == RULETYPE_BINARY) {
return EVENTSTATE_BLOCK_BINARY;
} else if (decision == RULETYPE_CERT) {
return EVENTSTATE_BLOCK_CERTIFICATE;
} else {
return EVENTSTATE_BLOCK_UNKNOWN;
}
} else {
return EVENTSTATE_UNKNOWN;
switch (decision) {
case ACTION_RESPOND_CHECKBW_ALLOW:
switch (type) {
case RULETYPE_BINARY: return EVENTSTATE_ALLOW_BINARY;
case RULETYPE_CERT: return EVENTSTATE_ALLOW_CERTIFICATE;
default: return EVENTSTATE_ALLOW_UNKNOWN;
}
case ACTION_RESPOND_CHECKBW_DENY:
switch (type) {
case RULETYPE_BINARY: return EVENTSTATE_BLOCK_BINARY;
case RULETYPE_CERT: return EVENTSTATE_BLOCK_CERTIFICATE;
default: return EVENTSTATE_BLOCK_UNKNOWN;
}
default: return EVENTSTATE_UNKNOWN;
}
}
@@ -259,7 +269,6 @@
}
- (void)initiateEventUploadForSHA256:(NSString *)sha256 {
signal(SIGCHLD, SIG_IGN);
pid_t child = fork();
if (child == 0) {
fclose(stdout);
@@ -295,11 +304,10 @@
}
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
struct utmpx *nxt;
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];
struct utmpx *nxt;
while ((nxt = getutxent())) {
if (nxt->ut_type != USER_PROCESS) continue;
@@ -312,11 +320,11 @@
sessionName = [NSString stringWithFormat:@"%s@%s", nxt->ut_user, nxt->ut_line];
}
if ([userName length] > 0) {
if (userName.length > 0) {
loggedInUsers[userName] = [NSNull null];
}
if ([sessionName length] > 1) {
if (sessionName.length > 1) {
loggedInHosts[sessionName] = [NSNull null];
}
}

View File

@@ -26,45 +26,49 @@ static inline double timeval_to_double(struct timeval tv) {
/// The watchdog thread function, used to monitor santad CPU/RAM usage and print a warning
/// if it goes over certain thresholds.
void *watchdog_thread_f(__unused void *idata) {
pthread_setname_np("Watchdog");
void *watchdogThreadFunction(__unused void *idata) {
pthread_setname_np("com.google.santa.watchdog");
// Number of seconds to wait between checks.
const int timeInterval = 60;
// Amount of CPU usage to trigger warning, as a percentage averaged over timeInterval
// santad's usual CPU usage is 0-3% but can occasionally spike if lots of processes start at once.
const int cpuWarnThreshold = 20;
const int cpuWarnThreshold = 20.0;
// Amount of RAM usage to trigger warning, in MB.
// santad's usual RAM usage is between 5-50MB but can spike if lots of processes start at once.
const int memWarnThreshold = 100;
const int memWarnThreshold = 250;
double prevTotalTime = 0.0;
double prevRamUseMB = 0.0;
struct rusage usage;
static double prev_total_time = 0.0;
struct mach_task_basic_info t_info;
mach_msg_type_number_t t_info_count = MACH_TASK_BASIC_INFO_COUNT;
struct mach_task_basic_info taskInfo;
mach_msg_type_number_t taskInfoCount = MACH_TASK_BASIC_INFO_COUNT;
while(true) {
sleep(timeInterval);
@autoreleasepool {
sleep(timeInterval);
// CPU
getrusage(RUSAGE_SELF, &usage);
double total_time = timeval_to_double(usage.ru_utime) + timeval_to_double(usage.ru_stime);
double percentage = (((total_time - prev_total_time) / (double)timeInterval) * 100.0);
prev_total_time = total_time;
// CPU
getrusage(RUSAGE_SELF, &usage);
double totalTime = timeval_to_double(usage.ru_utime) + timeval_to_double(usage.ru_stime);
double percentage = (((totalTime - prevTotalTime) / (double)timeInterval) * 100.0);
prevTotalTime = totalTime;
if (percentage > cpuWarnThreshold) {
LOGW(@"Watchdog: potentially high CPU use, ~%.2f%% over last %d seconds.",
percentage, timeInterval);
}
if (percentage > cpuWarnThreshold) {
LOGW(@"Watchdog: potentially high CPU use, ~%.2f%% over last %d seconds.",
percentage, timeInterval);
}
// RAM
if (KERN_SUCCESS == task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
(task_info_t)&t_info, &t_info_count)) {
double ramUseMb = (double) t_info.resident_size / 1024 / 1024;
if (ramUseMb > (double)memWarnThreshold) {
LOGW(@"Watchdog: potentially high RAM use, RSS is %.2fMB.", ramUseMb);
// RAM
if (KERN_SUCCESS == task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
(task_info_t)&taskInfo, &taskInfoCount)) {
double ramUseMB = (double) taskInfo.resident_size / 1024 / 1024;
if (ramUseMB > memWarnThreshold && ramUseMB > prevRamUseMB) {
LOGW(@"Watchdog: potentially high RAM use, RSS is %.2fMB.", ramUseMB);
}
prevRamUseMB = ramUseMB;
}
}
}
@@ -73,8 +77,8 @@ void *watchdog_thread_f(__unused void *idata) {
int main(int argc, const char *argv[]) {
@autoreleasepool {
// Do not buffer stdout
setbuf(stdout, NULL);
// Do not wait on child processes
signal(SIGCHLD, SIG_IGN);
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
@@ -83,14 +87,18 @@ int main(int argc, const char *argv[]) {
return 0;
}
// Close stdout/stderr so logging goes to syslog
fclose(stdout);
fclose(stderr);
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);
SNTApplication *s = [[SNTApplication alloc] init];
[s performSelectorInBackground:@selector(run) withObject:nil];
// Create watchdog thread
pthread_t watchdog_thread;
pthread_create(&watchdog_thread, NULL, watchdog_thread_f, NULL);
pthread_t watchdogThread;
pthread_create(&watchdogThread, NULL, watchdogThreadFunction, NULL);
[[NSRunLoop mainRunLoop] run];
}

View File

@@ -80,8 +80,8 @@
#pragma mark - Driver Helpers
/// Call in-kernel function: |kSantaUserClientReceive| passing the |action| and |vnodeId| via a
/// |santa_message_t| struct.
/// Call in-kernel function: |kSantaUserClientAllowBinary| or |kSantaUserClientDenyBinary|
/// passing the |vnodeID|.
- (void)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeid {
if (action == ACTION_RESPOND_CHECKBW_ALLOW) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0);
@@ -305,7 +305,6 @@
fputc(ch, outfile);
}
fclose(infile);
fclose(outfile);
// Now try running the temp file again. If it succeeds, the test failed.
NSTask *ps = [self taskWithPath:@"santakerneltests_tmp"];
@@ -313,7 +312,22 @@
@try {
[ps launch];
[ps waitUntilExit];
TFAIL();
TFAILINFO("Launched after write while file open");
[fm removeItemAtPath:@"santakerneltests_tmp" error:nil];
} @catch (NSException *exception) {
// This is a pass, but we have more to do.
}
// Close the file to flush the write.
fclose(outfile);
// And try running the temp file again. If it succeeds, the test failed.
ps = [self taskWithPath:@"santakerneltests_tmp"];
@try {
[ps launch];
[ps waitUntilExit];
TFAILINFO("Launched after file closed");
} @catch (NSException *exception) {
TPASS();
} @finally {
@@ -358,12 +372,12 @@
} else if (pid > 0) {
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == EACCES) {
if (WIFEXITED(status) && WEXITSTATUS(status) == EPERM) {
TPASS();
} else if (WIFSTOPPED(status)) {
TFAILINFO("Process was executed and is waiting for debugger");
} else {
TFAILINFO("Process did not exit with EACCESS as expected");
TFAILINFO("Process did not exit with EPERM as expected");
}
} else if (pid == 0) {
fclose(stdout);

View File

@@ -61,7 +61,8 @@
self.mockFileInfo = OCMClassMock([SNTFileInfo class]);
OCMStub([self.mockFileInfo alloc]).andReturn(self.mockFileInfo);
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY]).andReturn(self.mockFileInfo);
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY
error:[OCMArg setTo:nil]]).andReturn(self.mockFileInfo);
self.mockRuleDatabase = OCMClassMock([SNTRuleTable class]);
self.mockEventDatabase = OCMClassMock([SNTEventTable class]);
@@ -72,6 +73,16 @@
notifierConnection:nil];
}
/// Return a pre-configured santa_message_ t for testing with.
- (santa_message_t)getMessage {
santa_message_t message = {0};
message.pid = 12;
message.ppid = 1;
message.vnode_id = 1234;
strncpy(message.path, "/a/file", 7);
return message;
}
- (void)tearDown {
[self.mockFileInfo stopMocking];
[self.mockCodesignChecker stopMocking];
@@ -92,11 +103,7 @@
rule.state = RULESTATE_WHITELIST;
OCMExpect([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(12)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
forVnodeID:1234]);
@@ -112,11 +119,7 @@
rule.state = RULESTATE_BLACKLIST;
OCMExpect([self.mockRuleDatabase binaryRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(12)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
forVnodeID:1234]);
@@ -134,11 +137,7 @@
rule.state = RULESTATE_WHITELIST;
OCMExpect([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(12)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
forVnodeID:1234]);
@@ -148,6 +147,8 @@
id mockSut = OCMPartialMock(self.sut);
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
id cert = OCMClassMock([SNTCertificate class]);
OCMExpect([self.mockCodesignChecker leafCertificate]).andReturn(cert);
OCMExpect([cert SHA256]).andReturn(@"a");
@@ -156,11 +157,7 @@
rule.state = RULESTATE_BLACKLIST;
OCMExpect([self.mockRuleDatabase certificateRuleForSHA256:@"a"]).andReturn(rule);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(12)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
forVnodeID:1234]);
@@ -170,21 +167,15 @@
id mockSut = OCMPartialMock(self.sut);
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(YES);
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_MONITOR);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(12)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
forVnodeID:1234]);
OCMExpect([self.mockFileInfo SHA256]).andReturn(@"a");
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(12)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_DENY
forVnodeID:1234]);
}
@@ -194,13 +185,16 @@
OCMStub([mockSut fileIsInScope:OCMOCK_ANY]).andReturn(NO);
OCMExpect([self.mockConfigurator clientMode]).andReturn(CLIENTMODE_LOCKDOWN);
[self.sut validateBinaryWithPath:@"/a/file"
userName:@"nobody"
pid:@(24)
ppid:@(1)
vnodeId:1234];
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
forVnodeID:1234]);
}
- (void)testMissingShasum {
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postToKernelAction:ACTION_RESPOND_CHECKBW_ALLOW
forVnodeID:1234]);
}
@end