Skip to content

Commit

Permalink
Initial infrastructure for Proc FAA client (#237)
Browse files Browse the repository at this point in the history
This PR lays the foundation for the new Proc FAA client and sets up some
of the required interfaces between it and the Authorizer client and the
WatchItems class. It also implements newly required ES API interfaces.
None of this new code is yet hooked up to the rest of Santa - no new
client will be created yet at runtime.

Part of: #124
  • Loading branch information
mlw authored Feb 5, 2025
1 parent 2d5bda5 commit b0ef822
Show file tree
Hide file tree
Showing 20 changed files with 348 additions and 42 deletions.
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ test:
reload:
bazel run //:reload

.PHONY: fmt build test reload
clean:
bazel clean

realclean:
bazel clean --expunge

.PHONY: fmt build test reload clean realclean
28 changes: 28 additions & 0 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,19 @@ objc_library(
],
)

objc_library(
name = "SNTEndpointSecurityProcessFileAccessAuthorizer",
srcs = ["EventProviders/SNTEndpointSecurityProcessFileAccessAuthorizer.mm"],
hdrs = ["EventProviders/SNTEndpointSecurityProcessFileAccessAuthorizer.h"],
deps = [
":EndpointSecurityAPI",
":EndpointSecurityMessage",
":Metrics",
":SNTEndpointSecurityClient",
":SNTEndpointSecurityEventHandler",
],
)

objc_library(
name = "SNTEndpointSecurityDeviceManager",
srcs = ["EventProviders/SNTEndpointSecurityDeviceManager.mm"],
Expand Down Expand Up @@ -1420,6 +1433,20 @@ santa_unit_test(
],
)

santa_unit_test(
name = "SNTEndpointSecurityProcessFileAccessAuthorizerTest",
srcs = ["EventProviders/SNTEndpointSecurityProcessFileAccessAuthorizerTest.mm"],
sdk_dylibs = [
"EndpointSecurity",
],
deps = [
":MockEndpointSecurityAPI",
":SNTEndpointSecurityProcessFileAccessAuthorizer",
"//Source/common:TestUtils",
"@OCMock",
],
)

