Skip to content
This repository was archived by the owner on Feb 5, 2025. It is now read-only.
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#import "Source/santad/EventProviders/SNTEndpointSecurityTamperResistance.h"

#include <EndpointSecurity/ESTypes.h>
#include <bsm/libbsm.h>
#include <string.h>
#include <algorithm>

#import "Source/common/SNTLogging.h"
#include "Source/santad/DataLayer/WatchItemPolicy.h"
Expand Down Expand Up @@ -85,6 +87,26 @@ - (void)handleMessage:(Message &&)esMsg
break;
}

case ES_EVENT_TYPE_AUTH_SIGNAL: {
// Only block signals sent to us and not from launchd.
if (audit_token_to_pid(esMsg->event.signal.target->audit_token) == getpid() &&
audit_token_to_pid(esMsg->process->audit_token) != 1) {
LOGW(@"Preventing attempt to kill Santa daemon");
result = ES_AUTH_RESULT_DENY;
}
break;
}

case ES_EVENT_TYPE_AUTH_EXEC: {
// When not running a debug build, prevent attempts to kill Santa
// by launchctl commands.
#ifndef DEBUG
result = ValidateLaunchctlExec(esMsg);
if (result == ES_AUTH_RESULT_DENY) LOGW(@"Preventing attempt to kill Santa daemon");
#endif
break;
}

case ES_EVENT_TYPE_AUTH_KEXTLOAD: {
// TODO(mlw): Since we don't package the kext anymore, we should consider removing this.
// TODO(mlw): Consider logging when kext loads are attempted.
Expand Down Expand Up @@ -120,15 +142,53 @@ - (void)enable {
for (const auto &path : protectedPaths) {
watchPaths.push_back({path, WatchItemPathType::kLiteral});
}
watchPaths.push_back({"/Library/SystemExtensions", WatchItemPathType::kPrefix});
watchPaths.push_back({"/bin/launchctl", WatchItemPathType::kLiteral});

// Begin watching the protected set
[super muteTargetPaths:watchPaths];

[super subscribeAndClearCache:{
ES_EVENT_TYPE_AUTH_KEXTLOAD,
ES_EVENT_TYPE_AUTH_SIGNAL,
ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_AUTH_RENAME,
}];
}

es_auth_result_t ValidateLaunchctlExec(const Message &esMsg) {
es_string_token_t exec_path = esMsg->event.exec.target->executable->path;
if (strncmp(exec_path.data, "/bin/launchctl", exec_path.length) != 0) {
return ES_AUTH_RESULT_ALLOW;
}

// Ensure there are at least 2 arguments after the command
std::shared_ptr<EndpointSecurityAPI> esApi = esMsg.ESAPI();
uint32_t argCount = esApi->ExecArgCount(&esMsg->event.exec);
if (argCount < 2) {
return ES_AUTH_RESULT_ALLOW;
}

// Check for some allowed subcommands
es_string_token_t arg = esApi->ExecArg(&esMsg->event.exec, 1);
static const std::unordered_set<std::string> safe_commands{
"blame", "help", "hostinfo", "list", "plist", "print", "procinfo",
};
if (safe_commands.find(std::string(arg.data, arg.length)) != safe_commands.end()) {
return ES_AUTH_RESULT_ALLOW;
}

// Check whether com.google.santa.daemon is in the argument list.
// launchctl no longer accepts PIDs to operate on.
for (int i = 2; i < argCount; i++) {
es_string_token_t arg = esApi->ExecArg(&esMsg->event.exec, i);
if (strnstr(arg.data, "com.google.santa.daemon", arg.length) != NULL) {
return ES_AUTH_RESULT_DENY;
}
}

return ES_AUTH_RESULT_ALLOW;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ @implementation SNTEndpointSecurityTamperResistanceTest
- (void)testEnable {
// Ensure the client subscribes to expected event types
std::set<es_event_type_t> expectedEventSubs{
ES_EVENT_TYPE_AUTH_KEXTLOAD,
ES_EVENT_TYPE_AUTH_UNLINK,
ES_EVENT_TYPE_AUTH_RENAME,
ES_EVENT_TYPE_AUTH_KEXTLOAD, ES_EVENT_TYPE_AUTH_SIGNAL, ES_EVENT_TYPE_AUTH_EXEC,
ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_AUTH_RENAME,
};

auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
Expand All @@ -70,6 +69,8 @@ - (void)testEnable {
// Setup mocks to handle muting the rules db and events db
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, testing::_, WatchItemPathType::kLiteral))
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, testing::_, WatchItemPathType::kPrefix))
.WillRepeatedly(testing::Return(true));

SNTEndpointSecurityTamperResistance *tamperClient =
[[SNTEndpointSecurityTamperResistance alloc] initWithESAPI:mockESApi
Expand All @@ -86,7 +87,7 @@ - (void)testEnable {
- (void)testHandleMessage {
es_file_t file = MakeESFile("foo");
es_process_t proc = MakeESProcess(&file);
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_EXEC, &proc, ActionType::Auth);
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_LINK, &proc, ActionType::Auth);

es_file_t fileEventsDB = MakeESFile(kEventsDBPath.data());
es_file_t fileRulesDB = MakeESFile(kRulesDBPath.data());
Expand All @@ -106,6 +107,12 @@ - (void)testHandleMessage {
{&benignTok, ES_AUTH_RESULT_ALLOW},
};

std::map<std::pair<pid_t, pid_t>, es_auth_result_t> pidsToResult{
{{getpid(), 31838}, ES_AUTH_RESULT_DENY},
{{getpid(), 1}, ES_AUTH_RESULT_ALLOW},
{{435, 98381}, ES_AUTH_RESULT_ALLOW},
};

dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);

auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
Expand Down Expand Up @@ -232,6 +239,31 @@ - (void)testHandleMessage {
}
}

// Check SIGNAL tamper events
{
esMsg.event_type = ES_EVENT_TYPE_AUTH_SIGNAL;

for (const auto &kv : pidsToResult) {
Message msg(mockESApi, &esMsg);
es_process_t target_proc = MakeESProcess(&file);
target_proc.audit_token = MakeAuditToken(kv.first.first, 42);
esMsg.event.signal.target = &target_proc;
esMsg.process->audit_token = MakeAuditToken(kv.first.second, 42);

[mockTamperClient
handleMessage:std::move(msg)
recordEventMetrics:^(EventDisposition d) {
XCTAssertEqual(d, kv.second == ES_AUTH_RESULT_DENY ? EventDisposition::kProcessed
: EventDisposition::kDropped);
dispatch_semaphore_signal(semaMetrics);
}];

XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
XCTAssertEqual(gotAuthResult, kv.second);
XCTAssertEqual(gotCachable, kv.second == ES_AUTH_RESULT_ALLOW);
}
}

XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
XCTAssertTrue(OCMVerifyAll(mockTamperClient));

Expand Down