Skip to content
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

Make FileChangesRegex apply to all file change event types #1294

Merged
merged 4 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 40 additions & 19 deletions Source/santad/EventProviders/SNTEndpointSecurityRecorder.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
/// limitations under the License.

#import "Source/santad/EventProviders/SNTEndpointSecurityRecorder.h"
#include <os/base.h>

#include <EndpointSecurity/EndpointSecurity.h>

Expand All @@ -37,6 +38,7 @@
es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
switch (msg->event_type) {
case ES_EVENT_TYPE_NOTIFY_CLOSE: return msg->event.close.target;
case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA: return msg->event.exchangedata.file1;
case ES_EVENT_TYPE_NOTIFY_LINK: return msg->event.link.source;
case ES_EVENT_TYPE_NOTIFY_RENAME: return msg->event.rename.source;
case ES_EVENT_TYPE_NOTIFY_UNLINK: return msg->event.unlink.target;
Expand Down Expand Up @@ -91,10 +93,10 @@ - (void)handleMessage:(Message &&)esMsg
BOOL shouldLogClose = esMsg->event.close.modified;

#if HAVE_MACOS_13
if (@available(macOS 13.5, *)) {
if (esMsg->version >= 6) {
// As of macSO 13.0 we have a new field for if a file was mmaped with
// write permissions on close events. However it did not work until
// 13.5.
// write permissions on close events. However due to a bug in ES, it
// only worked for certain conditions until macOS 13.5 (FB12094635).
//
// If something was mmaped writable it was probably written to. Often
// developer tools do this to avoid lots of write syscalls, e.g. go's
Expand All @@ -114,8 +116,28 @@ - (void)handleMessage:(Message &&)esMsg

self->_authResultCache->RemoveFromCache(esMsg->event.close.target);

break;
}

default: break;
}

[self.compilerController handleEvent:esMsg withLogger:self->_logger];

switch (esMsg->event_type) {
case ES_EVENT_TYPE_NOTIFY_CLOSE: OS_FALLTHROUGH;
case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA: OS_FALLTHROUGH;
case ES_EVENT_TYPE_NOTIFY_LINK: OS_FALLTHROUGH;
case ES_EVENT_TYPE_NOTIFY_RENAME: OS_FALLTHROUGH;
case ES_EVENT_TYPE_NOTIFY_UNLINK: {
es_file_t *targetFile = GetTargetFileForPrefixTree(&(*esMsg));

if (!targetFile) {
break;
}

// Only log file changes that match the given regex
NSString *targetPath = santa::common::StringToNSString(esMsg->event.close.target->path.data);
NSString *targetPath = santa::common::StringToNSString(targetFile->path.data);
if (![[self.configurator fileChangesRegex]
numberOfMatchesInString:targetPath
options:0
Expand All @@ -127,25 +149,24 @@ - (void)handleMessage:(Message &&)esMsg
return;
}

if (self->_prefixTree->HasPrefix(targetFile->path.data)) {
recordEventMetrics(EventDisposition::kDropped);
return;
}

break;
}
default: break;
}

[self.compilerController handleEvent:esMsg withLogger:self->_logger];

if ((esMsg->event_type == ES_EVENT_TYPE_NOTIFY_FORK ||
esMsg->event_type == ES_EVENT_TYPE_NOTIFY_EXIT) &&
self.configurator.enableForkAndExitLogging == NO) {
recordEventMetrics(EventDisposition::kDropped);
return;
}
case ES_EVENT_TYPE_NOTIFY_FORK: OS_FALLTHROUGH;
case ES_EVENT_TYPE_NOTIFY_EXIT: {
if (self.configurator.enableForkAndExitLogging == NO) {
recordEventMetrics(EventDisposition::kDropped);
return;
}
break;
}

// Filter file op events matching the prefix tree.
es_file_t *targetFile = GetTargetFileForPrefixTree(&(*esMsg));
if (targetFile != NULL && self->_prefixTree->HasPrefix(targetFile->path.data)) {
recordEventMetrics(EventDisposition::kDropped);
return;
default: break;
}

// Enrich the message inline with the ES handler block to capture enrichment
Expand Down
53 changes: 47 additions & 6 deletions Source/santad/EventProviders/SNTEndpointSecurityRecorderTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ - (void)testEnable {
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}

typedef void (^testHelperBlock)(es_message_t *message,
typedef void (^TestHelperBlock)(es_message_t *message,
std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
SNTEndpointSecurityRecorder *recorderClient,
std::shared_ptr<PrefixTree<Unit>> prefixTree,
Expand All @@ -114,7 +114,7 @@ typedef void (^testHelperBlock)(es_message_t *message,

- (void)handleMessageShouldLog:(BOOL)shouldLog
shouldRemoveFromCache:(BOOL)shouldRemoveFromCache
withBlock:(testHelperBlock)testBlock {
withBlock:(TestHelperBlock)testBlock {
es_file_t file = MakeESFile("foo");
es_process_t proc = MakeESProcess(&file);
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_NOTIFY_CLOSE, &proc, ActionType::Auth);
Expand Down Expand Up @@ -176,7 +176,7 @@ - (void)testHandleMessageWithCloseMappedWriteable {
if (@available(macOS 13.0, *)) {
// CLOSE not modified, but was_mapped_writable, should remove from cache,
// and matches fileChangesRegex
testHelperBlock testBlock =
TestHelperBlock testBlock =
^(es_message_t *esMsg, std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
SNTEndpointSecurityRecorder *recorderClient, std::shared_ptr<PrefixTree<Unit>> prefixTree,
__autoreleasing dispatch_semaphore_t *sema,
Expand Down Expand Up @@ -208,7 +208,7 @@ - (void)testHandleEventCloseNotModifiedWithWasMappedWritable {
if (@available(macOS 13.0, *)) {
// CLOSE not modified, but was_mapped_writable, remove from cache, and does not match
// fileChangesRegex
testHelperBlock testBlock =
TestHelperBlock testBlock =
^(es_message_t *esMsg, std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
SNTEndpointSecurityRecorder *recorderClient, std::shared_ptr<PrefixTree<Unit>> prefixTree,
__autoreleasing dispatch_semaphore_t *sema,
Expand All @@ -232,7 +232,7 @@ - (void)testHandleEventCloseNotModifiedWithWasMappedWritable {

- (void)testHandleMessage {
// CLOSE not modified, bail early
testHelperBlock testBlock = ^(
TestHelperBlock testBlock = ^(
es_message_t *esMsg, std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
SNTEndpointSecurityRecorder *recorderClient, std::shared_ptr<PrefixTree<Unit>> prefixTree,
__autoreleasing dispatch_semaphore_t *sema, __autoreleasing dispatch_semaphore_t *semaMetrics) {
Expand Down Expand Up @@ -281,6 +281,7 @@ - (void)testHandleMessage {
esMsg->event.close.modified = true;
esMsg->event.close.target = &targetFileMissesRegex;
Message msg(mockESApi, esMsg);
OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs();
XCTAssertNoThrow([recorderClient handleMessage:Message(mockESApi, esMsg)
recordEventMetrics:^(EventDisposition d) {
XCTFail("Metrics record callback should not be called here");
Expand All @@ -289,6 +290,44 @@ - (void)testHandleMessage {

[self handleMessageShouldLog:NO shouldRemoveFromCache:YES withBlock:testBlock];

// UNLINK, remove from cache, but doesn't match fileChangesRegex
testBlock = ^(
es_message_t *esMsg, std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
SNTEndpointSecurityRecorder *recorderClient, std::shared_ptr<PrefixTree<Unit>> prefixTree,
__autoreleasing dispatch_semaphore_t *sema, __autoreleasing dispatch_semaphore_t *semaMetrics) {
esMsg->event_type = ES_EVENT_TYPE_NOTIFY_UNLINK;
esMsg->event.unlink.target = &targetFileMissesRegex;
Message msg(mockESApi, esMsg);
OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs();
XCTAssertNoThrow([recorderClient handleMessage:Message(mockESApi, esMsg)
recordEventMetrics:^(EventDisposition d) {
XCTFail("Metrics record callback should not be called here");
}]);
};

[self handleMessageShouldLog:NO shouldRemoveFromCache:NO withBlock:testBlock];

// EXCHANGEDATA, Prefix match, bail early
testBlock = ^(
es_message_t *esMsg, std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
SNTEndpointSecurityRecorder *recorderClient, std::shared_ptr<PrefixTree<Unit>> prefixTree,
__autoreleasing dispatch_semaphore_t *sema, __autoreleasing dispatch_semaphore_t *semaMetrics) {
esMsg->event_type = ES_EVENT_TYPE_NOTIFY_UNLINK;
esMsg->event.exchangedata.file1 = &targetFileMatchesRegex;
prefixTree->InsertPrefix(esMsg->event.exchangedata.file1->path.data, Unit{});
Message msg(mockESApi, esMsg);
OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs();
XCTAssertNoThrow([recorderClient handleMessage:Message(mockESApi, esMsg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, EventDisposition::kDropped);
dispatch_semaphore_signal(*semaMetrics);
}]);

XCTAssertSemaTrue(*semaMetrics, 5, "Metrics not recorded within expected window");
};

[self handleMessageShouldLog:NO shouldRemoveFromCache:NO withBlock:testBlock];

// LINK, Prefix match, bail early
testBlock =
^(es_message_t *esMsg, std::shared_ptr<MockEndpointSecurityAPI> mockESApi, id mockCC,
Expand Down Expand Up @@ -371,6 +410,7 @@ - (void)testGetTargetFileForPrefixTree {
extern es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg);

es_file_t closeFile = MakeESFile("close");
es_file_t exchangedataFile = MakeESFile("exchangedata");
es_file_t linkFile = MakeESFile("link");
es_file_t renameFile = MakeESFile("rename");
es_file_t unlinkFile = MakeESFile("unlink");
Expand All @@ -393,7 +433,8 @@ - (void)testGetTargetFileForPrefixTree {
XCTAssertEqual(GetTargetFileForPrefixTree(&esMsg), &unlinkFile);

esMsg.event_type = ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA;
XCTAssertEqual(GetTargetFileForPrefixTree(&esMsg), nullptr);
esMsg.event.exchangedata.file1 = &exchangedataFile;
XCTAssertEqual(GetTargetFileForPrefixTree(&esMsg), &exchangedataFile);

esMsg.event_type = ES_EVENT_TYPE_NOTIFY_EXEC;
XCTAssertEqual(GetTargetFileForPrefixTree(&esMsg), nullptr);
Expand Down
Loading