santa-driver: Split cache for root/non-root volume

Split the kernel-land cache into 2 separate caches, one for the root
volume and one for secondary volumes. When an unmount happens, clear
the non-root cache to ensure no overlap with filesystem IDs.
This commit is contained in:
Russell Hancox
2017-08-09 16:55:13 -04:00
committed by Russell Hancox
parent 0544011ee0
commit 616fd9570f
11 changed files with 84 additions and 39 deletions

View File

@@ -555,8 +555,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
NSData *d = [self.fileHandle readDataOfLength:range.length];
if (d.length != range.length) return nil;
return d;
}
@catch (NSException *e) {
} @catch (NSException *e) {
return nil;
}
}

View File

@@ -24,13 +24,13 @@
#include <IOKit/IOLib.h>
#ifdef DEBUG
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGD(format, ...) IOLog("D santa-driver: " format "\n", ##__VA_ARGS__);
#else // DEBUG
#define LOGD(...)
#define LOGD(format, ...)
#endif // DEBUG
#define LOGI(...) IOLog("I santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGW(...) IOLog("W santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGE(...) IOLog("E santa-driver: " __VA_ARGS__); IOLog("\n")
#define LOGI(format, ...) IOLog("I santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGW(format, ...) IOLog("W santa-driver: " format "\n", ##__VA_ARGS__);
#define LOGE(format, ...) IOLog("E santa-driver: " format "\n", ##__VA_ARGS__);
#else // KERNEL

View File

@@ -29,7 +29,8 @@ bool SantaDecisionManager::init() {
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
decision_cache_ = new SantaCache<uint64_t>(10000, 2);
root_decision_cache_ = new SantaCache<uint64_t>(5000, 2);
non_root_decision_cache_ = new SantaCache<uint64_t>(500, 2);
vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);
decision_dataqueue_ = IOSharedDataQueue::withEntries(
@@ -40,6 +41,12 @@ bool SantaDecisionManager::init() {
kMaxLogQueueEvents, sizeof(santa_message_t));
if (!log_dataqueue_) return kIOReturnNoMemory;
vfs_context_t ctx = vfs_context_create(NULL);
vnode_t root = vfs_rootvnode();
root_vsid_ = GetVnodeIDForVnode(ctx, root) >> 32;
vnode_put(root);
vfs_context_rele(ctx);
client_pid_ = 0;
ts_ = { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
@@ -49,7 +56,8 @@ bool SantaDecisionManager::init() {
}
void SantaDecisionManager::free() {
delete decision_cache_;
delete root_decision_cache_;
delete non_root_decision_cache_;
delete vnode_pid_map_;
if (decision_dataqueue_lock_) {
@@ -195,15 +203,28 @@ kern_return_t SantaDecisionManager::StopListener() {
#pragma mark Cache Management
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<uint64_t>* SantaDecisionManager::CacheForIdentifier(const uint64_t identifier) {
return (identifier >> 32 == root_vsid_) ? root_decision_cache_ : non_root_decision_cache_;
}
void SantaDecisionManager::AddToCache(
uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
auto decision_cache = CacheForIdentifier(identifier);
// If a previous entry was not found and the new entry is not `REQUEST_BINARY`, remove the
// existing entry. This is to prevent adding an ALLOW to the cache after a write has occurred.
if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
decision_cache_->remove(identifier);
if (decision_cache->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
decision_cache->remove(identifier);
}
if (unlikely(!identifier)) return;
@@ -211,17 +232,18 @@ void SantaDecisionManager::AddToCache(
}
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
decision_cache_->remove(identifier);
CacheForIdentifier(identifier)->remove(identifier);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
uint64_t SantaDecisionManager::CacheCount() const {
return decision_cache_->count();
return root_decision_cache_->count() + non_root_decision_cache_->count();
}
void SantaDecisionManager::ClearCache() {
decision_cache_->clear();
void SantaDecisionManager::ClearCache(bool non_root_only) {
if (!non_root_only) root_decision_cache_->clear();
non_root_decision_cache_->clear();
}
#pragma mark Decision Fetching
@@ -230,7 +252,9 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
uint64_t cache_val = decision_cache_->get(identifier);
auto decision_cache = CacheForIdentifier(identifier);
uint64_t cache_val = decision_cache->get(identifier);
if (cache_val == 0) return result;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
@@ -241,7 +265,7 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache_->remove(identifier);
decision_cache->remove(identifier);
return ACTION_UNSET;
}
}

View File

@@ -99,7 +99,7 @@ class SantaDecisionManager : public OSObject {
uint64_t CacheCount() const;
/// Clears the cache.
void ClearCache();
void ClearCache(bool non_root_only = true);
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
@@ -244,9 +244,22 @@ class SantaDecisionManager : public OSObject {
}
private:
SantaCache<uint64_t> *decision_cache_;
SantaCache<uint64_t> *root_decision_cache_;
SantaCache<uint64_t> *non_root_decision_cache_;
SantaCache<uint64_t> *vnode_pid_map_;
/**
Return the correct cache for a given identifier.
@param identifier The identifier
@return SantaCache* The cache to use
*/
SantaCache<uint64_t>* CacheForIdentifier(const uint64_t identifier);
// This is the file system ID of the root filesystem,
// used to determine which cache to use for requests
uint32_t root_vsid_;
lck_grp_t *sdm_lock_grp_;
lck_grp_attr_t *sdm_lock_grp_attr_;
lck_attr_t *sdm_lock_attr_;

View File

@@ -133,7 +133,7 @@ IOReturn SantaDriverClient::allow_binary(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
return kIOReturnSuccess;
@@ -144,7 +144,7 @@ IOReturn SantaDriverClient::deny_binary(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
return kIOReturnSuccess;
@@ -155,7 +155,8 @@ IOReturn SantaDriverClient::clear_cache(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
me->decisionManager->ClearCache();
const bool non_root_only = static_cast<const bool>(arguments->scalarInput[0]);
me->decisionManager->ClearCache(non_root_only);
return kIOReturnSuccess;
}
@@ -174,7 +175,7 @@ IOReturn SantaDriverClient::check_cache(
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
uint64_t input = *arguments->scalarInput;
const uint64_t input = static_cast<const uint64_t>(arguments->scalarInput[0]);
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(input);
return kIOReturnSuccess;
@@ -195,7 +196,7 @@ IOReturn SantaDriverClient::externalMethod(
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::clear_cache, 0, 0, 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 1, 0 },
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }
};

View File

@@ -115,7 +115,7 @@
origMode = newMode;
if (newMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCache];
[self.driverManager flushCacheNonRootOnly:NO];
}
}
@@ -253,20 +253,26 @@
void diskAppearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
[app.eventLog logDiskAppeared:props];
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
if (props[@"DAVolumePath"]) [app.eventLog logDiskAppeared:props];
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
[app.eventLog logDiskDisappeared:props];
[app.driverManager flushCacheNonRootOnly:YES];
}
@end

View File

@@ -62,7 +62,7 @@ double watchdogRAMPeak = 0;
}
- (void)flushCache:(void (^)(BOOL))reply {
reply([self.driverManager flushCache]);
reply([self.driverManager flushCacheNonRootOnly:NO]);
}
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply {
@@ -86,7 +86,7 @@ double watchdogRAMPeak = 0;
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", SNTRuleStateWhitelist];
if ([rules filteredArrayUsingPredicate:p].count || cleanSlate) {
LOGI(@"Received non-whitelist rule, flushing cache");
[self.driverManager flushCache];
[self.driverManager flushCacheNonRootOnly:NO];
}
reply(error);
@@ -170,7 +170,7 @@ double watchdogRAMPeak = 0;
error:NULL];
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
LOGI(@"Received new whitelist regex, flushing cache");
[self.driverManager flushCache];
[self.driverManager flushCacheNonRootOnly:NO];
reply();
}
@@ -180,7 +180,7 @@ double watchdogRAMPeak = 0;
error:NULL];
[[SNTConfigurator configurator] setBlacklistPathRegex:re];
LOGI(@"Received new blacklist regex, flushing cache");
[self.driverManager flushCache];
[self.driverManager flushCacheNonRootOnly:NO];
reply();
}

View File

@@ -55,11 +55,11 @@
///
/// Flush the kernel's binary cache.
///
- (BOOL)flushCache;
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly;
///
/// Check the kernel cache for a VnodeID
///
-(santa_action_t)checkCache:(uint64_t)vnodeID;
- (santa_action_t)checkCache:(uint64_t)vnodeID;
@end

View File

@@ -180,9 +180,14 @@ static const int MAX_DELAY = 15;
return cache_count;
}
- (BOOL)flushCache {
- (BOOL)flushCacheNonRootOnly:(BOOL)nonRootOnly {
const uint64_t nonRoot = nonRootOnly;
return IOConnectCallScalarMethod(_connection,
kSantaUserClientClearCache, 0, 0, 0, 0) == KERN_SUCCESS;
kSantaUserClientClearCache,
&nonRoot,
1,
0,
0) == KERN_SUCCESS;
}
- (santa_action_t)checkCache:(uint64_t)vnodeID {

View File

@@ -216,8 +216,6 @@
}
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
if (![diskProperties[@"DAVolumeMountable"] boolValue]) return;
NSString *dmgPath = @"";
NSString *serial = @"";
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
@@ -252,8 +250,6 @@
}
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
if (![diskProperties[@"DAVolumeMountable"] boolValue]) return;
LOGI(@"action=DISKDISAPPEAR|mount=%@|volume=%@|bsdname=%@",
[diskProperties[@"DAVolumePath"] path] ?: @"",
diskProperties[@"DAVolumeName"] ?: @"",

View File

@@ -104,7 +104,8 @@
/// Call in-kernel function: |kSantaUserClientClearCache|
- (void)flushCache {
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, 0, 0, 0, 0);
uint64_t nonRootOnly = 0;
IOConnectCallScalarMethod(self.connection, kSantaUserClientClearCache, &nonRootOnly, 1, 0, 0);
}
#pragma mark - Connection Tests