-
Notifications
You must be signed in to change notification settings - Fork 298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add transitive whitelisting to Santa #224
Changes from 4 commits
8f972eb
48b656d
1a9d07b
0d77a2c
a594175
881322f
7a32d31
0f9d63a
163ac9b
47a981e
16092cd
0b106e4
9c4c7fa
6348fd1
04d8f41
fef9351
0e840d2
c4b47e1
43ce8aa
83649ee
079a6d3
dc10e23
d35f0b5
6cb4ad5
aa01a79
a08e7f8
88b0972
0cb3d6a
1cd9551
8f82370
1b687a1
d2df097
340f59d
b9c7585
c293d5d
a9201dd
91c5d71
e583712
75de096
6d295c9
ca00a28
e0bf6dd
16769e8
93a7e86
bba1aee
21561ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,13 @@ | |
|
||
#include "SantaDecisionManager.h" | ||
|
||
// This is a made-up KAUTH_FILEOP constant which represents a KAUTH_VNODE_WRITE_DATA event | ||
// that gets passed to SantaDecisionManager's FileOpCallback method. It is defined as 8 | ||
// because that was the first unused integer from sys/kauth.h. The reason that we don't | ||
// simply use the KAUTH_VNODE_WRITE_DATA constant as is is because it overlaps with the other | ||
// KAUTH_FILEOP constants. | ||
#define KAUTH_FILEOP_WRITE 8 | ||
|
||
#define super OSObject | ||
OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject); | ||
|
||
|
@@ -28,6 +35,7 @@ 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_); | ||
compiler_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_); | ||
|
||
root_decision_cache_ = new SantaCache<uint64_t>(5000, 2); | ||
non_root_decision_cache_ = new SantaCache<uint64_t>(500, 2); | ||
|
@@ -41,6 +49,10 @@ bool SantaDecisionManager::init() { | |
kMaxLogQueueEvents, sizeof(santa_message_t)); | ||
if (!log_dataqueue_) return kIOReturnNoMemory; | ||
|
||
compiler_dataqueue_ = IOSharedDataQueue::withEntries( | ||
kMaxCompilerQueueEvents, sizeof(santa_message_t)); | ||
if (!compiler_dataqueue_) return kIOReturnNoMemory; | ||
|
||
client_pid_ = 0; | ||
root_fsid_ = 0; | ||
|
||
|
@@ -65,6 +77,11 @@ void SantaDecisionManager::free() { | |
log_dataqueue_lock_ = nullptr; | ||
} | ||
|
||
if (compiler_dataqueue_lock_) { | ||
lck_mtx_free(compiler_dataqueue_lock_, sdm_lock_grp_); | ||
compiler_dataqueue_lock_ = nullptr; | ||
} | ||
|
||
if (sdm_lock_attr_) { | ||
lck_attr_free(sdm_lock_attr_); | ||
sdm_lock_attr_ = nullptr; | ||
|
@@ -82,6 +99,7 @@ void SantaDecisionManager::free() { | |
|
||
OSSafeReleaseNULL(decision_dataqueue_); | ||
OSSafeReleaseNULL(log_dataqueue_); | ||
OSSafeReleaseNULL(compiler_dataqueue_); | ||
|
||
super::free(); | ||
} | ||
|
@@ -110,6 +128,7 @@ void SantaDecisionManager::ConnectClient(pid_t pid) { | |
|
||
failed_decision_queue_requests_ = 0; | ||
failed_log_queue_requests_ = 0; | ||
failed_compiler_queue_requests_ = 0; | ||
} | ||
|
||
void SantaDecisionManager::DisconnectClient(bool itDied) { | ||
|
@@ -137,6 +156,12 @@ void SantaDecisionManager::DisconnectClient(bool itDied) { | |
log_dataqueue_ = IOSharedDataQueue::withEntries( | ||
kMaxLogQueueEvents, sizeof(santa_message_t)); | ||
lck_mtx_unlock(log_dataqueue_lock_); | ||
|
||
lck_mtx_lock(compiler_dataqueue_lock_); | ||
compiler_dataqueue_->release(); | ||
compiler_dataqueue_ = IOSharedDataQueue::withEntries( | ||
kMaxCompilerQueueEvents, sizeof(santa_message_t)); | ||
lck_mtx_unlock(compiler_dataqueue_lock_); | ||
} | ||
} | ||
|
||
|
@@ -163,6 +188,12 @@ void SantaDecisionManager::SetLogPort(mach_port_t port) { | |
lck_mtx_unlock(log_dataqueue_lock_); | ||
} | ||
|
||
void SantaDecisionManager::SetCompilerPort(mach_port_t port) { | ||
lck_mtx_lock(compiler_dataqueue_lock_); | ||
compiler_dataqueue_->setNotificationPort(port); | ||
lck_mtx_unlock(compiler_dataqueue_lock_); | ||
} | ||
|
||
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const { | ||
return decision_dataqueue_->getMemoryDescriptor(); | ||
} | ||
|
@@ -171,6 +202,10 @@ IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const { | |
return log_dataqueue_->getMemoryDescriptor(); | ||
} | ||
|
||
IOMemoryDescriptor *SantaDecisionManager::GetCompilerMemoryDescriptor() const { | ||
return compiler_dataqueue_->getMemoryDescriptor(); | ||
} | ||
|
||
#pragma mark Listener Control | ||
|
||
kern_return_t SantaDecisionManager::StartListener() { | ||
|
@@ -417,6 +452,26 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) { | |
return kr; | ||
} | ||
|
||
bool SantaDecisionManager::PostToCompilerQueue(santa_message_t *message) { | ||
lck_mtx_lock(compiler_dataqueue_lock_); | ||
auto kr = compiler_dataqueue_->enqueue(message, sizeof(santa_message_t)); | ||
if (!kr) { | ||
if (failed_compiler_queue_requests_++ == 0) { | ||
LOGW("Dropping compiler queue messages"); | ||
} | ||
// If enqueue failed, pop an item off the queue and try again. | ||
uint32_t dataSize = 0; | ||
compiler_dataqueue_->dequeue(0, &dataSize); | ||
kr = compiler_dataqueue_->enqueue(message, sizeof(santa_message_t)); | ||
} else { | ||
if (failed_compiler_queue_requests_ > 0) { | ||
failed_compiler_queue_requests_--; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can remove this pop and re-queue bit, it's unnecessary. |
||
} | ||
lck_mtx_unlock(compiler_dataqueue_lock_); | ||
return kr; | ||
} | ||
|
||
#pragma mark Invocation Tracking & PID comparison | ||
|
||
void SantaDecisionManager::IncrementListenerInvocations() { | ||
|
@@ -495,6 +550,7 @@ void SantaDecisionManager::FileOpCallback( | |
message->ppid = (val & ~0xFFFFFFFF00000000); | ||
} | ||
PostToLogQueue(message); | ||
PostToCompilerQueue(message); | ||
delete message; | ||
return; | ||
} | ||
|
@@ -509,9 +565,13 @@ void SantaDecisionManager::FileOpCallback( | |
proc_name(message->pid, message->pname, sizeof(message->pname)); | ||
|
||
switch (action) { | ||
case KAUTH_FILEOP_CLOSE: | ||
case KAUTH_FILEOP_WRITE: | ||
// This is actually a KAUTH_VNODE_WRITE_DATA event. | ||
message->action = ACTION_NOTIFY_WRITE; | ||
break; | ||
case KAUTH_FILEOP_CLOSE: | ||
message->action = ACTION_NOTIFY_CLOSE; | ||
break; | ||
case KAUTH_FILEOP_RENAME: | ||
message->action = ACTION_NOTIFY_RENAME; | ||
break; | ||
|
@@ -529,7 +589,16 @@ void SantaDecisionManager::FileOpCallback( | |
return; | ||
} | ||
|
||
PostToLogQueue(message); | ||
// We don't log the ACTION_NOTIFY_CLOSE messages because they mostly duplicate the | ||
// ACTION_NOTIFY_WRITE messages (though they aren't precisely the same) | ||
if (message->action != ACTION_NOTIFY_CLOSE) { | ||
PostToLogQueue(message); | ||
} | ||
|
||
if (message->action == ACTION_NOTIFY_CLOSE || message->action == ACTION_NOTIFY_RENAME) { | ||
PostToCompilerQueue(message); | ||
} | ||
|
||
delete message; | ||
} | ||
} | ||
|
@@ -552,6 +621,10 @@ extern "C" int fileop_scope_callback( | |
char *new_path = nullptr; | ||
|
||
switch (action) { | ||
// We only care about KAUTH_FILEOP_CLOSE events where the closed file was modified. | ||
case KAUTH_FILEOP_CLOSE: | ||
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED)) | ||
return KAUTH_RESULT_DEFER; | ||
case KAUTH_FILEOP_DELETE: | ||
case KAUTH_FILEOP_EXEC: | ||
vp = reinterpret_cast<vnode_t>(arg0); | ||
|
@@ -604,7 +677,9 @@ extern "C" int vnode_scope_callback( | |
char path[MAXPATHLEN]; | ||
int pathlen = MAXPATHLEN; | ||
vn_getpath(vp, path, &pathlen); | ||
sdm->FileOpCallback(KAUTH_FILEOP_CLOSE, vp, path, nullptr); | ||
// KAUTH_VNODE_WRITE_DATA events are translated into a fake KAUTH_FILEOP_WRITE event | ||
// so that we can handle them in the FileOpCallback function. | ||
sdm->FileOpCallback(KAUTH_FILEOP_WRITE, vp, path, nullptr); | ||
sdm->DecrementListenerInvocations(); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ + (NSString *)longHelpText { | |
@" --whitelist: add to whitelist\n" | ||
@" --blacklist: add to blacklist\n" | ||
@" --silent-blacklist: add to silent blacklist\n" | ||
@" --compiler: whitelist and mark as a compiler\n" | ||
@" --remove: remove existing rule\n" | ||
@" --check: check for an existing rule\n" | ||
@"\n" | ||
|
@@ -61,15 +62,21 @@ + (NSString *)longHelpText { | |
@" Does not work with --check. Use the fileinfo verb to check.\n" | ||
@" the rule state of a file.\n" | ||
@" --sha256 {sha256}: hash to add/remove/check\n" | ||
@" --type {ruletype}: type of rules to remove\n" | ||
@"\n" | ||
@" Optionally:\n" | ||
@" --certificate: add or check a certificate sha256 rule instead of binary\n" | ||
@" --message {message}: custom message\n"); | ||
@" --message {message}: custom message\n" | ||
@" --force: allow manual changes even when SyncBaseUrl is set\n"); | ||
} | ||
|
||
- (void)runWithArguments:(NSArray *)arguments { | ||
SNTConfigurator *config = [SNTConfigurator configurator]; | ||
if ([config syncBaseURL] && ![arguments containsObject:@"--check"]) { | ||
// TODO: --force flag was added so that we could manually add compiler rules during testing. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can add the --force flag in an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
// It's possibly a bad idea and should be removed. | ||
if ([config syncBaseURL] && | ||
![arguments containsObject:@"--check"] && | ||
![arguments containsObject:@"--force"]) { | ||
printf("SyncBaseURL is set, rules are managed centrally.\n"); | ||
exit(1); | ||
} | ||
|
@@ -91,6 +98,8 @@ - (void)runWithArguments:(NSArray *)arguments { | |
newRule.state = SNTRuleStateBlacklist; | ||
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) { | ||
newRule.state = SNTRuleStateSilentBlacklist; | ||
} else if ([arg caseInsensitiveCompare:@"--compiler"] == NSOrderedSame) { | ||
newRule.state = SNTRuleStateWhitelistCompiler; | ||
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) { | ||
newRule.state = SNTRuleStateRemove; | ||
} else if ([arg caseInsensitiveCompare:@"--check"] == NSOrderedSame) { | ||
|
@@ -115,6 +124,12 @@ - (void)runWithArguments:(NSArray *)arguments { | |
[self printErrorUsageAndExit:@"--message requires an argument"]; | ||
} | ||
newRule.customMsg = arguments[i]; | ||
} else if ([arg caseInsensitiveCompare:@"--type"] == NSOrderedSame) { | ||
if (++i > arguments.count - 1) { | ||
[self printErrorUsageAndExit:@"--type requires an argument"]; | ||
} | ||
} else if ([arg caseInsensitiveCompare:@"--force"] == NSOrderedSame) { | ||
// Don't do anything special. | ||
} else { | ||
[self printErrorUsageAndExit:[@"Unknown argument: " stringByAppendingString:arg]]; | ||
} | ||
|
@@ -191,6 +206,12 @@ - (void)printStateOfRule:(SNTRule *)rule daemonConnection:(SNTXPCConnection *)da | |
case SNTEventStateBlockScope: | ||
[output appendString:@" (Scope)"]; | ||
break; | ||
case SNTEventStateAllowCompiler: | ||
[output appendString:@" (Compiler)"]; | ||
break; | ||
case SNTEventStateAllowTransitive: | ||
[output appendString:@" (Transitive)"]; | ||
break; | ||
default: | ||
output = @"None".mutableCopy; | ||
break; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is still needed we should give the actions used by KAUTH_SCOPE_FILEOP room to grow. I don't really like the idea of adding our own action, but if we need to make it more than 8 incase fileop adds a new action in the future.
Maybe KAUTH_FILEOP_DELETE + 32 or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.