Skip to content

Commit

Permalink
Include SNTEndpointSecurityManagerTest in the main test_suite (#566)
Browse files Browse the repository at this point in the history
* Include SNTEndpointSecurityManagerTest in the main test_suite and clean it up.

This commit (1) adds es_unsubscribe and es_delete_client to our ESF shim
to fix the test segfaulting, and (2) cleans up the unit tests themselves by
breaking out the timeout test from the regular unlink test
  • Loading branch information
tnek authored Aug 11, 2021
1 parent 27ee665 commit 54d6653
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 41 deletions.
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,6 @@ test_suite(
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
],
)
8 changes: 8 additions & 0 deletions Source/santad/EventProviders/EndpointSecurityTestUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos)
es_return_t es_subscribe(es_client_t *_Nonnull client, const es_event_type_t *_Nonnull events,
uint32_t event_count);

API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos) es_return_t es_delete_client(es_client_t *_Nullable client);

API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos) es_return_t
es_unsubscribe(es_client_t *_Nonnull client, const es_event_type_t *_Nonnull events,
uint32_t event_count);
14 changes: 14 additions & 0 deletions Source/santad/EventProviders/EndpointSecurityTestUtil.mm
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ es_new_client_result_t es_new_client(es_client_t *_Nullable *_Nonnull client,
return ES_NEW_CLIENT_RESULT_SUCCESS;
};

API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos) es_return_t es_delete_client(es_client_t *_Nullable client) {
[[MockEndpointSecurity mockEndpointSecurity] reset];
return ES_RETURN_SUCCESS;
};

API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos)
es_respond_result_t es_respond_auth_result(es_client_t *_Nonnull client,
Expand All @@ -132,3 +138,11 @@ es_return_t es_subscribe(es_client_t *_Nonnull client, const es_event_type_t *_N
[MockEndpointSecurity mockEndpointSecurity].subscribed = YES;
return ES_RETURN_SUCCESS;
}
API_AVAILABLE(macos(10.15))
API_UNAVAILABLE(ios, tvos, watchos)
es_return_t es_unsubscribe(es_client_t *_Nonnull client, const es_event_type_t *_Nonnull events,
uint32_t event_count) {
[MockEndpointSecurity mockEndpointSecurity].subscribed = NO;

return ES_RETURN_SUCCESS;
};
156 changes: 115 additions & 41 deletions Source/santad/EventProviders/SNTEndpointSecurityManagerTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,50 +33,109 @@ - (void)setUp {
fclose(stdout);
}

- (void)testDenyOnTimeout {
// There should be two events: an early uncached DENY as the consequence for not
// meeting the decision deadline and an actual cached decision from our message
// handler.
__block int wantNumResp = 2;

MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
SNTEndpointSecurityManager *snt = [[SNTEndpointSecurityManager alloc] init];

XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for santa's Auth dispatch queue"];

__block NSMutableArray<ESResponse *> *events = [NSMutableArray array];
[mockES registerResponseCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}

if (events.count >= wantNumResp) {
[expectation fulfill];
}
}];

es_file_t dbFile = {.path = MakeStringToken(kEventsDBPath)};
es_file_t otherBinary = {.path = MakeStringToken(@"somebinary")};
es_process_t proc = {
.executable = &otherBinary,
.is_es_client = false,
.codesigning_flags = 570509313,
.session_id = 12345,
.group_id = 12345,
.ppid = 12345,
.original_ppid = 12345,
.is_platform_binary = false,
};
es_event_unlink_t unlink_event = {.target = &dbFile};
es_events_t event = {.unlink = unlink_event};
es_message_t m = {
.version = 4,
.event_type = ES_EVENT_TYPE_AUTH_UNLINK,
.event = event,
.mach_time = 1234,
.action_type = ES_ACTION_TYPE_AUTH,
.deadline = 1234,
.process = &proc,
.seq_num = 1337,
};

[mockES triggerHandler:&m];

[self waitForExpectationsWithTimeout:10.0
handler:^(NSError *error) {
if (error) {
XCTFail(@"Santa auth test timed out without receiving two "
@"events. Instead, had error: %@",
error);
}
}];

for (ESResponse *resp in events) {
XCTAssertEqual(
resp.result, ES_AUTH_RESULT_DENY,
@"Failed to automatically deny on timeout and also the malicious event afterwards");
}
}

