diff --git a/Source/santad/EventProviders/SNTEndpointSecurityManager.mm b/Source/santad/EventProviders/SNTEndpointSecurityManager.mm index 3743d744e..28d2db2da 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityManager.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityManager.mm @@ -303,8 +303,7 @@ - (void)messageHandler:(es_message_t *)m API_AVAILABLE(macos(10.15)) { NSString *path = [[NSString alloc] initWithBytes:pathToken.data length:pathToken.length encoding:NSUTF8StringEncoding]; - if (([path isEqualToString:@"/private/var/db/santa/rules.db"] || - [path isEqualToString:@"/private/var/db/santa/events.db"]) && + if ([self isDatabasePath:path] && audit_token_to_pid(m->process->audit_token) != self.selfPID) { LOGW(@"Preventing attempt to delete Santa databases!"); es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true); @@ -319,15 +318,26 @@ - (void)messageHandler:(es_message_t *)m API_AVAILABLE(macos(10.15)) { length:pathToken.length encoding:NSUTF8StringEncoding]; - if (([path isEqualToString:@"/private/var/db/santa/rules.db"] || - [path isEqualToString:@"/private/var/db/santa/events.db"]) && + if ([self isDatabasePath:path] && audit_token_to_pid(m->process->audit_token) != self.selfPID) { LOGW(@"Preventing attempt to rename Santa databases!"); es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true); return; } - es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true); - return; + if (m->event.rename.destination_type == ES_DESTINATION_TYPE_EXISTING_FILE) { + es_string_token_t destToken = m->event.rename.destination.existing_file->path; + NSString *destPath = [[NSString alloc] initWithBytes:destToken.data + length:destToken.length + encoding:NSUTF8StringEncoding]; + if ([self isDatabasePath:destPath] && + audit_token_to_pid(m->process->audit_token) != self.selfPID) { + LOGW(@"Preventing attempt to overwrite Santa databases!"); + es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, true); + return; + } + es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true); + return; + } } case ES_EVENT_TYPE_AUTH_KEXTLOAD: { es_string_token_t identifier = m->event.kextload.identifier; @@ -561,6 +571,11 @@ - (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file { }; } +- (BOOL)isDatabasePath:(NSString *)path { + return [path isEqualToString:@"/private/var/db/santa/rules.db"] || + [path isEqualToString:@"/private/var/db/santa/events.db"]; +} + - (BOOL)isCompilerPID:(pid_t)pid { return (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()); } diff --git a/Source/santad/EventProviders/SNTEndpointSecurityManagerTest.mm b/Source/santad/EventProviders/SNTEndpointSecurityManagerTest.mm index d4027c3da..960356857 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityManagerTest.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityManagerTest.mm @@ -200,4 +200,130 @@ - (void)testSkipOtherESEvents { XCTAssertEqual(got.result, ES_AUTH_RESULT_ALLOW); } +- (void)testRenameOverwriteRulesDB { + for (const NSString *testPath in @[ kEventsDBPath, kRulesDBPath ]) { + MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity]; + [mockES reset]; + SNTEndpointSecurityManager *snt = [[SNTEndpointSecurityManager alloc] init]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"]; + __block NSMutableArray *events = [NSMutableArray array]; + __block ESResponse *got = nil; + [mockES registerResponseCallback:^(ESResponse *r) { + got = r; + [expectation fulfill]; + }]; + es_file_t otherFile = {.path = MakeStringToken(@"/some/other/path")}; + es_file_t dbFile = {.path = MakeStringToken(testPath)}; + + es_event_rename_t renameEvent = { + .destination_type = ES_DESTINATION_TYPE_EXISTING_FILE, + .source = &otherFile, + .destination = {.existing_file = &dbFile}, + }; + + 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_events_t event = {.rename = renameEvent}; + es_message_t m = { + .version = 4, + .event_type = ES_EVENT_TYPE_AUTH_RENAME, + .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]; + + [self waitForExpectationsWithTimeout:10.0 + handler:^(NSError *error) { + if (error) { + XCTFail(@"Santa auth test timed out with error: %@", error); + } + }]; + + XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY, @"Failed to deny rename overwrite of %@", + testPath); + XCTAssertTrue(got.shouldCache, @"Failed to cache rename deny decision of %@", testPath); + } +} + +- (void)testRenameRulesDB { + for (const NSString *testPath in @[ kEventsDBPath, kRulesDBPath ]) { + MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity]; + [mockES reset]; + SNTEndpointSecurityManager *snt = [[SNTEndpointSecurityManager alloc] init]; + + XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for response from ES"]; + __block NSMutableArray *events = [NSMutableArray array]; + __block ESResponse *got = nil; + [mockES registerResponseCallback:^(ESResponse *r) { + got = r; + [expectation fulfill]; + }]; + es_file_t otherFile = {.path = MakeStringToken(@"/some/other/path")}; + es_file_t dbFile = {.path = MakeStringToken(testPath)}; + + es_event_rename_t renameEvent = { + .source = &dbFile, + .destination_type = ES_DESTINATION_TYPE_NEW_PATH, + .destination = + { + .new_path = + { + .dir = &otherFile, + .filename = MakeStringToken(@"someotherfilename"), + }, + }, + }; + + 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_events_t event = {.rename = renameEvent}; + es_message_t m = { + .version = 4, + .event_type = ES_EVENT_TYPE_AUTH_RENAME, + .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]; + + [self waitForExpectationsWithTimeout:10.0 + handler:^(NSError *error) { + if (error) { + XCTFail(@"Santa auth test timed out with error: %@", error); + } + }]; + + XCTAssertEqual(got.result, ES_AUTH_RESULT_DENY, @"Failed to deny rename of %@", testPath); + XCTAssertTrue(got.shouldCache, @"Failed to cache rename deny decision of %@", testPath); + } +} + @end