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
This commit is contained in:
Matt W
2022-12-21 03:15:01 +01:00
committed by GitHub
parent fec3766da4
commit 60f53bc20a
15 changed files with 405 additions and 36 deletions

View File

@@ -64,6 +64,11 @@ objc_library(
hdrs = ["SantaVnode.h"],
)
objc_library(
name = "Platform",
hdrs = ["Platform.h"],
)
objc_library(
name = "SantaVnodeHash",
srcs = ["SantaVnodeHash.mm"],

34
Source/common/Platform.h Normal file
View File

@@ -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 <Availability.h>
#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

View File

@@ -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",

View File

@@ -18,6 +18,7 @@
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#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;

View File

@@ -19,12 +19,16 @@
#include <ctype.h>
#include <glob.h>
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <iterator>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#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<std::pair<std::string, WatchItemPathType>> paths_to_watch;
std::vector<std::pair<std::string, WatchItemPathType>> 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<SNTEndpointSecurityDynamicEventHandler> &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 {

View File

@@ -19,7 +19,9 @@
#import <Foundation/Foundation.h>
#include <set>
#include <string_view>
#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<EndpointSecurity
virtual bool Subscribe(const Client &client, const std::set<es_event_type_t> &);
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);

View File

@@ -13,11 +13,14 @@
/// limitations under the License.
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
#include <EndpointSecurity/ESTypes.h>
#include <set>
#include <vector>
#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;

View File

@@ -22,6 +22,7 @@
#include <set>
#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<es_event_type_t> &));
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));

View File

@@ -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<std::pair<std::string, WatchItemPathType>> &)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<std::pair<std::string, WatchItemPathType>> &)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 {

View File

@@ -17,9 +17,11 @@
#include <memory>
#include <string>
#include <vector>
#import <Foundation/Foundation.h>
#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<es_event_type_t> &)events;
- (bool)unsubscribeAll;
- (bool)unmuteEverything;
- (bool)enableTargetPathWatching;
- (bool)muteTargetPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
- (bool)unmuteTargetPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
/// Responds to the Message with the given auth result
///

View File

@@ -23,6 +23,7 @@
#include <memory>
#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<MockEndpointSecurityAPI>();
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<MockEndpointSecurityAPI>();
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<MockEndpointSecurityAPI>();
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<MockEndpointSecurityAPI>();
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<std::pair<std::string, WatchItemPathType>> paths = {
{"a", WatchItemPathType::kLiteral},
{"b", WatchItemPathType::kLiteral},
{"c", WatchItemPathType::kPrefix},
};
XCTAssertFalse([client muteTargetPaths:paths]);
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}
- (void)testUnmuteTargetPaths {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
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<std::pair<std::string, WatchItemPathType>> 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<MockEndpointSecurityAPI>();
@@ -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<EnrichedMessage>(
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<EnrichedMessage>(
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<EnrichedMessage> msg) {
dispatch_semaphore_signal(sema);
}];
[client processEnrichedMessage:enrichedMsg
handler:^(std::shared_ptr<EnrichedMessage> 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());
}

View File

@@ -14,6 +14,10 @@
#import <Foundation/Foundation.h>
#include <string>
#include <vector>
#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<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>>
&)newPaths
removedPaths:
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)
removedPaths;
@end

View File

@@ -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<std::string> &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<std::string> &targets)
_decisionCache = decisionCache;
[self establishClientOrDie];
[super enableTargetPathWatching];
[super unmuteEverything];
}
return self;
}
@@ -347,7 +351,7 @@ void PopulatePathTargets(const Message &msg, std::vector<std::string> &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<std::string> &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<std::string> &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<std::pair<std::string, WatchItemPathType>> &)newPaths
removedPaths:
(const std::vector<std::pair<std::string, WatchItemPathType>> &)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

View File

@@ -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<MockEndpointSecurityAPI> 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<WatchItemPolicy>)policy
@@ -57,6 +64,8 @@ extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_r
- (FileAccessPolicyDecision)applyPolicy:
(std::optional<std::shared_ptr<WatchItemPolicy>>)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<MockEndpointSecurityAPI>();
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<MockEndpointSecurityAPI>();
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<MockEndpointSecurityAPI>();
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<MockEndpointSecurityAPI>();
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());

View File

@@ -321,8 +321,11 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
// execution policy appropriately.
[authorizer_client enable];
[tamper_client enable];
// Start monitoring any watched items
watch_items->BeginPeriodicTask();
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];