From 60f53bc20a6e74465cc3b2cc47eab35abc1506da Mon Sep 17 00:00:00 2001 From: Matt W <436037+mlw@users.noreply.github.com> Date: Wed, 21 Dec 2022 03:15:01 +0100 Subject: [PATCH] Adopt new ES APIs to monitor target paths (#975) * WIP begin adopting new ES APIs inverting target mute paths * Track subscription status so as not to unnecessarily enable/disable * Properly chain call to invert target mute paths. Fix using wrong Message obj. * Add base client tests * Support compiling on older platforms * More changes to support compiling on older platforms * Only enable watch items periodic task on macOS 13 * Add more asserts to test * Disable ES caching for now * lint --- Source/common/BUILD | 5 + Source/common/Platform.h | 34 ++++ Source/santad/BUILD | 9 ++ Source/santad/DataLayer/SNTRuleTable.m | 3 +- Source/santad/DataLayer/WatchItems.mm | 27 +++- .../EndpointSecurity/EndpointSecurityAPI.h | 13 ++ .../EndpointSecurity/EndpointSecurityAPI.mm | 72 ++++++++- .../MockEndpointSecurityAPI.h | 14 ++ .../SNTEndpointSecurityClient.mm | 30 ++++ .../SNTEndpointSecurityClientBase.h | 8 + .../SNTEndpointSecurityClientTest.mm | 148 ++++++++++++++++-- .../SNTEndpointSecurityEventHandler.h | 13 ++ ...SNTEndpointSecurityFileAccessAuthorizer.mm | 42 ++++- ...ndpointSecurityFileAccessAuthorizerTest.mm | 16 ++ Source/santad/Santad.mm | 7 +- 15 files changed, 405 insertions(+), 36 deletions(-) create mode 100644 Source/common/Platform.h diff --git a/Source/common/BUILD b/Source/common/BUILD index c27a2cb5..8408907d 100644 --- a/Source/common/BUILD +++ b/Source/common/BUILD @@ -64,6 +64,11 @@ objc_library( hdrs = ["SantaVnode.h"], ) +objc_library( + name = "Platform", + hdrs = ["Platform.h"], +) + objc_library( name = "SantaVnodeHash", srcs = ["SantaVnodeHash.mm"], diff --git a/Source/common/Platform.h b/Source/common/Platform.h new file mode 100644 index 00000000..aa056597 --- /dev/null +++ b/Source/common/Platform.h @@ -0,0 +1,34 @@ +/// Copyright 2022 Google LLC +/// +/// 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 +/// +/// 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. + +#ifndef SANTA__COMMON__PLATFORM_H +#define SANTA__COMMON__PLATFORM_H + +#include + +#if defined(MAC_OS_VERSION_12_0) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0 +#define HAVE_MACOS_12 1 +#else +#define HAVE_MACOS_12 0 +#endif + +#if defined(MAC_OS_VERSION_13_0) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0 +#define HAVE_MACOS_13 1 +#else +#define HAVE_MACOS_13 0 +#endif + +#endif diff --git a/Source/santad/BUILD b/Source/santad/BUILD index de0ee12e..313cfb4f 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -23,6 +23,7 @@ objc_library( hdrs = ["DataLayer/SNTRuleTable.h"], deps = [ ":SNTDatabaseTable", + "//Source/common:Platform", "//Source/common:SNTCachedDecision", "//Source/common:SNTCommonEnums", "//Source/common:SNTConfigurator", @@ -92,6 +93,7 @@ objc_library( deps = [ ":EndpointSecurityMessage", ":Metrics", + ":WatchItemPolicy", ], ) @@ -238,6 +240,7 @@ objc_library( ":EndpointSecurityEnrichedTypes", ":EndpointSecurityMessage", ":Metrics", + ":WatchItemPolicy", ], ) @@ -553,6 +556,7 @@ objc_library( hdrs = ["EventProviders/EndpointSecurity/Message.h"], deps = [ ":EndpointSecurityClient", + ":WatchItemPolicy", ], ) @@ -568,6 +572,8 @@ objc_library( deps = [ ":EndpointSecurityClient", ":EndpointSecurityMessage", + ":WatchItemPolicy", + "//Source/common:Platform", ], ) @@ -739,6 +745,7 @@ objc_library( ":EndpointSecurityAPI", ":EndpointSecurityClient", ":EndpointSecurityMessage", + ":WatchItemPolicy", "@com_google_googletest//:gtest", ], ) @@ -786,6 +793,7 @@ santa_unit_test( "EndpointSecurity", ], deps = [ + "//Source/common:Platform", "//Source/common:SNTCachedDecision", "//Source/common:SNTCommonEnums", "//Source/common:SNTConfigurator", @@ -1082,6 +1090,7 @@ santa_unit_test( ":Metrics", ":MockEndpointSecurityAPI", ":SNTEndpointSecurityClient", + ":WatchItemPolicy", "//Source/common:TestUtils", "@OCMock", "@com_google_googletest//:gtest", diff --git a/Source/santad/DataLayer/SNTRuleTable.m b/Source/santad/DataLayer/SNTRuleTable.m index d2c3d9fc..c1c43e07 100644 --- a/Source/santad/DataLayer/SNTRuleTable.m +++ b/Source/santad/DataLayer/SNTRuleTable.m @@ -18,6 +18,7 @@ #import #import +#import "Source/common/Platform.h" #import "Source/common/SNTCachedDecision.h" #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTFileInfo.h" @@ -36,7 +37,7 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL // instead we use the following preprocessor macros to conditionally compile these API. The // drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed // on macOS 12 or later, the dynamic mute set will not be computed. -#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0 +#if HAVE_MACOS_12 // Create a temporary ES client in order to grab the default set of muted paths. // TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created es_client_t *client = NULL; diff --git a/Source/santad/DataLayer/WatchItems.mm b/Source/santad/DataLayer/WatchItems.mm index 4e5c71d7..dedb2745 100644 --- a/Source/santad/DataLayer/WatchItems.mm +++ b/Source/santad/DataLayer/WatchItems.mm @@ -19,12 +19,16 @@ #include #include +#include #include #include +#include #include #include #include +#include #include +#include #import "Source/common/PrefixTree.h" #import "Source/common/SNTLogging.h" @@ -322,8 +326,18 @@ void WatchItems::UpdateCurrentState( (current_config_ == nil && new_config != nil) || (currently_monitored_paths_ != new_monitored_paths) || (new_config && ![current_config_ isEqualToDictionary:new_config])) { - // TODO(mlw): In upcoming PR, need to use ES API to stop watching removed paths, - // and start watching newly configured paths. + std::vector> paths_to_watch; + std::vector> paths_to_stop_watching; + + // New paths to watch are those that are in the new set, but not current + std::set_difference(new_monitored_paths.begin(), new_monitored_paths.end(), + currently_monitored_paths_.begin(), currently_monitored_paths_.end(), + std::back_inserter(paths_to_watch)); + + // Paths to stop watching are in the current set, but not new + std::set_difference(currently_monitored_paths_.begin(), currently_monitored_paths_.end(), + new_monitored_paths.begin(), new_monitored_paths.end(), + std::back_inserter(paths_to_stop_watching)); std::swap(watch_items_, new_tree); std::swap(currently_monitored_paths_, new_monitored_paths); @@ -334,18 +348,15 @@ void WatchItems::UpdateCurrentState( policy_version_ = ""; } - bool anyPathsMonitored = currently_monitored_paths_.size() > 0; for (const id &client : registerd_clients_) { // 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_, ^{ - if (anyPathsMonitored) { - [client enable]; - } else { - [client disable]; - } + [client watchItemsCount:currently_monitored_paths_.size() + newPaths:paths_to_watch + removedPaths:paths_to_stop_watching]; }); } } else { diff --git a/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h b/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h index 60a5788a..1b18fae4 100644 --- a/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h +++ b/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h @@ -19,7 +19,9 @@ #import #include +#include +#include "Source/santad/DataLayer/WatchItemPolicy.h" #include "Source/santad/EventProviders/EndpointSecurity/Client.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -34,6 +36,17 @@ class EndpointSecurityAPI : public std::enable_shared_from_this &); virtual bool UnsubscribeAll(const Client &client); + virtual bool UnmuteAllPaths(const Client &client); + virtual bool UnmuteAllTargetPaths(const Client &client); + + virtual bool IsTargetPathMutingInverted(const Client &client); + virtual bool InvertTargetPathMuting(const Client &client); + + virtual bool MuteTargetPath(const Client &client, std::string_view path, + santa::santad::data_layer::WatchItemPathType path_type); + virtual bool UnmuteTargetPath(const Client &client, std::string_view path, + santa::santad::data_layer::WatchItemPathType path_type); + virtual void RetainMessage(const es_message_t *msg); virtual void ReleaseMessage(const es_message_t *msg); diff --git a/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.mm b/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.mm index b3c25a39..93764b08 100644 --- a/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.mm +++ b/Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.mm @@ -13,11 +13,14 @@ /// limitations under the License. #include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" -#include #include #include +#include "Source/common/Platform.h" + +using santa::santad::data_layer::WatchItemPathType; + namespace santa::santad::event_providers::endpoint_security { Client EndpointSecurityAPI::NewClient(void (^message_handler)(es_client_t *, Message)) { @@ -51,6 +54,73 @@ bool EndpointSecurityAPI::UnsubscribeAll(const Client &client) { return es_unsubscribe_all(client.Get()) == ES_RETURN_SUCCESS; } +bool EndpointSecurityAPI::UnmuteAllPaths(const Client &client) { + return es_unmute_all_paths(client.Get()) == ES_RETURN_SUCCESS; +} + +bool EndpointSecurityAPI::UnmuteAllTargetPaths(const Client &client) { +#if HAVE_MACOS_13 + if (@available(macOS 13.0, *)) { + return es_unmute_all_target_paths(client.Get()) == ES_RETURN_SUCCESS; + } +#endif + + return true; +} + +bool EndpointSecurityAPI::IsTargetPathMutingInverted(const Client &client) { +#if HAVE_MACOS_13 + if (@available(macOS 13.0, *)) { + return es_muting_inverted(client.Get(), ES_MUTE_INVERSION_TYPE_TARGET_PATH) == ES_MUTE_INVERTED; + } +#endif + + return false; +} + +bool EndpointSecurityAPI::InvertTargetPathMuting(const Client &client) { +#if HAVE_MACOS_13 + if (@available(macOS 13.0, *)) { + if (!IsTargetPathMutingInverted(client)) { + return es_invert_muting(client.Get(), ES_MUTE_INVERSION_TYPE_TARGET_PATH) == + ES_RETURN_SUCCESS; + } else { + return true; + } + } +#endif + + return false; +} + +bool EndpointSecurityAPI::MuteTargetPath(const Client &client, std::string_view path, + WatchItemPathType path_type) { +#if HAVE_MACOS_13 + if (@available(macOS 13.0, *)) { + return es_mute_path(client.Get(), path.data(), + path_type == WatchItemPathType::kPrefix + ? ES_MUTE_PATH_TYPE_TARGET_PREFIX + : ES_MUTE_PATH_TYPE_TARGET_LITERAL) == ES_RETURN_SUCCESS; + } +#endif + + return false; +} + +bool EndpointSecurityAPI::UnmuteTargetPath(const Client &client, std::string_view path, + WatchItemPathType path_type) { +#if HAVE_MACOS_13 + if (@available(macOS 13.0, *)) { + return es_unmute_path(client.Get(), path.data(), + path_type == WatchItemPathType::kPrefix + ? ES_MUTE_PATH_TYPE_TARGET_PREFIX + : ES_MUTE_PATH_TYPE_TARGET_LITERAL) == ES_RETURN_SUCCESS; + } +#endif + + return true; +} + bool EndpointSecurityAPI::RespondAuthResult(const Client &client, const Message &msg, es_auth_result_t result, bool cache) { return es_respond_auth_result(client.Get(), &(*msg), result, cache) == ES_RESPOND_RESULT_SUCCESS; diff --git a/Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h b/Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h index 15425f2b..41d440f7 100644 --- a/Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h +++ b/Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h @@ -22,6 +22,7 @@ #include +#include "Source/santad/DataLayer/WatchItemPolicy.h" #include "Source/santad/EventProviders/EndpointSecurity/Client.h" #include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -40,6 +41,19 @@ class MockEndpointSecurityAPI const std::set &)); MOCK_METHOD(bool, UnsubscribeAll, (const Client &client)); + MOCK_METHOD(bool, UnmuteAllPaths, (const Client &client)); + MOCK_METHOD(bool, UnmuteAllTargetPaths, (const Client &client)); + + MOCK_METHOD(bool, IsTargetPathMutingInverted, (const Client &client)); + MOCK_METHOD(bool, InvertTargetPathMuting, (const Client &client)); + + MOCK_METHOD(bool, MuteTargetPath, + (const Client &client, std::string_view path, + santa::santad::data_layer::WatchItemPathType path_type)); + MOCK_METHOD(bool, UnmuteTargetPath, + (const Client &client, std::string_view path, + santa::santad::data_layer::WatchItemPathType path_type)); + MOCK_METHOD(void, RetainMessage, (const es_message_t *msg)); MOCK_METHOD(void, ReleaseMessage, (const es_message_t *msg)); diff --git a/Source/santad/EventProviders/SNTEndpointSecurityClient.mm b/Source/santad/EventProviders/SNTEndpointSecurityClient.mm index 518ebaed..69c053b8 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityClient.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityClient.mm @@ -25,6 +25,7 @@ #import "Source/common/SNTConfigurator.h" #import "Source/common/SNTLogging.h" #include "Source/common/SystemResources.h" +#include "Source/santad/DataLayer/WatchItemPolicy.h" #include "Source/santad/EventProviders/EndpointSecurity/Client.h" #include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -33,6 +34,7 @@ using santa::santad::EventDisposition; using santa::santad::Metrics; using santa::santad::Processor; +using santa::santad::data_layer::WatchItemPathType; using santa::santad::event_providers::endpoint_security::Client; using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI; using santa::santad::event_providers::endpoint_security::EnrichedMessage; @@ -185,6 +187,34 @@ using santa::santad::event_providers::endpoint_security::Message; return _esApi->UnsubscribeAll(_esClient); } +- (bool)unmuteEverything { + bool result = _esApi->UnmuteAllPaths(_esClient); + result = _esApi->UnmuteAllTargetPaths(_esClient) && result; + return result; +} + +- (bool)enableTargetPathWatching { + return _esApi->InvertTargetPathMuting(_esClient); +} + +- (bool)muteTargetPaths:(const std::vector> &)paths { + bool result = true; + for (const auto &pathAndTypePair : paths) { + result = + _esApi->MuteTargetPath(_esClient, pathAndTypePair.first, pathAndTypePair.second) && result; + } + return result; +} + +- (bool)unmuteTargetPaths:(const std::vector> &)paths { + bool result = true; + for (const auto &pathAndTypePair : paths) { + result = + _esApi->UnmuteTargetPath(_esClient, pathAndTypePair.first, pathAndTypePair.second) && result; + } + return result; +} + - (bool)respondToMessage:(const Message &)msg withAuthResult:(es_auth_result_t)result cacheable:(bool)cacheable { diff --git a/Source/santad/EventProviders/SNTEndpointSecurityClientBase.h b/Source/santad/EventProviders/SNTEndpointSecurityClientBase.h index 92515086..f88c5084 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityClientBase.h +++ b/Source/santad/EventProviders/SNTEndpointSecurityClientBase.h @@ -17,9 +17,11 @@ #include #include +#include #import +#include "Source/santad/DataLayer/WatchItemPolicy.h" #include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h" #include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -47,6 +49,12 @@ - (bool)subscribeAndClearCache:(const std::set &)events; - (bool)unsubscribeAll; +- (bool)unmuteEverything; +- (bool)enableTargetPathWatching; +- (bool)muteTargetPaths: + (const std::vector> &)paths; +- (bool)unmuteTargetPaths: + (const std::vector> &)paths; /// Responds to the Message with the given auth result /// diff --git a/Source/santad/EventProviders/SNTEndpointSecurityClientTest.mm b/Source/santad/EventProviders/SNTEndpointSecurityClientTest.mm index 5a4e4f26..a3f47ecc 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityClientTest.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityClientTest.mm @@ -23,6 +23,7 @@ #include #include "Source/common/TestUtils.h" +#include "Source/santad/DataLayer/WatchItemPolicy.h" #include "Source/santad/EventProviders/EndpointSecurity/Client.h" #include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" @@ -31,6 +32,7 @@ #include "Source/santad/Metrics.h" using santa::santad::Processor; +using santa::santad::data_layer::WatchItemPathType; using santa::santad::event_providers::endpoint_security::Client; using santa::santad::event_providers::endpoint_security::EnrichedClose; using santa::santad::event_providers::endpoint_security::EnrichedFile; @@ -244,6 +246,117 @@ using santa::santad::event_providers::endpoint_security::Message; XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); } +- (void)testUnsubscribeAll { + auto mockESApi = std::make_shared(); + SNTEndpointSecurityClient *client = + [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi + metrics:nullptr + processor:Processor::kUnknown]; + + // Test the underlying unsubscribe all impl returning both true and false + EXPECT_CALL(*mockESApi, UnsubscribeAll) + .WillOnce(testing::Return(true)) + .WillOnce(testing::Return(false)); + + XCTAssertTrue([client unsubscribeAll]); + XCTAssertFalse([client unsubscribeAll]); + + XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); +} + +- (void)testUnmuteEverything { + auto mockESApi = std::make_shared(); + SNTEndpointSecurityClient *client = + [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi + metrics:nullptr + processor:Processor::kUnknown]; + + // Test variations of underlying unmute impls returning both true and false + EXPECT_CALL(*mockESApi, UnmuteAllPaths) + .WillOnce(testing::Return(true)) + .WillOnce(testing::Return(false)); + EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths) + .WillOnce(testing::Return(true)) + .WillOnce(testing::Return(true)); + + XCTAssertTrue([client unmuteEverything]); + XCTAssertFalse([client unmuteEverything]); + + XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); +} + +- (void)testEnableTargetPathWatching { + auto mockESApi = std::make_shared(); + SNTEndpointSecurityClient *client = + [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi + metrics:nullptr + processor:Processor::kUnknown]; + + // Test the underlying invert nute impl returning both true and false + EXPECT_CALL(*mockESApi, InvertTargetPathMuting) + .WillOnce(testing::Return(true)) + .WillOnce(testing::Return(false)); + + XCTAssertTrue([client enableTargetPathWatching]); + XCTAssertFalse([client enableTargetPathWatching]); + + XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); +} + +- (void)testMuteTargetPaths { + auto mockESApi = std::make_shared(); + SNTEndpointSecurityClient *client = + [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi + metrics:nullptr + processor:Processor::kUnknown]; + + // Ensure all paths are attempted to be muted even if some fail. + // Ensure if any paths fail the overall result is false. + EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "a", WatchItemPathType::kLiteral)) + .WillOnce(testing::Return(true)); + EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "b", WatchItemPathType::kLiteral)) + .WillOnce(testing::Return(false)); + EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, "c", WatchItemPathType::kPrefix)) + .WillOnce(testing::Return(true)); + + std::vector> paths = { + {"a", WatchItemPathType::kLiteral}, + {"b", WatchItemPathType::kLiteral}, + {"c", WatchItemPathType::kPrefix}, + }; + + XCTAssertFalse([client muteTargetPaths:paths]); + + XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); +} + +- (void)testUnmuteTargetPaths { + auto mockESApi = std::make_shared(); + SNTEndpointSecurityClient *client = + [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi + metrics:nullptr + processor:Processor::kUnknown]; + + // Ensure all paths are attempted to be unmuted even if some fail. + // Ensure if any paths fail the overall result is false. + EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "a", WatchItemPathType::kLiteral)) + .WillOnce(testing::Return(true)); + EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "b", WatchItemPathType::kLiteral)) + .WillOnce(testing::Return(false)); + EXPECT_CALL(*mockESApi, UnmuteTargetPath(testing::_, "c", WatchItemPathType::kPrefix)) + .WillOnce(testing::Return(true)); + + std::vector> paths = { + {"a", WatchItemPathType::kLiteral}, + {"b", WatchItemPathType::kLiteral}, + {"c", WatchItemPathType::kPrefix}, + }; + + XCTAssertFalse([client unmuteTargetPaths:paths]); + + XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); +} + - (void)testRespondToMessageWithAuthResultCacheable { es_message_t esMsg; auto mockESApi = std::make_shared(); @@ -280,21 +393,22 @@ using santa::santad::event_providers::endpoint_security::Message; [[SNTEndpointSecurityClient alloc] initWithESAPI:mockESApi metrics:nullptr processor:Processor::kUnknown]; + { + auto enrichedMsg = std::make_shared( + EnrichedClose(Message(mockESApi, &esMsg), + EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt, + EnrichedFile(std::nullopt, std::nullopt, std::nullopt)), + EnrichedFile(std::nullopt, std::nullopt, std::nullopt))); - auto enrichedMsg = std::make_shared( - EnrichedClose(Message(mockESApi, &esMsg), - EnrichedProcess(std::nullopt, std::nullopt, std::nullopt, std::nullopt, - EnrichedFile(std::nullopt, std::nullopt, std::nullopt)), - EnrichedFile(std::nullopt, std::nullopt, std::nullopt))); + [client processEnrichedMessage:enrichedMsg + handler:^(std::shared_ptr msg) { + dispatch_semaphore_signal(sema); + }]; - [client processEnrichedMessage:enrichedMsg - handler:^(std::shared_ptr msg) { - dispatch_semaphore_signal(sema); - }]; - - XCTAssertEqual(0, - dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)), - "Handler block not called within expected time window"); + XCTAssertEqual( + 0, dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)), + "Handler block not called within expected time window"); + } XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); } @@ -355,11 +469,11 @@ using santa::santad::event_providers::endpoint_security::Message; handler:^(const Message &msg) { dispatch_semaphore_signal(sema); }]); - } - XCTAssertEqual(0, - dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)), - "Handler block not called within expected time window"); + XCTAssertEqual( + 0, dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)), + "Handler block not called within expected time window"); + } XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); } diff --git a/Source/santad/EventProviders/SNTEndpointSecurityEventHandler.h b/Source/santad/EventProviders/SNTEndpointSecurityEventHandler.h index 786a44d2..c61512cd 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityEventHandler.h +++ b/Source/santad/EventProviders/SNTEndpointSecurityEventHandler.h @@ -14,6 +14,10 @@ #import +#include +#include + +#include "Source/santad/DataLayer/WatchItemPolicy.h" #include "Source/santad/EventProviders/EndpointSecurity/Message.h" #include "Source/santad/Metrics.h" @@ -39,4 +43,13 @@ // Called when a client should no longer receive events. - (void)disable; +- (void) + watchItemsCount:(size_t)count + newPaths: + (const std::vector> + &)newPaths + removedPaths: + (const std::vector> &) + removedPaths; + @end diff --git a/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm b/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm index 70c401f6..37f584ba 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizer.mm @@ -40,6 +40,7 @@ #include "Source/santad/EventProviders/EndpointSecurity/Message.h" using santa::santad::EventDisposition; +using santa::santad::data_layer::WatchItemPathType; using santa::santad::data_layer::WatchItemPolicy; using santa::santad::data_layer::WatchItems; using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI; @@ -155,7 +156,7 @@ void PopulatePathTargets(const Message &msg, std::vector &targets) @interface SNTEndpointSecurityFileAccessAuthorizer () @property SNTDecisionCache *decisionCache; -@property BOOL isSubscribed; +@property bool isSubscribed; @end @implementation SNTEndpointSecurityFileAccessAuthorizer { @@ -185,6 +186,9 @@ void PopulatePathTargets(const Message &msg, std::vector &targets) _decisionCache = decisionCache; [self establishClientOrDie]; + + [super enableTargetPathWatching]; + [super unmuteEverything]; } return self; } @@ -347,7 +351,7 @@ void PopulatePathTargets(const Message &msg, std::vector &targets) handler:^(Message &&esMsg) { self->_logger->LogFileAccess( policyVersionCopy, policyNameCopy, esMsg, - self->_enricher->Enrich(*msg->process, EnrichOptions::kLocalOnly), + self->_enricher->Enrich(*esMsg->process, EnrichOptions::kLocalOnly), targetCopy, policyDecision); }]; @@ -380,9 +384,7 @@ void PopulatePathTargets(const Message &msg, std::vector &targets) prevDecision = curDecision; } - [self respondToMessage:msg - withAuthResult:policyResult - cacheable:(policyResult == ES_AUTH_RESULT_ALLOW)]; + [self respondToMessage:msg withAuthResult:policyResult cacheable:false]; } - (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg @@ -402,11 +404,37 @@ void PopulatePathTargets(const Message &msg, std::vector &targets) // ES_EVENT_TYPE_AUTH_CLONE // ES_EVENT_TYPE_AUTH_EXCHANGEDATA // ES_EVENT_TYPE_AUTH_COPYFILE - [super subscribeAndClearCache:{ES_EVENT_TYPE_AUTH_OPEN}]; + if (!self.isSubscribed) { + self.isSubscribed = [super subscribe:{ES_EVENT_TYPE_AUTH_OPEN}]; + [super clearCache]; + } } - (void)disable { - [super unsubscribeAll]; + if (self.isSubscribed) { + if ([super unsubscribeAll]) { + self.isSubscribed = false; + } + [super unmuteEverything]; + } +} + +- (void)watchItemsCount:(size_t)count + newPaths:(const std::vector> &)newPaths + removedPaths: + (const std::vector> &)removedPaths { + if (count == 0) { + [self disable]; + } else { + // Stop watching removed paths + [super unmuteTargetPaths:removedPaths]; + + // Begin watching the added paths + [super muteTargetPaths:newPaths]; + + // begin receiving events (if not already) + [self enable]; + } } @end diff --git a/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizerTest.mm b/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizerTest.mm index 80597b33..415a5248 100644 --- a/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizerTest.mm +++ b/Source/santad/EventProviders/SNTEndpointSecurityFileAccessAuthorizerTest.mm @@ -50,6 +50,13 @@ extern es_auth_result_t FileAccessPolicyDecisionToESAuthResult(FileAccessPolicyD extern bool ShouldLogDecision(FileAccessPolicyDecision decision); extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_result_t result2); +void SetExpectationsForFileAccessAuthorizerInit( + std::shared_ptr mockESApi) { + EXPECT_CALL(*mockESApi, InvertTargetPathMuting).WillOnce(testing::Return(true)); + EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true)); + EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true)); +} + @interface SNTEndpointSecurityFileAccessAuthorizer (Testing) - (NSString *)getCertificateHash:(es_file_t *)esFile; - (FileAccessPolicyDecision)specialCaseForPolicy:(std::shared_ptr)policy @@ -57,6 +64,8 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r - (FileAccessPolicyDecision)applyPolicy: (std::optional>)optionalPolicy toMessage:(const Message &)msg; + +@property bool isSubscribed; @end @interface SNTEndpointSecurityFileAccessAuthorizerTest : XCTestCase @@ -98,6 +107,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r auto mockESApi = std::make_shared(); mockESApi->SetExpectationsESNewClient(); + SetExpectationsForFileAccessAuthorizerInit(mockESApi); SNTEndpointSecurityFileAccessAuthorizer *accessClient = [[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi @@ -229,6 +239,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r auto mockESApi = std::make_shared(); mockESApi->SetExpectationsESNewClient(); mockESApi->SetExpectationsRetainReleaseMessage(); + SetExpectationsForFileAccessAuthorizerInit(mockESApi); SNTEndpointSecurityFileAccessAuthorizer *accessClient = [[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi @@ -309,6 +320,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r auto mockESApi = std::make_shared(); mockESApi->SetExpectationsESNewClient(); mockESApi->SetExpectationsRetainReleaseMessage(); + SetExpectationsForFileAccessAuthorizerInit(mockESApi); SNTEndpointSecurityFileAccessAuthorizer *accessClient = [[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi @@ -445,6 +457,7 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r - (void)testDisable { auto mockESApi = std::make_shared(); mockESApi->SetExpectationsESNewClient(); + SetExpectationsForFileAccessAuthorizerInit(mockESApi); SNTEndpointSecurityFileAccessAuthorizer *accessClient = [[SNTEndpointSecurityFileAccessAuthorizer alloc] initWithESAPI:mockESApi @@ -455,7 +468,10 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r decisionCache:nil]; EXPECT_CALL(*mockESApi, UnsubscribeAll); + EXPECT_CALL(*mockESApi, UnmuteAllPaths).WillOnce(testing::Return(true)); + EXPECT_CALL(*mockESApi, UnmuteAllTargetPaths).WillOnce(testing::Return(true)); + accessClient.isSubscribed = true; [accessClient disable]; XCTBubbleMockVerifyAndClearExpectations(mockESApi.get()); diff --git a/Source/santad/Santad.mm b/Source/santad/Santad.mm index a8da2c10..72cf8d3e 100644 --- a/Source/santad/Santad.mm +++ b/Source/santad/Santad.mm @@ -321,8 +321,11 @@ void SantadMain(std::shared_ptr esapi, std::shared_ptrBeginPeriodicTask(); + if (@available(macOS 13.0, *)) { + // Start monitoring any watched items + // Note: This feature is only enabled on macOS 13.0+ + watch_items->BeginPeriodicTask(); + } [monitor_client enable]; [device_client enable];