Skip to content

Commit

Permalink
Merge 5454a8c into 42eb0a3
Browse files Browse the repository at this point in the history
  • Loading branch information
mlw committed Feb 20, 2024
2 parents 42eb0a3 + 5454a8c commit 2199080
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 25 deletions.
60 changes: 41 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,25 @@ - (void)handleMessage:(Message &&)esMsg
return;
}

if (self->_prefixTree->HasPrefix(targetFile->path.data)) {
NSLog(@"doing drop from prefix tree...");
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

0 comments on commit 2199080

Please sign in to comment.