Skip to content

Commit

Permalink
santad: Fix transitive rules when using the sysx cache feature (#540)
Browse files Browse the repository at this point in the history
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/.
  • Loading branch information
russellhancox committed Mar 4, 2021
1 parent 78643d3 commit ad1868a
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 37 deletions.
5 changes: 4 additions & 1 deletion Source/common/SNTLogging.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
44 changes: 26 additions & 18 deletions Source/santad/EventProviders/SNTCachingEndpointSecurityManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#import "Source/common/SantaCache.h"
#import "Source/common/SNTLogging.h"

#include <atomic>
#include <bsm/libbsm.h>
#include <EndpointSecurity/EndpointSecurity.h>

uint64_t GetCurrentUptime() {
Expand All @@ -28,7 +28,6 @@ uint64_t GetCurrentUptime() {
}

@implementation SNTCachingEndpointSecurityManager {
std::atomic<bool> _compilerPIDs[PID_MAX];
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand All @@ -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,
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions Source/santad/EventProviders/SNTEndpointSecurityManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const pid_t PID_MAX = 99999;
@interface SNTEndpointSecurityManager : NSObject<SNTEventProvider>
- (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
55 changes: 38 additions & 17 deletions Source/santad/EventProviders/SNTEndpointSecurityManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
#include <libproc.h>


@interface SNTEndpointSecurityManager ()
@interface SNTEndpointSecurityManager () {
std::atomic<bool> _compilerPIDs[PID_MAX];
}

@property(nonatomic) SNTPrefixTree *prefixTree;
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
Expand All @@ -36,9 +38,7 @@ @interface SNTEndpointSecurityManager ()

@end

@implementation SNTEndpointSecurityManager {
std::atomic<bool> _compilerPIDs[PID_MAX];
}
@implementation SNTEndpointSecurityManager

- (instancetype)init API_AVAILABLE(macos(10.15)) {
self = [super init];
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<std::atomic<bool>>(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));
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion version.bzl
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""The version for all Santa components."""

SANTA_VERSION = "2021.2"
SANTA_VERSION = "2021.3"

0 comments on commit ad1868a

Please sign in to comment.