santa_unit_test(
name = "SNTEndpointSecurityTamperResistanceTest",
srcs = ["EventProviders/SNTEndpointSecurityTamperResistanceTest.mm"],
Expand Down Expand Up @@ -1538,6 +1565,7 @@ test_suite(
":SNTEndpointSecurityClientTest",
":SNTEndpointSecurityDeviceManagerTest",
":SNTEndpointSecurityFileAccessAuthorizerTest",
":SNTEndpointSecurityProcessFileAccessAuthorizerTest",
":SNTEndpointSecurityRecorderTest",
":SNTEndpointSecurityTamperResistanceTest",
":SNTEndpointSecurityTreeAwareClientTest",
Expand Down
6 changes: 4 additions & 2 deletions Source/santad/DataLayer/WatchItems.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {

void BeginPeriodicTask();

void RegisterClient(id<SNTEndpointSecurityDynamicEventHandler> client);
void RegisterDataClient(id<SNTDataFileAccessAuthorizer> client);
void RegisterProcessClient(id<SNTProcessFileAccessAuthorizer> client);

void SetConfigPath(NSString *config_path);
void SetConfig(NSDictionary *config);
Expand Down Expand Up @@ -177,7 +178,8 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
NSDictionary *current_config_ ABSL_GUARDED_BY(lock_);
NSTimeInterval last_update_time_ ABSL_GUARDED_BY(lock_);
std::string policy_version_ ABSL_GUARDED_BY(lock_);
std::set<id<SNTEndpointSecurityDynamicEventHandler>> registerd_clients_ ABSL_GUARDED_BY(lock_);
id<SNTDataFileAccessAuthorizer> registered_data_client_ ABSL_GUARDED_BY(lock_);
id<SNTProcessFileAccessAuthorizer> registered_proc_client_ ABSL_GUARDED_BY(lock_);
bool periodic_task_started_ = false;
NSString *policy_event_detail_url_ ABSL_GUARDED_BY(lock_);
NSString *policy_event_detail_text_ ABSL_GUARDED_BY(lock_);
Expand Down
21 changes: 15 additions & 6 deletions Source/santad/DataLayer/WatchItems.mm
Original file line number Diff line number Diff line change
Expand Up @@ -796,9 +796,18 @@ bool ParseConfig(NSDictionary *config, SetSharedDataWatchItemPolicy &data_polici
}
}

void WatchItems::RegisterClient(id<SNTEndpointSecurityDynamicEventHandler> client) {
void WatchItems::RegisterDataClient(id<SNTDataFileAccessAuthorizer> client) {
absl::MutexLock lock(&lock_);
registerd_clients_.insert(client);
if (!registered_data_client_) {
registered_data_client_ = client;
}
}

void WatchItems::RegisterProcessClient(id<SNTProcessFileAccessAuthorizer> client) {
absl::MutexLock lock(&lock_);
if (!registered_proc_client_) {
registered_proc_client_ = client;
}
}

void WatchItems::UpdateCurrentState(DataWatchItems new_data_watch_items,
Expand Down Expand Up @@ -847,15 +856,15 @@ bool ParseConfig(NSDictionary *config, SetSharedDataWatchItemPolicy &data_polici

LOGD(@"Changes to watch items detected, notifying registered clients.");

for (const id<SNTEndpointSecurityDynamicEventHandler> &client : registerd_clients_) {
if (registered_data_client_) {
// Note: Enable clients on an async queue in case they perform any
// synchronous work that could trigger ES events. Otherwise they might
// trigger AUTH ES events that would attempt to re-enter this object and
// potentially deadlock.
dispatch_async(q_, ^{
[client watchItemsCount:data_watch_items_.Count()
newPaths:paths_to_watch
removedPaths:paths_to_stop_watching];
[registered_data_client_ watchItemsCount:data_watch_items_.Count()
newPaths:paths_to_watch
removedPaths:paths_to_stop_watching];
});
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
virtual bool UnmuteTargetPath(const Client &client, std::string_view path,
santa::WatchItemPathType path_type);

virtual bool IsProcessMutingInverted(const Client &client);
virtual bool InvertProcessMuting(const Client &client);
virtual bool MuteProcess(const Client &client, const audit_token_t *tok);

virtual void RetainMessage(const es_message_t *msg);
virtual void ReleaseMessage(const es_message_t *msg);

Expand All @@ -55,8 +59,6 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
virtual bool RespondFlagsResult(const Client &client, const Message &msg, uint32_t allowed_flags,
bool cache);

virtual bool MuteProcess(const Client &client, const audit_token_t *tok);

virtual bool ClearCache(const Client &client);

virtual uint32_t ExecArgCount(const es_event_exec_t *event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,34 @@
return false;
}

bool EndpointSecurityAPI::IsProcessMutingInverted(const Client &client) {
#if HAVE_MACOS_13
if (@available(macOS 13.0, *)) {
return es_muting_inverted(client.Get(), ES_MUTE_INVERSION_TYPE_PROCESS) == ES_MUTE_INVERTED;
}
#endif

return false;
}

bool EndpointSecurityAPI::InvertProcessMuting(const Client &client) {
#if HAVE_MACOS_13
if (@available(macOS 13.0, *)) {
if (!IsProcessMutingInverted(client)) {
return es_invert_muting(client.Get(), ES_MUTE_INVERSION_TYPE_PROCESS) == ES_RETURN_SUCCESS;
} else {
return true;
}
}
#endif

return false;
}

bool EndpointSecurityAPI::MuteProcess(const Client &client, const audit_token_t *tok) {
return es_mute_process(client.Get(), tok) == ES_RETURN_SUCCESS;
}

bool EndpointSecurityAPI::MuteTargetPath(const Client &client, std::string_view path,
WatchItemPathType path_type) {
#if HAVE_MACOS_13
Expand Down Expand Up @@ -131,10 +159,6 @@
return es_respond_flags_result(client.Get(), &(*msg), allowed_flags, cache);
}

bool EndpointSecurityAPI::MuteProcess(const Client &client, const audit_token_t *tok) {
return es_mute_process(client.Get(), tok) == ES_RETURN_SUCCESS;
}

bool EndpointSecurityAPI::ClearCache(const Client &client) {
return es_clear_cache(client.Get()) == ES_CLEAR_CACHE_RESULT_SUCCESS;
}
Expand Down
2 changes: 2 additions & 0 deletions Source/santad/EventProviders/SNTEndpointSecurityAuthorizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache
ttyWriter:(std::shared_ptr<santa::TTYWriter>)ttyWriter;

- (void)registerAuthExecProbe:(id<SNTEndpointSecurityProbe>)watcher;

@end
45 changes: 33 additions & 12 deletions Source/santad/EventProviders/SNTEndpointSecurityAuthorizer.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@interface SNTEndpointSecurityAuthorizer ()
@property SNTCompilerController *compilerController;
@property SNTExecutionController *execController;
@property id<SNTEndpointSecurityProbe> procWatcherProbe;
@end

@implementation SNTEndpointSecurityAuthorizer {
Expand Down Expand Up @@ -66,6 +67,27 @@ - (NSString *)description {
return @"Authorizer";
}

- (bool)respondToMessage:(const santa::Message &)msg
withAuthResult:(es_auth_result_t)result
forcePreventCache:(BOOL)forcePreventCache {
// Don't let the ES framework cache DENY results. Santa only flushes ES cache
// when a new DENY rule is received. If DENY results were cached and a rule
// update made the executable allowable, ES would continue to apply the DENY
// cached result. Note however that the local AuthResultCache will cache
// DENY results. The caller may also prevent caching if it has reason to so.
bool cacheable = (result == ES_AUTH_RESULT_ALLOW) && !forcePreventCache;

if (self.procWatcherProbe) {
santa::ProbeInterest interest = [self.procWatcherProbe probeInterest:msg];

// Prevent caching if a probe is interested in the process. But don't re-enable
// caching if it was already previously disabled.
cacheable = cacheable && (interest == santa::ProbeInterest::kUninterested);
}

return [self respondToMessage:msg withAuthResult:result cacheable:cacheable];
}

- (void)processMessage:(Message)msg {
if (msg->event_type == ES_EVENT_TYPE_AUTH_PROC_SUSPEND_RESUME) {
[self.execController
Expand Down Expand Up @@ -95,9 +117,8 @@ - (void)processMessage:(Message)msg {
default: break;
}

[self respondToMessage:msg
withAuthResult:authResult
cacheable:(authResult == ES_AUTH_RESULT_ALLOW)];
[self respondToMessage:msg withAuthResult:authResult forcePreventCache:NO];

return;
} else if (returnAction == SNTActionRespondHold) {
_ttyWriter->Write(
Expand Down Expand Up @@ -188,15 +209,11 @@ - (bool)postAction:(SNTAction)action forMessage:(const Message &)esMsg {
self->_authResultCache->AddToCache(esMsg->event.exec.target->executable, action);

if (action != SNTActionHoldAllowed && action != SNTActionHoldDenied) {
// Don't let the ES framework cache DENY results. Santa only flushes ES cache
// when a new DENY rule is received. If DENY results were cached and a rule
// update made the executable allowable, ES would continue to apply the DENY
// cached result. Note however that the local AuthResultCache will cache
// DENY results.
return [self
respondToMessage:esMsg
withAuthResult:authResult
cacheable:(authResult == ES_AUTH_RESULT_ALLOW && action != SNTActionRespondHold)];
// Do not allow caching when the action is SNTActionRespondHold because Santa
// also authorizes EXECs that occur while the current authorization is pending.
return [self respondToMessage:esMsg
withAuthResult:authResult
forcePreventCache:(action == SNTActionRespondHold)];
} else {
return true;
}
Expand All @@ -209,4 +226,8 @@ - (void)enable {
}];
}

- (void)registerAuthExecProbe:(id<SNTEndpointSecurityProbe>)watcher {
self.procWatcherProbe = watcher;
}

@end
13 changes: 13 additions & 0 deletions Source/santad/EventProviders/SNTEndpointSecurityClient.mm
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ - (bool)subscribeAndClearCache:(const std::set<es_event_type_t> &)events {
- (bool)unsubscribeAll {
return _esApi->UnsubscribeAll(_esClient);
}
- (bool)unmuteAllPaths {
return _esApi->UnmuteAllPaths(_esClient);
}

- (bool)unmuteAllTargetPaths {
return _esApi->UnmuteAllTargetPaths(_esClient);
Expand All @@ -227,6 +230,16 @@ - (bool)enableTargetPathWatching {
return _esApi->InvertTargetPathMuting(_esClient);
}

- (bool)enableProcessWatching {
[self unmuteAllPaths];
[self unmuteAllTargetPaths];
return _esApi->InvertProcessMuting(_esClient);
}

- (bool)muteProcess:(const audit_token_t *)tok {
return _esApi->MuteProcess(_esClient, tok);
}

- (bool)muteTargetPaths:(const santa::SetPairPathAndType &)paths {
bool result = true;
for (const auto &PairPathAndType : paths) {
Expand Down
3 changes: 3 additions & 0 deletions Source/santad/EventProviders/SNTEndpointSecurityClientBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
- (bool)muteTargetPaths:(const santa::SetPairPathAndType &)paths;
- (bool)unmuteTargetPaths:(const santa::SetPairPathAndType &)paths;

- (bool)enableProcessWatching;
- (bool)muteProcess:(const audit_token_t *)tok;

/// Responds to the Message with the given auth result
///
/// @param Message The wrapped es_message_t being responded to
Expand Down
46 changes: 33 additions & 13 deletions Source/santad/EventProviders/SNTEndpointSecurityEventHandler.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/// Copyright 2022 Google Inc. All rights reserved.
/// Copyright 2025 North Pole Security, Inc.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.

#import <Foundation/Foundation.h>

Expand All @@ -35,16 +36,35 @@

@end

// Extension of the `SNTEndpointSecurityEventHandler` protocol for
// `SNTEndpointSecurityClient` subclasses that can be dynamically
// enabled and disabled.
@protocol SNTEndpointSecurityDynamicEventHandler <SNTEndpointSecurityEventHandler>

// Called when a client should no longer receive events.
- (void)disable;
// Protocol for an object that implements the necessary interfaces
// for handling updates to Data FAA rules.
@protocol SNTDataFileAccessAuthorizer

- (void)watchItemsCount:(size_t)count
newPaths:(const santa::SetPairPathAndType &)newPaths
removedPaths:(const santa::SetPairPathAndType &)removedPaths;

@end

// Protocol for an object that implements the necessary interfaces
// for handling updates to Data FAA rules.
@protocol SNTProcessFileAccessAuthorizer

// TODO: Currently just a stub

@end

namespace santa {

enum class ProbeInterest {
kInterested,
kUninterested,
};

} // namespace santa

@protocol SNTEndpointSecurityProbe <NSObject>

- (santa::ProbeInterest)probeInterest:(const santa::Message &)esMsg;

@end
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Copyright 2022 Google LLC
/// Copyright 2025 North Pole Security, Inc.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,7 +32,7 @@ typedef void (^SNTFileAccessBlockCallback)(SNTFileAccessEvent *event, NSString *
NSString *customURL, NSString *customText);

@interface SNTEndpointSecurityFileAccessAuthorizer
: SNTEndpointSecurityClient <SNTEndpointSecurityDynamicEventHandler>
: SNTEndpointSecurityClient <SNTEndpointSecurityEventHandler, SNTDataFileAccessAuthorizer>

- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
metrics:(std::shared_ptr<santa::Metrics>)metrics
Expand Down
Loading

0 comments on commit b0ef822

Please sign in to comment.