- (void)testDeleteRulesDB {
for (const NSString *testPath in @[ kEventsDBPath, kRulesDBPath ]) {
// There should be two events: an early uncached DENY as the consequence for not
// meeting the decision deadline and an actual cached decision from our message
// handler.
__block int wantNumResp = 2;

MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
SNTEndpointSecurityManager *snt = [[SNTEndpointSecurityManager alloc] init];

snt.logCallback = ^(santa_message_t m) {
};
snt.decisionCallback = ^(santa_message_t m) {
};

XCTestExpectation *expectation =
[self expectationWithDescription:@"Wait for santa's Auth dispatch queue"];

__block NSMutableArray<ESResponse *> *events = [NSMutableArray array];
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];
__block ESResponse *got = nil;
[mockES registerResponseCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}

if (events.count >= wantNumResp) {
[expectation fulfill];
}
got = r;
[expectation fulfill];
}];

es_file_t dbFile = {.path = MakeStringToken(testPath)};
es_file_t otherBinary = {.path = MakeStringToken(@"somebinary")};
es_process_t proc = {
.executable = &otherBinary,
.is_es_client = false,
.codesigning_flags = 570509313,
.session_id = 12345,
.group_id = 12345,
.ppid = 12345,
.original_ppid = 12345,
.is_platform_binary = false,
};
es_event_unlink_t unlink_event = {.target = &dbFile};
es_events_t event = {.unlink = unlink_event};
es_message_t m = {
.version = 4,
.event_type = ES_EVENT_TYPE_AUTH_UNLINK,
.event = event,
.mach_time = DISPATCH_TIME_NOW,
.action_type = ES_ACTION_TYPE_AUTH,
.deadline = DISPATCH_TIME_NOW + NSEC_PER_SEC * 60,
.deadline = DISPATCH_TIME_FOREVER,
.process = &proc,
.seq_num = 1337,
};
[mockES triggerHandler:&m];

Expand All @@ -87,12 +146,8 @@ - (void)testDeleteRulesDB {
}
}];

bool wasCached = false;
for (ESResponse *resp in events) {
XCTAssertEqual(resp.result, ES_AUTH_RESULT_DENY, @"Failed to deny deletion of %@", testPath);
wasCached = wasCached | resp.shouldCache;
}
XCTAssertTrue(wasCached, @"Failed to cache deletion decision of %@", testPath);
XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY, @"Failed to deny deletion of %@", testPath);
XCTAssertTrue(got.shouldCache, @"Failed to cache deletion decision of %@", testPath);
}
}

Expand All @@ -101,29 +156,48 @@ - (void)testSkipOtherESEvents {
[mockES reset];
SNTEndpointSecurityManager *snt = [[SNTEndpointSecurityManager alloc] init];

snt.logCallback = ^(santa_message_t m) {
};
snt.decisionCallback = ^(santa_message_t m) {
};
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"];

__block NSMutableArray<ESResponse *> *events = [NSMutableArray array];
__block ESResponse *got = nil;
[mockES registerResponseCallback:^(ESResponse *r) {
@synchronized(self) {
[events addObject:r];
}
got = r;
[expectation fulfill];
}];

es_file_t dbFile = {.path = MakeStringToken(@"/some/other/path")};
es_file_t otherBinary = {.path = MakeStringToken(@"somebinary")};
es_process_t proc = {.executable = &otherBinary, .is_es_client = true};
es_process_t proc = {
.executable = &otherBinary,
.is_es_client = true,
.codesigning_flags = 570509313,
.session_id = 12345,
.group_id = 12345,
.ppid = 12345,
.original_ppid = 12345,
.is_platform_binary = false,
};
es_event_unlink_t unlink_event = {.target = &dbFile};
es_events_t event = {.unlink = unlink_event};
es_message_t m = {
.version = 4,
.event_type = ES_EVENT_TYPE_AUTH_UNLINK,
.event = event,
.mach_time = DISPATCH_TIME_NOW,
.action_type = ES_ACTION_TYPE_AUTH,
.deadline = DISPATCH_TIME_FOREVER,
.process = &proc,
.seq_num = 1337,
};
[mockES triggerHandler:&m];

XCTAssertEqual(events.count, 1);
XCTAssertEqual(events[0].result, ES_AUTH_RESULT_ALLOW);
XCTAssertEqual(events[0].shouldCache, false);
[mockES triggerHandler:&m];
[self waitForExpectationsWithTimeout:10.0
handler:^(NSError *error) {
if (error) {
XCTFail(@"Santa auth test timed out with error: %@", error);
}
}];

XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW);
}

@end

0 comments on commit 54d6653

Please sign in to comment.