From ad1868a50fb88b7741e596f6ddf26843921edd27 Mon Sep 17 00:00:00 2001 From: Russell Hancox Date: Thu, 4 Mar 2021 09:47:32 -0500 Subject: [PATCH] santad: Fix transitive rules when using the sysx cache feature (#540) This fixes transitive allowlisting when `EnableSysxCache` is turned on, reduces the deadline timer to fire 5s before the ES deadline, remaps our DEBUG logs to NOTICE so they can be more easily seen in Console and prevents transitive rules being created for paths under /dev/. --- Source/common/SNTLogging.m | 5 +- .../SNTCachingEndpointSecurityManager.mm | 44 +++++++++------ .../SNTEndpointSecurityManager.h | 4 ++ .../SNTEndpointSecurityManager.mm | 55 +++++++++++++------ version.bzl | 2 +- 5 files changed, 73 insertions(+), 37 deletions(-) diff --git a/Source/common/SNTLogging.m b/Source/common/SNTLogging.m index 4d88bc4e6..262a97aac 100644 --- a/Source/common/SNTLogging.m +++ b/Source/common/SNTLogging.m @@ -88,7 +88,10 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) { break; case LOG_LEVEL_DEBUG: levelName = "D"; - syslogLevel = ASL_LEVEL_DEBUG; + // Log debug messages at the same ASL level as INFO. + // While it would make sense to use DEBUG, watching debug-level logs + // in Console means enabling all debug logs, which is absurdly noisy. + syslogLevel = ASL_LEVEL_NOTICE; break; } diff --git a/Source/santad/EventProviders/SNTCachingEndpointSecurityManager.mm b/Source/santad/EventProviders/SNTCachingEndpointSecurityManager.mm index 18d711196..412cf6688 100644 --- a/Source/santad/EventProviders/SNTCachingEndpointSecurityManager.mm +++ b/Source/santad/EventProviders/SNTCachingEndpointSecurityManager.mm @@ -17,7 +17,7 @@ #import "Source/common/SantaCache.h" #import "Source/common/SNTLogging.h" -#include +#include #include uint64_t GetCurrentUptime() { @@ -28,7 +28,6 @@ uint64_t GetCurrentUptime() { } @implementation SNTCachingEndpointSecurityManager { - std::atomic _compilerPIDs[PID_MAX]; SantaCache *_decisionCache; } @@ -56,10 +55,20 @@ - (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) { // If item is in cache but hasn't received a response yet, sleep for a bit. // If item is not in cache, break out of loop and forward request to callback. if (RESPONSE_VALID(return_action)) { - if (return_action == ACTION_RESPOND_ALLOW) { - es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true); - } else { - es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false); + switch (return_action) { + case ACTION_RESPOND_ALLOW: + es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true); + break; + case ACTION_RESPOND_ALLOW_COMPILER: { + pid_t pid = audit_token_to_pid(m->process->audit_token); + [self setIsCompilerPID:pid]; + // Don't let ES cache compilers + es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false); + break; + } + default: + es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false); + break; } return YES; } else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) { @@ -70,7 +79,7 @@ - (BOOL)respondFromCache:(es_message_t *)m API_AVAILABLE(macos(10.15)) { } } - [self addToCache:vnode_id decision:ACTION_REQUEST_BINARY timeout:0]; + [self addToCache:vnode_id decision:ACTION_REQUEST_BINARY currentTicks:0]; return NO; } @@ -79,25 +88,24 @@ - (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm es_respond_result_t ret; switch (action) { case ACTION_RESPOND_ALLOW_COMPILER: - if (sm.pid >= PID_MAX) { - LOGE(@"Unable to watch compiler pid=%d >= pid_max=%d", sm.pid, PID_MAX); - } else { - LOGD(@"Watching compiler pid=%d path=%s", sm.pid, sm.path); - self->_compilerPIDs[sm.pid].store(true); - } - // Allow the exec, but don't cache the decision so subsequent execs of the compiler get - // marked appropriately. + [self setIsCompilerPID:sm.pid]; + + // Allow the exec and cache in our internal cache but don't let ES cache, because then + // we won't see future execs of the compiler in order to record the PID. + [self addToCache:sm.vnode_id + decision:ACTION_RESPOND_ALLOW_COMPILER + currentTicks:GetCurrentUptime()]; ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message, ES_AUTH_RESULT_ALLOW, false); break; case ACTION_RESPOND_ALLOW: case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE: - [self addToCache:sm.vnode_id decision:ACTION_RESPOND_ALLOW timeout:GetCurrentUptime()]; + [self addToCache:sm.vnode_id decision:ACTION_RESPOND_ALLOW currentTicks:GetCurrentUptime()]; ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message, ES_AUTH_RESULT_ALLOW, true); break; case ACTION_RESPOND_DENY: - [self addToCache:sm.vnode_id decision:ACTION_RESPOND_DENY timeout:GetCurrentUptime()]; + [self addToCache:sm.vnode_id decision:ACTION_RESPOND_DENY currentTicks:GetCurrentUptime()]; OS_FALLTHROUGH; case ACTION_RESPOND_TOOLONG: ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message, @@ -114,7 +122,7 @@ - (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm - (void)addToCache:(santa_vnode_id_t)identifier decision:(santa_action_t)decision - timeout:(uint64_t)microsecs { + currentTicks:(uint64_t)microsecs { switch (decision) { case ACTION_REQUEST_BINARY: _decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0); diff --git a/Source/santad/EventProviders/SNTEndpointSecurityManager.h b/Source/santad/EventProviders/SNTEndpointSecurityManager.h index c924449d8..2fcfcab49 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityManager.h +++ b/Source/santad/EventProviders/SNTEndpointSecurityManager.h @@ -25,6 +25,10 @@ const pid_t PID_MAX = 99999; @interface SNTEndpointSecurityManager : NSObject - (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file; +- (BOOL)isCompilerPID:(pid_t)pid; +- (void)setIsCompilerPID:(pid_t)pid; +- (void)setNotCompilerPID:(pid_t)pid; + @property(readonly, nonatomic) es_client_t *client; @end diff --git a/Source/santad/EventProviders/SNTEndpointSecurityManager.mm b/Source/santad/EventProviders/SNTEndpointSecurityManager.mm index 3a8048228..601d69abe 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityManager.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityManager.mm @@ -25,7 +25,9 @@ #include -@interface SNTEndpointSecurityManager () +@interface SNTEndpointSecurityManager () { + std::atomic _compilerPIDs[PID_MAX]; +} @property(nonatomic) SNTPrefixTree *prefixTree; @property(nonatomic, copy) void (^decisionCallback)(santa_message_t); @@ -36,9 +38,7 @@ @interface SNTEndpointSecurityManager () @end -@implementation SNTEndpointSecurityManager { - std::atomic _compilerPIDs[PID_MAX]; -} +@implementation SNTEndpointSecurityManager - (instancetype)init API_AVAILABLE(macos(10.15)) { self = [super init]; @@ -107,7 +107,7 @@ - (void)establishClient API_AVAILABLE(macos(10.15)) { [self removeCacheEntryForVnodeID:[self vnodeIDForFile:m->event.close.target]]; // Create a transitive rule if the file was modified by a running compiler - if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) { + if ([self isCompilerPID:pid]) { santa_message_t sm = {}; BOOL truncated = [self populateBufferFromESFile:m->event.close.target buffer:sm.path @@ -117,6 +117,9 @@ - (void)establishClient API_AVAILABLE(macos(10.15)) { sm.path, pid); break; } + if ([@(sm.path) hasPrefix:@"/dev/"]) { + break; + } sm.action = ACTION_NOTIFY_WHITELIST; sm.pid = pid; sm.pidversion = pidversion; @@ -129,7 +132,7 @@ - (void)establishClient API_AVAILABLE(macos(10.15)) { } case ES_EVENT_TYPE_NOTIFY_RENAME: { // Create a transitive rule if the file was renamed by a running compiler - if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) { + if ([self isCompilerPID:pid]) { santa_message_t sm = {}; BOOL truncated = [self populateRenamedNewPathFromESMessage:m->event.rename buffer:sm.path @@ -139,6 +142,9 @@ - (void)establishClient API_AVAILABLE(macos(10.15)) { sm.path, pid); break; } + if ([@(sm.path) hasPrefix:@"/dev/"]) { + break; + } sm.action = ACTION_NOTIFY_WHITELIST; sm.pid = pid; sm.pidversion = pidversion; @@ -151,7 +157,7 @@ - (void)establishClient API_AVAILABLE(macos(10.15)) { } case ES_EVENT_TYPE_NOTIFY_EXIT: { // Update the set of running compiler PIDs - if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false); + [self setNotCompilerPID:pid]; // Skip the standard pipeline and just log. if (![config enableForkAndExitLogging]) return; @@ -191,12 +197,12 @@ - (void)establishClient API_AVAILABLE(macos(10.15)) { switch (m->action_type) { case ES_ACTION_TYPE_AUTH: { - // Create a timer to deny the execution 2 seconds before the deadline, + // Create a timer to deny the execution 5 seconds before the deadline, // if a response hasn't already been sent. This block will still be enqueued if - // the the deadline - 2 secs is < DISPATCH_TIME_NOW. - // As of 10.15.2, a typical deadline is 60 seconds. + // the the deadline - 5 secs is < DISPATCH_TIME_NOW. + // As of 10.15.5, a typical deadline is 60 seconds. auto responded = std::make_shared>(false); - dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -2), self.esAuthQueue, ^(void) { + dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) { if (responded->load()) return; LOGE(@"Deadline reached: deny pid=%d ret=%d", pid, es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false)); @@ -446,12 +452,8 @@ - (int)postAction:(santa_action_t)action forMessage:(santa_message_t)sm es_respond_result_t ret; switch (action) { case ACTION_RESPOND_ALLOW_COMPILER: - if (sm.pid >= PID_MAX) { - LOGE(@"Unable to watch compiler pid=%d >= pid_max=%d", sm.pid, PID_MAX); - } else { - LOGD(@"Watching compiler pid=%d path=%s", sm.pid, sm.path); - self->_compilerPIDs[sm.pid].store(true); - } + [self setIsCompilerPID:sm.pid]; + // Allow the exec, but don't cache the decision so subsequent execs of the compiler get // marked appropriately. ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message, @@ -564,4 +566,23 @@ - (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file { }; } +- (BOOL)isCompilerPID:(pid_t)pid { + return (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()); +} + +- (void)setIsCompilerPID:(pid_t)pid { + if (pid < 1) { + LOGE(@"Unable to watch compiler pid=%d", pid); + } else if (pid >= PID_MAX) { + LOGE(@"Unable to watch compiler pid=%d >= PID_MAX(%d)", pid, PID_MAX); + } else { + self->_compilerPIDs[pid].store(true); + LOGD(@"Watching compiler pid=%d", pid); + } +} + +- (void)setNotCompilerPID:(pid_t)pid { + if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false); +} + @end diff --git a/version.bzl b/version.bzl index 76fd1c148..9c7fd17be 100644 --- a/version.bzl +++ b/version.bzl @@ -1,3 +1,3 @@ """The version for all Santa components.""" -SANTA_VERSION = "2021.2" +SANTA_VERSION = "2021.3"