Compare commits

...

10 Commits

Author SHA1 Message Date
Russell Hancox
feac080fa7 sync: Permit XSRF header between sync stages/sessions (#1081) 2023-04-27 10:52:35 -04:00
Nick Gregory
d0f2a0ac4d One more TSAN fix (#1079) 2023-04-26 17:30:06 +02:00
Pete Markowsky
7fc06ea9d8 Make the sync client content encoding a tunable (#1076)
Make the sync client content encoding a tunable.

This makes the sync client's content encoding a tunable so that it can be
compatible with more sync servers.

Removed the "backwards compatibility" config option.

---------

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2023-04-24 15:00:29 +02:00
Russell Hancox
1dfeeac936 README: Add more badges (#1075) 2023-04-21 09:54:33 -04:00
Matt W
ac9b5d9399 Cache flush metrics (#1074)
* Added a reason enum when flushing auth result cache

* Set metrics when auth result cache is flushed.
2023-04-20 16:47:06 -04:00
Matt W
7f3f1c5448 Process unmount events first (#1073) 2023-04-19 11:13:13 -04:00
Russell Hancox
46efd6893f config: Add EnableSilentTTYMode key to disable TTY notifications. (#1072)
Fixes #1067
2023-04-19 10:38:24 -04:00
Matt W
50232578d6 Fix string length issues (#1070) 2023-04-13 10:03:52 -04:00
Russell Hancox
d83be03a20 sync: Add more complete XSSI prefix to be stripped. (#1068)
Sync will try stripping both the new longer prefix and the existing short prefix if the response data begins with either. This should have no impact on existing sync servers but will allow sync servers in the future to use the longer prefix if they wish.
2023-04-07 15:27:41 -04:00
Russell Hancox
119b29b534 GUI: Device event window, handle empty remount args (#1066) 2023-04-05 16:34:05 -04:00
32 changed files with 398 additions and 165 deletions

View File

@@ -18,7 +18,7 @@ jobs:
DYLIB_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/${CLANG_VERSION}/lib/darwin/libclang_rt.${{ matrix.sanitizer }}_osx_dynamic.dylib"
bazel test --config=${{ matrix.sanitizer }} \
--test_strategy=exclusive --test_output=errors \
--test_strategy=exclusive --test_output=all \
--test_env=DYLD_INSERT_LIBRARIES=${DYLIB_PATH} \
--runs_per_test 5 -t- :unit_tests \
--define=SANTA_BUILD_TYPE=adhoc

View File

@@ -1,4 +1,10 @@
# Santa [![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml)
# Santa
[![license](https://img.shields.io/github/license/google/santa)](https://github.com/google/santa/blob/main/LICENSE)
[![CI](https://github.com/google/santa/actions/workflows/ci.yml/badge.svg)](https://github.com/google/santa/actions/workflows/ci.yml)
[![latest release](https://img.shields.io/github/v/release/google/santa.svg)](https://github.com/google/santa/releases/latest)
[![latest release date](https://img.shields.io/github/release-date/google/santa.svg)](https://github.com/google/santa/releases/latest)
[![downloads](https://img.shields.io/github/downloads/google/santa/latest/total)](https://github.com/google/santa/releases/latest)
<p align="center">
<img src="https://raw.githubusercontent.com/google/santa/main/Source/gui/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />

View File

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

View File

@@ -129,6 +129,12 @@ typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
SNTSyncStatusTypeUnknown,
};
typedef NS_ENUM(NSInteger, SNTSyncContentEncoding) {
SNTSyncContentEncodingNone,
SNTSyncContentEncodingDeflate,
SNTSyncContentEncodingGzip,
};
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,

View File

@@ -291,6 +291,14 @@
///
@property(readonly, nonatomic) BOOL enableSilentMode;
///
/// When silent TTY mode is enabled, Santa will not emit TTY notifications for
/// blocked processes.
///
/// Defaults to NO.
///
@property(readonly, nonatomic) BOOL enableSilentTTYMode;
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
@@ -402,6 +410,8 @@
///
@property(nonatomic) BOOL syncCleanRequired;
#pragma mark - USB Settings
///
/// USB Mount Blocking. Defaults to false.
///
@@ -512,6 +522,12 @@
///
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
///
/// If set, "santactl sync" will use the supplied "Content-Encoding", possible
/// settings include "gzip", "deflate", "none". If empty defaults to "deflate".
///
@property(readonly, nonatomic) SNTSyncContentEncoding syncClientContentEncoding;
///
/// Contains the FCM project name.
///

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTCommonEnums.h"
#include <sys/stat.h>
@@ -72,6 +73,7 @@ static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kEnableSilentModeKey = @"EnableSilentMode";
static NSString *const kEnableSilentTTYModeKey = @"EnableSilentTTYMode";
static NSString *const kAboutTextKey = @"AboutText";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
@@ -107,8 +109,7 @@ static NSString *const kEnableForkAndExitLogging = @"EnableForkAndExitLogging";
static NSString *const kIgnoreOtherEndpointSecurityClients = @"IgnoreOtherEndpointSecurityClients";
static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
static NSString *const kEnableBackwardsCompatibleContentEncoding =
@"EnableBackwardsCompatibleContentEncoding";
static NSString *const kClientContentEncoding = @"SyncClientContentEncoding";
static NSString *const kFCMProject = @"FCMProject";
static NSString *const kFCMEntity = @"FCMEntity";
@@ -128,7 +129,6 @@ static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
static NSString *const kEnableAllEventUploadKey = @"EnableAllEventUpload";
static NSString *const kDisableUnknownEventUploadKey = @"DisableUnknownEventUpload";
// TODO(markowsky): move these to sync server only.
static NSString *const kMetricFormat = @"MetricFormat";
static NSString *const kMetricURL = @"MetricURL";
static NSString *const kMetricExportInterval = @"MetricExportInterval";
@@ -181,6 +181,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kEnableSilentModeKey : number,
kEnableSilentTTYModeKey : number,
kAboutTextKey : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
@@ -198,6 +199,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kClientAuthCertificatePasswordKey : string,
kClientAuthCertificateCNKey : string,
kClientAuthCertificateIssuerKey : string,
kClientContentEncoding : string,
kServerAuthRootsDataKey : data,
kServerAuthRootsFileKey : string,
kMachineOwnerKey : string,
@@ -219,7 +221,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kEnableForkAndExitLogging : number,
kIgnoreOtherEndpointSecurityClients : number,
kEnableDebugLogging : number,
kEnableBackwardsCompatibleContentEncoding : number,
kFCMProject : string,
kFCMEntity : string,
kFCMAPIKey : string,
@@ -461,10 +462,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBackwardsCompatibleContentEncoding {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
return [self configStateSet];
}
@@ -645,6 +642,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (BOOL)enableSilentTTYMode {
NSNumber *number = self.configState[kEnableSilentTTYModeKey];
return number ? [number boolValue] : NO;
}
- (NSString *)aboutText {
return self.configState[kAboutTextKey];
}
@@ -708,6 +710,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return self.configState[kClientAuthCertificateIssuerKey];
}
- (SNTSyncContentEncoding)syncClientContentEncoding {
NSString *contentEncoding = [self.configState[kClientContentEncoding] lowercaseString];
if ([contentEncoding isEqualToString:@"deflate"]) {
return SNTSyncContentEncodingDeflate;
} else if ([contentEncoding isEqualToString:@"gzip"]) {
return SNTSyncContentEncodingGzip;
} else if ([contentEncoding isEqualToString:@"none"]) {
return SNTSyncContentEncodingNone;
} else {
// Ensure we have the same default zlib behavior Santa's always had otherwise.
return SNTSyncContentEncodingDeflate;
}
}
- (NSData *)syncServerAuthRootsData {
return self.configState[kServerAuthRootsDataKey];
}
@@ -881,11 +897,6 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [number boolValue] || self.debugFlag;
}
- (BOOL)enableBackwardsCompatibleContentEncoding {
NSNumber *number = self.configState[kEnableBackwardsCompatibleContentEncoding];
return number ? [number boolValue] : NO;
}
- (NSString *)fcmProject {
return self.configState[kFCMProject];
}

35
Source/common/String.h Normal file
View File

@@ -0,0 +1,35 @@
/// Copyright 2023 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__STRING_H
#define SANTA__COMMON__STRING_H
#include <Foundation/Foundation.h>
#include <string>
#include <string_view>
namespace santa::common {
static inline std::string_view NSStringToUTF8StringView(NSString *str) {
return std::string_view(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}
static inline std::string NSStringToUTF8String(NSString *str) {
return std::string(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
}
} // namespace santa::common
#endif

View File

@@ -37,7 +37,7 @@ struct SNTDeviceMessageWindowView: View {
Text("Device Name").bold()
Text("Device BSD Path").bold()
if event!.remountArgs.count > 0 {
if event!.remountArgs?.count ?? 0 > 0 {
Text("Remount Mode").bold()
}
}
@@ -46,7 +46,7 @@ struct SNTDeviceMessageWindowView: View {
Text(event!.mntonname)
Text(event!.mntfromname)
if event!.remountArgs.count > 0 {
if event!.remountArgs?.count ?? 0 > 0 {
Text(event!.readableRemountArgs())
}
}

View File

@@ -37,14 +37,14 @@ objc_library(
objc_library(
name = "santactl_lib",
srcs = [
"main.m",
"Commands/SNTCommandFileInfo.m",
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandVersion.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandSync.m",
"Commands/SNTCommandVersion.m",
"main.m",
] + select({
"//:opt_build": [],
"//conditions:default": [

View File

@@ -49,6 +49,7 @@ objc_library(
":WatchItemPolicy",
"//Source/common:PrefixTree",
"//Source/common:SNTLogging",
"//Source/common:String",
"//Source/common:Unit",
],
)
@@ -232,6 +233,7 @@ objc_library(
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SantaVnode",
"//Source/common:String",
"@MOLCodesignChecker",
],
)
@@ -382,6 +384,7 @@ objc_library(
":EndpointSecurityClient",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SantaCache",
"//Source/common:SantaVnode",
"//Source/common:SantaVnodeHash",
@@ -468,6 +471,7 @@ objc_library(
],
deps = [
":EndpointSecurityMessage",
"//Source/common:String",
],
)
@@ -501,6 +505,7 @@ objc_library(
"//Source/common:SNTConfigurator",
"//Source/common:SNTLogging",
"//Source/common:SNTStoredEvent",
"//Source/common:String",
"//Source/common:santa_cc_proto_library_wrapper",
],
)

View File

@@ -34,8 +34,12 @@
#import "Source/common/PrefixTree.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/String.h"
#import "Source/common/Unit.h"
#include "Source/santad/DataLayer/WatchItemPolicy.h"
using santa::common::NSStringToUTF8String;
using santa::common::NSStringToUTF8StringView;
using santa::common::PrefixTree;
using santa::common::Unit;
using santa::santad::data_layer::WatchItemPathType;
@@ -255,7 +259,7 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
return Unit{};
}
path_list.push_back({std::string(path_str.UTF8String, path_str.length), path_type});
path_list.push_back({NSStringToUTF8String(path_str), path_type});
} else if ([path isKindOfClass:[NSString class]]) {
if (!LenRangeValidator(1, PATH_MAX)(path, err)) {
PopulateError(err, [NSString stringWithFormat:@"Invalid path length: %@",
@@ -264,8 +268,8 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
return Unit{};
}
path_list.push_back({std::string(((NSString *)path).UTF8String, ((NSString *)path).length),
kWatchItemPolicyDefaultPathType});
path_list.push_back(
{NSStringToUTF8String(((NSString *)path)), kWatchItemPolicyDefaultPathType});
} else {
PopulateError(
err, [NSString stringWithFormat:
@@ -340,12 +344,11 @@ std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *wat
}
proc_list.push_back(WatchItemPolicy::Process(
std::string([(process[kWatchItemConfigKeyProcessesBinaryPath] ?: @"") UTF8String]),
std::string([(process[kWatchItemConfigKeyProcessesSigningID] ?: @"") UTF8String]),
std::string([(process[kWatchItemConfigKeyProcessesTeamID] ?: @"") UTF8String]),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesBinaryPath] ?: @""),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesSigningID] ?: @""),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesTeamID] ?: @""),
HexStringToBytes(process[kWatchItemConfigKeyProcessesCDHash]),
std::string(
[(process[kWatchItemConfigKeyProcessesCertificateSha256] ?: @"") UTF8String]),
NSStringToUTF8String(process[kWatchItemConfigKeyProcessesCertificateSha256] ?: @""),
process[kWatchItemConfigKeyProcessesPlatformBinary]
? std::make_optional(
(bool)[process[kWatchItemConfigKeyProcessesPlatformBinary] boolValue])
@@ -423,8 +426,8 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
for (const PathAndTypePair &path_type_pair : std::get<PathList>(path_list)) {
policies.push_back(std::make_shared<WatchItemPolicy>(
[name UTF8String], path_type_pair.first, path_type_pair.second, allow_read_access, audit_only,
std::get<ProcessList>(proc_list)));
NSStringToUTF8StringView(name), path_type_pair.first, path_type_pair.second,
allow_read_access, audit_only, std::get<ProcessList>(proc_list)));
}
return true;
@@ -645,7 +648,7 @@ void WatchItems::UpdateCurrentState(
std::swap(currently_monitored_paths_, new_monitored_paths);
current_config_ = new_config;
if (new_config) {
policy_version_ = [new_config[kWatchItemConfigKeyVersion] UTF8String];
policy_version_ = NSStringToUTF8String(new_config[kWatchItemConfigKeyVersion]);
} else {
policy_version_ = "";
}

View File

@@ -22,6 +22,7 @@
#include <memory>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTMetricSet.h"
#include "Source/common/SantaCache.h"
#import "Source/common/SantaVnode.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
@@ -33,16 +34,29 @@ enum class FlushCacheMode {
kAllCaches,
};
enum class FlushCacheReason {
kClientModeChanged,
kPathRegexChanged,
kRulesChanged,
kStaticRulesChanged,
kExplicitCommand,
kFilesystemUnmounted,
};
class AuthResultCache {
public:
// Santa currently only flushes caches when new DENY rules are added, not
// ALLOW rules. This means this value should be low enough so that if a
// ALLOW rules. This means cache_deny_time_ms should be low enough so that if a
// previously denied binary is allowed, it can be re-executed by the user in a
// timely manner. But the value should be high enough to allow the cache to be
// effective in the event the binary is executed in rapid succession.
static std::unique_ptr<AuthResultCache> Create(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
SNTMetricSet *metric_set, uint64_t cache_deny_time_ms = 1500);
AuthResultCache(
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
uint64_t cache_deny_time_ms = 1500);
SNTMetricCounter *flush_count, uint64_t cache_deny_time_ms = 1500);
virtual ~AuthResultCache();
AuthResultCache(AuthResultCache &&other) = delete;
@@ -55,7 +69,7 @@ class AuthResultCache {
virtual SNTAction CheckCache(const es_file_t *es_file);
virtual SNTAction CheckCache(SantaVnode vnode_id);
virtual void FlushCache(FlushCacheMode mode);
virtual void FlushCache(FlushCacheMode mode, FlushCacheReason reason);
virtual NSArray<NSNumber *> *CacheCounts();
@@ -66,6 +80,7 @@ class AuthResultCache {
SantaCache<SantaVnode, uint64_t> *nonroot_cache_;
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi_;
SNTMetricCounter *flush_count_;
uint64_t root_devno_;
uint64_t cache_deny_time_ns_;
dispatch_queue_t q_;

View File

@@ -25,6 +25,13 @@
using santa::santad::event_providers::endpoint_security::Client;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
static NSString *const kFlushCacheReasonClientModeChanged = @"ClientModeChanged";
static NSString *const kFlushCacheReasonPathRegexChanged = @"PathRegexChanged";
static NSString *const kFlushCacheReasonRulesChanged = @"RulesChanged";
static NSString *const kFlushCacheReasonStaticRulesChanged = @"StaticRulesChanged";
static NSString *const kFlushCacheReasonExplicitCommand = @"ExplicitCommand";
static NSString *const kFlushCacheReasonFilesystemUnmounted = @"FilesystemUnmounted";
namespace santa::santad::event_providers {
static inline uint64_t GetCurrentUptime() {
@@ -44,9 +51,36 @@ static inline uint64_t TimestampFromCachedValue(uint64_t cachedValue) {
return (cachedValue & ~(0xFF00000000000000));
}
NSString *const FlushCacheReasonToString(FlushCacheReason reason) {
switch (reason) {
case FlushCacheReason::kClientModeChanged: return kFlushCacheReasonClientModeChanged;
case FlushCacheReason::kPathRegexChanged: return kFlushCacheReasonPathRegexChanged;
case FlushCacheReason::kRulesChanged: return kFlushCacheReasonRulesChanged;
case FlushCacheReason::kStaticRulesChanged: return kFlushCacheReasonStaticRulesChanged;
case FlushCacheReason::kExplicitCommand: return kFlushCacheReasonExplicitCommand;
case FlushCacheReason::kFilesystemUnmounted: return kFlushCacheReasonFilesystemUnmounted;
default:
[NSException raise:@"Invalid reason" format:@"Unknown reason value: %d", reason];
return nil;
}
}
std::unique_ptr<AuthResultCache> AuthResultCache::Create(std::shared_ptr<EndpointSecurityAPI> esapi,
SNTMetricSet *metric_set,
uint64_t cache_deny_time_ms) {
SNTMetricCounter *flush_count =
[metric_set counterWithName:@"/santa/flush_count"
fieldNames:@[ @"Reason" ]
helpText:@"Count of times the auth result cache is flushed by reason"];
return std::make_unique<AuthResultCache>(esapi, flush_count, cache_deny_time_ms);
}
AuthResultCache::AuthResultCache(std::shared_ptr<EndpointSecurityAPI> esapi,
uint64_t cache_deny_time_ms)
: esapi_(esapi), cache_deny_time_ns_(cache_deny_time_ms * NSEC_PER_MSEC) {
SNTMetricCounter *flush_count, uint64_t cache_deny_time_ms)
: esapi_(esapi),
flush_count_(flush_count),
cache_deny_time_ns_(cache_deny_time_ms * NSEC_PER_MSEC) {
root_cache_ = new SantaCache<SantaVnode, uint64_t>();
nonroot_cache_ = new SantaCache<SantaVnode, uint64_t>();
@@ -118,7 +152,7 @@ SantaCache<SantaVnode, uint64_t> *AuthResultCache::CacheForVnodeID(SantaVnode vn
return (vnode_id.fsid == root_devno_ || root_devno_ == 0) ? root_cache_ : nonroot_cache_;
}
void AuthResultCache::FlushCache(FlushCacheMode mode) {
void AuthResultCache::FlushCache(FlushCacheMode mode, FlushCacheReason reason) {
nonroot_cache_->clear();
if (mode == FlushCacheMode::kAllCaches) {
root_cache_->clear();
@@ -134,6 +168,8 @@ void AuthResultCache::FlushCache(FlushCacheMode mode) {
shared_esapi->ClearCache(Client());
});
}
[flush_count_ incrementForFieldValues:@[ FlushCacheReasonToString(reason) ]];
}
NSArray<NSNumber *> *AuthResultCache::CacheCounts() {

View File

@@ -29,6 +29,13 @@
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
namespace santa::santad::event_providers {
extern NSString *const FlushCacheReasonToString(FlushCacheReason reason);
}
using santa::santad::event_providers::FlushCacheReasonToString;
// Grab the st_dev number of the root volume to match the root cache
static uint64_t RootDevno() {
@@ -66,14 +73,14 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
- (void)testEmptyCacheExpectedNumberOfCacheCounts {
auto esapi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(esapi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(esapi, nil);
AssertCacheCounts(cache, 0, 0);
}
- (void)testBasicOperation {
auto esapi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(esapi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(esapi, nil);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
es_file_t nonrootFile = MakeCacheableFile(RootDevno() + 123, 222);
@@ -110,7 +117,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
- (void)testFlushCache {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(mockESApi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(mockESApi, nil);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
es_file_t nonrootFile = MakeCacheableFile(RootDevno() + 123, 111);
@@ -121,7 +128,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
AssertCacheCounts(cache, 1, 1);
// Flush non-root only
cache->FlushCache(FlushCacheMode::kNonRootOnly);
cache->FlushCache(FlushCacheMode::kNonRootOnly, FlushCacheReason::kClientModeChanged);
AssertCacheCounts(cache, 1, 0);
@@ -138,7 +145,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
dispatch_semaphore_signal(sema);
return true;
}));
cache->FlushCache(FlushCacheMode::kAllCaches);
cache->FlushCache(FlushCacheMode::kAllCaches, FlushCacheReason::kClientModeChanged);
XCTAssertEqual(0,
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC)),
@@ -151,7 +158,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
- (void)testCacheStateMachine {
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
auto cache = std::make_shared<AuthResultCache>(mockESApi);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(mockESApi, nil);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
@@ -193,7 +200,7 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
// Create a cache with a lowered cache expiry value
uint64_t expiryMS = 250;
auto cache = std::make_shared<AuthResultCache>(mockESApi, expiryMS);
std::shared_ptr<AuthResultCache> cache = AuthResultCache::Create(mockESApi, nil, expiryMS);
es_file_t rootFile = MakeCacheableFile(RootDevno(), 111);
@@ -215,4 +222,21 @@ static inline void AssertCacheCounts(std::shared_ptr<AuthResultCache> cache, uin
AssertCacheCounts(cache, 0, 0);
}
- (void)testFlushCacheReasonToString {
std::map<FlushCacheReason, NSString *> reasonToString = {
{FlushCacheReason::kClientModeChanged, @"ClientModeChanged"},
{FlushCacheReason::kPathRegexChanged, @"PathRegexChanged"},
{FlushCacheReason::kRulesChanged, @"RulesChanged"},
{FlushCacheReason::kStaticRulesChanged, @"StaticRulesChanged"},
{FlushCacheReason::kExplicitCommand, @"ExplicitCommand"},
{FlushCacheReason::kFilesystemUnmounted, @"FilesystemUnmounted"},
};
for (const auto &kv : reasonToString) {
XCTAssertEqualObjects(FlushCacheReasonToString(kv.first), kv.second);
}
XCTAssertThrows(FlushCacheReasonToString((FlushCacheReason)12345));
}
@end

View File

@@ -234,7 +234,7 @@ class MockAuthResultCache : public AuthResultCache {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, CheckCache)
.WillOnce(testing::Return(SNTActionRequestBinary))
.WillOnce(testing::Return(SNTActionRequestBinary))
@@ -301,7 +301,7 @@ class MockAuthResultCache : public AuthResultCache {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, AddToCache(&execFile, SNTActionRespondAllowCompiler))
.WillOnce(testing::Return(true));
EXPECT_CALL(*mockAuthCache, AddToCache(&execFile, SNTActionRespondAllow))

View File

@@ -414,6 +414,13 @@ using santa::santad::event_providers::endpoint_security::Message;
[client processEnrichedMessage:enrichedMsg
handler:^(std::shared_ptr<EnrichedMessage> msg) {
// reset the shared_ptr to drop the held message.
// This is a workaround for a TSAN only false positive
// which happens if we switch back to the sem wait
// after signaling, but _before_ the implicit release
// of msg. In that case, the mock verify and the
// call of the mock's Release method can data race.
msg.reset();
dispatch_semaphore_signal(sema);
}];

View File

@@ -33,6 +33,7 @@
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::Message;
using santa::santad::logs::endpoint_security::Logger;
@@ -172,6 +173,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)handleMessage:(Message &&)esMsg
recordEventMetrics:(void (^)(EventDisposition))recordEventMetrics {
// Process the unmount event first so that caches are flushed before any
// other potential early returns.
if (esMsg->event_type == ES_EVENT_TYPE_NOTIFY_UNMOUNT) {
self->_authResultCache->FlushCache(FlushCacheMode::kNonRootOnly,
FlushCacheReason::kFilesystemUnmounted);
recordEventMetrics(EventDisposition::kProcessed);
return;
}
if (!self.blockUSBMount) {
// TODO: We should also unsubscribe from events when this isn't set, but
// this is generally a low-volume event type.
@@ -180,12 +190,6 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
if (esMsg->event_type == ES_EVENT_TYPE_NOTIFY_UNMOUNT) {
self->_authResultCache->FlushCache(FlushCacheMode::kNonRootOnly);
recordEventMetrics(EventDisposition::kProcessed);
return;
}
[self processMessage:std::move(esMsg)
handler:^(const Message &msg) {
es_auth_result_t result = [self handleAuthMount:msg];

View File

@@ -39,13 +39,14 @@
using santa::santad::EventDisposition;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::santad::event_providers::endpoint_security::Message;
class MockAuthResultCache : public AuthResultCache {
public:
using AuthResultCache::AuthResultCache;
MOCK_METHOD(void, FlushCache, (FlushCacheMode mode));
MOCK_METHOD(void, FlushCache, (FlushCacheMode mode, FlushCacheReason reason));
};
@interface SNTEndpointSecurityDeviceManager (Testing)
@@ -316,7 +317,7 @@ class MockAuthResultCache : public AuthResultCache {
mockESApi->SetExpectationsESNewClient();
mockESApi->SetExpectationsRetainReleaseMessage();
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, FlushCache);
SNTEndpointSecurityDeviceManager *deviceManager =

View File

@@ -105,7 +105,7 @@ class MockLogger : public Logger {
auto mockEnricher = std::make_shared<MockEnricher>();
EXPECT_CALL(*mockEnricher, Enrich).WillOnce(testing::Return(enrichedMsg));
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr);
auto mockAuthCache = std::make_shared<MockAuthResultCache>(nullptr, nil);
EXPECT_CALL(*mockAuthCache, RemoveFromCache(&targetFile)).Times(1);
dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);

View File

@@ -23,6 +23,7 @@
#include <sys/wait.h>
#include <time.h>
#include <functional>
#include <optional>
#include <string_view>
@@ -30,6 +31,7 @@
#import "Source/common/SNTConfigurator.h"
#include "Source/common/SNTLogging.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/String.h"
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h"
#import "Source/santad/SNTDecisionCache.h"
@@ -38,6 +40,7 @@
using google::protobuf::Arena;
using google::protobuf::Timestamp;
using santa::common::NSStringToUTF8StringView;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::EnrichedClose;
using santa::santad::event_providers::endpoint_security::EnrichedEventType;
@@ -94,15 +97,15 @@ static inline void EncodePath(std::string *buf, const es_file_t *es_file) {
buf->append(std::string_view(es_file->path.data, es_file->path.length));
}
static inline void EncodeString(std::string *buf, NSString *value) {
static inline void EncodeString(std::function<std::string *()> lazy_f, NSString *value) {
if (value) {
buf->append(std::string_view([value UTF8String], [value length]));
lazy_f()->append(NSStringToUTF8StringView(value));
}
}
static inline void EncodeString(std::string *buf, std::string_view value) {
static inline void EncodeString(std::function<std::string *()> lazy_f, std::string_view value) {
if (value.length() > 0) {
buf->append(std::string_view(value.data(), value.length()));
lazy_f()->append(value);
}
}
@@ -125,7 +128,7 @@ static inline void EncodeGroupInfo(::pbv1::GroupInfo *pb_group_info, gid_t gid,
static inline void EncodeHash(::pbv1::Hash *pb_hash, NSString *sha256) {
if (sha256) {
pb_hash->set_type(::pbv1::Hash::HASH_ALGO_SHA256);
pb_hash->set_hash([sha256 UTF8String], [sha256 length]);
EncodeString([pb_hash] { return pb_hash->mutable_hash(); }, sha256);
}
}
@@ -162,7 +165,7 @@ static inline void EncodeFileInfo(::pbv1::FileInfo *pb_file, const es_file_t *es
static inline void EncodeFileInfoLight(::pbv1::FileInfoLight *pb_file, std::string_view path,
bool truncated) {
EncodeString(pb_file->mutable_path(), path);
EncodeString([pb_file] { return pb_file->mutable_path(); }, path);
pb_file->set_truncated(truncated);
}
@@ -262,9 +265,7 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
EncodeHash(pb_cert_info->mutable_hash(), cert_hash);
}
if (common_name) {
pb_cert_info->set_common_name([common_name UTF8String], [common_name length]);
}
EncodeString([pb_cert_info] { return pb_cert_info->mutable_common_name(); }, common_name);
}
::pbv1::Execution::Decision GetDecisionEnum(SNTEventState event_state) {
@@ -356,7 +357,7 @@ static inline void EncodeCertificateInfo(::pbv1::CertificateInfo *pb_cert_info,
::pbv1::SantaMessage *santa_msg = Arena::CreateMessage<::pbv1::SantaMessage>(arena);
if (EnabledMachineID()) {
EncodeString(santa_msg->mutable_machine_id(), MachineID());
EncodeString([santa_msg] { return santa_msg->mutable_machine_id(); }, MachineID());
}
EncodeTimestamp(santa_msg->mutable_event_time(), event_time);
EncodeTimestamp(santa_msg->mutable_processed_time(), processed_time);
@@ -491,18 +492,11 @@ std::vector<uint8_t> Protobuf::SerializeMessage(const EnrichedExec &msg, SNTCach
EncodeCertificateInfo(pb_exec->mutable_certificate_info(), cd.certSHA256, cd.certCommonName);
}
if (cd.decisionExtra) {
pb_exec->set_explain([cd.decisionExtra UTF8String], [cd.decisionExtra length]);
}
if (cd.quarantineURL) {
pb_exec->set_quarantine_url([cd.quarantineURL UTF8String], [cd.quarantineURL length]);
}
EncodeString([pb_exec] { return pb_exec->mutable_explain(); }, cd.decisionExtra);
EncodeString([pb_exec] { return pb_exec->mutable_quarantine_url(); }, cd.quarantineURL);
NSString *orig_path = Utilities::OriginalPathForTranslocation(msg.es_msg().event.exec.target);
if (orig_path) {
pb_exec->set_original_path([orig_path UTF8String], [orig_path length]);
}
EncodeString([pb_exec] { return pb_exec->mutable_original_path(); }, orig_path);
return FinalizeProto(santa_msg);
}
@@ -594,8 +588,9 @@ std::vector<uint8_t> Protobuf::SerializeFileAccess(const std::string &policy_ver
EncodeProcessInfo(file_access->mutable_instigator(), msg->version, msg->process,
enriched_process);
EncodeFileInfoLight(file_access->mutable_target(), target, false);
EncodeString(file_access->mutable_policy_version(), policy_version);
EncodeString(file_access->mutable_policy_name(), policy_name);
EncodeString([file_access] { return file_access->mutable_policy_version(); }, policy_version);
EncodeString([file_access] { return file_access->mutable_policy_name(); }, policy_name);
file_access->set_access_type(GetAccessType(msg->event_type));
file_access->set_policy_decision(GetPolicyDecision(decision));
@@ -629,10 +624,12 @@ std::vector<uint8_t> Protobuf::SerializeBundleHashingEvent(SNTStoredEvent *event
EncodeHash(pb_bundle->mutable_file_hash(), event.fileSHA256);
EncodeHash(pb_bundle->mutable_bundle_hash(), event.fileBundleHash);
pb_bundle->set_bundle_name([NonNull(event.fileBundleName) UTF8String]);
pb_bundle->set_bundle_id([NonNull(event.fileBundleID) UTF8String]);
pb_bundle->set_bundle_path([NonNull(event.fileBundlePath) UTF8String]);
pb_bundle->set_path([NonNull(event.filePath) UTF8String]);
EncodeString([pb_bundle] { return pb_bundle->mutable_bundle_name(); },
NonNull(event.fileBundleName));
EncodeString([pb_bundle] { return pb_bundle->mutable_bundle_id(); }, NonNull(event.fileBundleID));
EncodeString([pb_bundle] { return pb_bundle->mutable_bundle_path(); },
NonNull(event.fileBundlePath));
EncodeString([pb_bundle] { return pb_bundle->mutable_path(); }, NonNull(event.filePath));
return FinalizeProto(santa_msg);
}
@@ -652,14 +649,14 @@ static void EncodeDisk(::pbv1::Disk *pb_disk, ::pbv1::Disk_Action action, NSDict
stringWithFormat:@"%@ %@", NonNull(props[@"DADeviceVendor"]), NonNull(props[@"DADeviceModel"])];
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
EncodeString(pb_disk->mutable_mount(), [props[@"DAVolumePath"] path]);
EncodeString(pb_disk->mutable_volume(), props[@"DAVolumeName"]);
EncodeString(pb_disk->mutable_bsd_name(), props[@"DAMediaBSDName"]);
EncodeString(pb_disk->mutable_fs(), props[@"DAVolumeKind"]);
EncodeString(pb_disk->mutable_model(), model);
EncodeString(pb_disk->mutable_serial(), serial);
EncodeString(pb_disk->mutable_bus(), props[@"DADeviceProtocol"]);
EncodeString(pb_disk->mutable_dmg_path(), dmg_path);
EncodeString([pb_disk] { return pb_disk->mutable_mount(); }, [props[@"DAVolumePath"] path]);
EncodeString([pb_disk] { return pb_disk->mutable_volume(); }, props[@"DAVolumeName"]);
EncodeString([pb_disk] { return pb_disk->mutable_bsd_name(); }, props[@"DAMediaBSDName"]);
EncodeString([pb_disk] { return pb_disk->mutable_fs(); }, props[@"DAVolumeKind"]);
EncodeString([pb_disk] { return pb_disk->mutable_model(); }, model);
EncodeString([pb_disk] { return pb_disk->mutable_serial(); }, serial);
EncodeString([pb_disk] { return pb_disk->mutable_bus(); }, props[@"DADeviceProtocol"]);
EncodeString([pb_disk] { return pb_disk->mutable_dmg_path(); }, dmg_path);
if (props[@"DAAppearanceTime"]) {
// Note: `DAAppearanceTime` is set via `CFAbsoluteTimeGetCurrent`, which uses the defined

View File

@@ -51,8 +51,7 @@ class SanitizableString {
friend std::ostream &operator<<(std::ostream &ss, const SanitizableString &sani_string);
private:
const char *data_;
size_t length_;
std::string_view data_;
mutable bool sanitized_ = false;
mutable std::optional<std::string> sanitized_string_;
};

View File

@@ -14,37 +14,35 @@
#include "Source/santad/Logs/EndpointSecurity/Serializers/SanitizableString.h"
#include "Source/common/String.h"
using santa::common::NSStringToUTF8StringView;
namespace santa::santad::logs::endpoint_security::serializers {
SanitizableString::SanitizableString(const es_file_t *file)
: data_(file->path.data), length_(file->path.length) {}
: data_(file->path.data, file->path.length) {}
SanitizableString::SanitizableString(const es_string_token_t &tok)
: data_(tok.data), length_(tok.length) {}
SanitizableString::SanitizableString(const es_string_token_t &tok) : data_(tok.data, tok.length) {}
SanitizableString::SanitizableString(NSString *str)
: data_([str UTF8String]), length_([str length]) {}
SanitizableString::SanitizableString(NSString *str) : data_(NSStringToUTF8StringView(str)) {}
SanitizableString::SanitizableString(const char *str, size_t len) : data_(str), length_(len) {}
SanitizableString::SanitizableString(const char *str, size_t len) : data_(str, len) {}
std::string_view SanitizableString::String() const {
return std::string_view(data_, length_);
return data_;
}
std::string_view SanitizableString::Sanitized() const {
if (!sanitized_) {
sanitized_ = true;
sanitized_string_ = SanitizeString(data_, length_);
sanitized_string_ = SanitizeString(data_.data(), data_.length());
}
if (sanitized_string_.has_value()) {
return sanitized_string_.value();
} else {
if (data_) {
return std::string_view(data_, length_);
} else {
return "";
}
return data_;
}
}

View File

@@ -39,6 +39,7 @@ using santa::santad::data_layer::WatchItems;
using santa::santad::data_layer::WatchItemsState;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::santad::logs::endpoint_security::Logger;
// Globals used by the santad watchdog thread
@@ -84,12 +85,9 @@ double watchdogRAMPeak = 0;
reply([counts[0] unsignedLongLongValue], [counts[1] unsignedLongLongValue]);
}
- (void)flushAllCaches {
self->_authResultCache->FlushCache(FlushCacheMode::kAllCaches);
}
- (void)flushCache:(void (^)(BOOL))reply {
[self flushAllCaches];
self->_authResultCache->FlushCache(FlushCacheMode::kAllCaches,
FlushCacheReason::kExplicitCommand);
reply(YES);
}
@@ -125,7 +123,7 @@ double watchdogRAMPeak = 0;
// The actual cache flushing happens after the new rules have been added to the database.
if (flushCache) {
LOGI(@"Flushing caches");
[self flushAllCaches];
self->_authResultCache->FlushCache(FlushCacheMode::kAllCaches, FlushCacheReason::kRulesChanged);
}
reply(error);

View File

@@ -36,6 +36,7 @@
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
#include "Source/common/SantaVnode.h"
#include "Source/common/String.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/SNTDecisionCache.h"
@@ -293,7 +294,7 @@ static NSString *const kPrinterProxyPostMonterey =
NSAttributedString *s = [SNTBlockMessage attributedBlockMessageForEvent:se
customMessage:cd.customMsg];
if (targetProc->tty && targetProc->tty->path.length > 0) {
if (targetProc->tty && targetProc->tty->path.length > 0 && !config.enableSilentTTYMode) {
NSMutableString *msg = [NSMutableString stringWithCapacity:1024];
[msg appendFormat:@"\n\033[1mSanta\033[0m\n\n%@\n\n", s.string];
[msg appendFormat:@"\033[1mPath: \033[0m %@\n"
@@ -360,7 +361,8 @@ static NSString *const kPrinterProxyPostMonterey =
- (void)printMessage:(NSString *)msg toTTY:(const char *)path {
int fd = open(path, O_WRONLY | O_NOCTTY);
write(fd, msg.UTF8String, msg.length);
std::string_view str = santa::common::NSStringToUTF8StringView(msg);
write(fd, str.data(), str.length());
close(fd);
}

View File

@@ -43,6 +43,7 @@ using santa::santad::Metrics;
using santa::santad::data_layer::WatchItems;
using santa::santad::event_providers::AuthResultCache;
using santa::santad::event_providers::FlushCacheMode;
using santa::santad::event_providers::FlushCacheReason;
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
using santa::santad::event_providers::endpoint_security::Enricher;
using santa::santad::logs::endpoint_security::Logger;
@@ -141,34 +142,33 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
EstablishSyncServiceConnection(syncd_queue);
NSArray<SNTKVOManager *> *kvoObservers = @[
[[SNTKVOManager alloc] initWithObject:configurator
selector:@selector(clientMode)
type:[NSNumber class]
callback:^(NSNumber *oldValue, NSNumber *newValue) {
if ([oldValue longLongValue] == [newValue longLongValue]) {
// Note: This case apparently can happen and if not checked
// will result in excessive notification messages sent to the
// user when calling `postClientModeNotification` below
return;
}
[[SNTKVOManager alloc]
initWithObject:configurator
selector:@selector(clientMode)
type:[NSNumber class]
callback:^(NSNumber *oldValue, NSNumber *newValue) {
if ([oldValue longLongValue] == [newValue longLongValue]) {
// Note: This case apparently can happen and if not checked
// will result in excessive notification messages sent to the
// user when calling `postClientModeNotification` below
return;
}
SNTClientMode clientMode =
(SNTClientMode)[newValue longLongValue];
SNTClientMode clientMode = (SNTClientMode)[newValue longLongValue];
switch (clientMode) {
case SNTClientModeLockdown:
LOGI(@"Changed client mode to Lockdown, flushing cache.");
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches);
break;
case SNTClientModeMonitor:
LOGI(@"Changed client mode to Monitor.");
break;
default: LOGW(@"Changed client mode to unknown value."); break;
}
switch (clientMode) {
case SNTClientModeLockdown:
LOGI(@"Changed client mode to Lockdown, flushing cache.");
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches,
FlushCacheReason::kClientModeChanged);
break;
case SNTClientModeMonitor: LOGI(@"Changed client mode to Monitor."); break;
default: LOGW(@"Changed client mode to unknown value."); break;
}
[[notifier_queue.notifierConnection remoteObjectProxy]
postClientModeNotification:clientMode];
}],
[[notifier_queue.notifierConnection remoteObjectProxy]
postClientModeNotification:clientMode];
}],
[[SNTKVOManager alloc]
initWithObject:configurator
selector:@selector(syncBaseURL)
@@ -233,7 +233,8 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
}
LOGI(@"Changed allowlist regex, flushing cache");
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches);
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches,
FlushCacheReason::kPathRegexChanged);
}],
[[SNTKVOManager alloc]
initWithObject:configurator
@@ -246,7 +247,8 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
}
LOGI(@"Changed denylist regex, flushing cache");
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches);
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches,
FlushCacheReason::kPathRegexChanged);
}],
[[SNTKVOManager alloc] initWithObject:configurator
selector:@selector(blockUSBMount)
@@ -324,7 +326,9 @@ void SantadMain(std::shared_ptr<EndpointSecurityAPI> esapi, std::shared_ptr<Logg
callback:^(NSArray *oldValue, NSArray *newValue) {
if ([oldValue isEqual:newValue]) return;
LOGI(@"StaticRules set has changed, flushing cache.");
auth_result_cache->FlushCache(FlushCacheMode::kAllCaches);
auth_result_cache->FlushCache(
FlushCacheMode::kAllCaches,
FlushCacheReason::kStaticRulesChanged);
}],
[[SNTKVOManager alloc]
initWithObject:configurator

View File

@@ -49,6 +49,8 @@ class SantadDeps {
std::unique_ptr<santa::santad::logs::endpoint_security::Logger> logger,
std::shared_ptr<santa::santad::Metrics> metrics,
std::shared_ptr<santa::santad::data_layer::WatchItems> watch_items,
std::shared_ptr<santa::santad::event_providers::AuthResultCache>
auth_result_cache,
MOLXPCConnection *control_connection,
SNTCompilerController *compiler_controller,
SNTNotificationQueue *notifier_queue, SNTSyncdQueue *syncd_queue,

View File

@@ -135,25 +135,31 @@ std::unique_ptr<SantadDeps> SantadDeps::Create(SNTConfigurator *configurator,
exit(EXIT_FAILURE);
}
return std::make_unique<SantadDeps>(
esapi, std::move(logger), std::move(metrics), std::move(watch_items), control_connection,
compiler_controller, notifier_queue, syncd_queue, exec_controller, prefix_tree);
std::shared_ptr<::AuthResultCache> auth_result_cache = AuthResultCache::Create(esapi, metric_set);
if (!auth_result_cache) {
LOGE(@"Failed to create auth result cache");
exit(EXIT_FAILURE);
}
return std::make_unique<SantadDeps>(esapi, std::move(logger), std::move(metrics),
std::move(watch_items), std::move(auth_result_cache),
control_connection, compiler_controller, notifier_queue,
syncd_queue, exec_controller, prefix_tree);
}
SantadDeps::SantadDeps(std::shared_ptr<EndpointSecurityAPI> esapi, std::unique_ptr<::Logger> logger,
std::shared_ptr<::Metrics> metrics,
std::shared_ptr<::WatchItems> watch_items,
MOLXPCConnection *control_connection,
SNTCompilerController *compiler_controller,
SNTNotificationQueue *notifier_queue, SNTSyncdQueue *syncd_queue,
SNTExecutionController *exec_controller,
std::shared_ptr<::PrefixTree<Unit>> prefix_tree)
SantadDeps::SantadDeps(
std::shared_ptr<EndpointSecurityAPI> esapi, std::unique_ptr<::Logger> logger,
std::shared_ptr<::Metrics> metrics, std::shared_ptr<::WatchItems> watch_items,
std::shared_ptr<santa::santad::event_providers::AuthResultCache> auth_result_cache,
MOLXPCConnection *control_connection, SNTCompilerController *compiler_controller,
SNTNotificationQueue *notifier_queue, SNTSyncdQueue *syncd_queue,
SNTExecutionController *exec_controller, std::shared_ptr<::PrefixTree<Unit>> prefix_tree)
: esapi_(std::move(esapi)),
logger_(std::move(logger)),
metrics_(std::move(metrics)),
watch_items_(std::move(watch_items)),
enricher_(std::make_shared<::Enricher>()),
auth_result_cache_(std::make_shared<::AuthResultCache>(esapi_)),
auth_result_cache_(std::move(auth_result_cache)),
control_connection_(control_connection),
compiler_controller_(compiler_controller),
notifier_queue_(notifier_queue),

View File

@@ -54,6 +54,7 @@ static const uint8_t kMaxEnqueuedSyncs = 2;
@property NSUInteger eventBatchSize;
@property NSString *xsrfToken;
@property NSString *xsrfTokenHeader;
@end
@@ -125,6 +126,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
[self startReachability];
}
self.xsrfToken = syncState.xsrfToken;
self.xsrfTokenHeader = syncState.xsrfTokenHeader;
}
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event
@@ -158,6 +160,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
[self startReachability];
}
self.xsrfToken = syncState.xsrfToken;
self.xsrfTokenHeader = syncState.xsrfTokenHeader;
}
- (void)isFCMListening:(void (^)(BOOL))reply {
@@ -229,6 +232,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
BOOL ret = [p sync];
LOGD(@"Rule download %@", ret ? @"complete" : @"failed");
self.xsrfToken = syncState.xsrfToken;
self.xsrfTokenHeader = syncState.xsrfTokenHeader;
});
}
@@ -254,6 +258,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
if ([p sync]) {
SLOGD(@"Preflight complete");
self.xsrfToken = syncState.xsrfToken;
self.xsrfTokenHeader = syncState.xsrfTokenHeader;
// Clean up reachability if it was started for a non-network error
[self stopReachability];
@@ -355,6 +360,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
}
syncState.xsrfToken = self.xsrfToken;
syncState.xsrfTokenHeader = self.xsrfTokenHeader;
NSURLSessionConfiguration *sessConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessConfig.connectionProxyDictionary = [[SNTConfigurator configurator] syncProxyConfig];
@@ -391,10 +397,7 @@ static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReacha
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.compressedContentEncoding =
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
syncState.contentEncoding = config.syncClientContentEncoding;
syncState.pushNotificationsToken = self.pushNotifications.token;
return syncState;

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/santasyncservice/SNTSyncStage.h"
#include "Source/common/SNTCommonEnums.h"
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -70,10 +71,27 @@
NSString *xsrfHeader = self.syncState.xsrfTokenHeader ?: kDefaultXSRFTokenHeader;
[req setValue:self.syncState.xsrfToken forHTTPHeaderField:xsrfHeader];
NSData *compressed = [requestBody zlibCompressed];
NSData *compressed;
NSString *contentEncodingHeader;
switch (self.syncState.contentEncoding) {
case SNTSyncContentEncodingNone: break;
case SNTSyncContentEncodingGzip:
compressed = [requestBody gzipCompressed];
contentEncodingHeader = @"gzip";
break;
case SNTSyncContentEncodingDeflate:
compressed = [requestBody zlibCompressed];
contentEncodingHeader = @"deflate";
break;
default:
// This would be a programming error.
LOGD(@"Unexpected value for content encoding %ld", self.syncState.contentEncoding);
}
if (compressed) {
requestBody = compressed;
[req setValue:self.syncState.compressedContentEncoding forHTTPHeaderField:@"Content-Encoding"];
[req setValue:contentEncodingHeader forHTTPHeaderField:@"Content-Encoding"];
}
[req setHTTPBody:requestBody];
@@ -183,9 +201,15 @@
}
- (NSData *)stripXssi:(NSData *)data {
static const char xssi[3] = {']', ')', '}'};
if (data.length < 3 || strncmp(data.bytes, xssi, 3)) return data;
return [data subdataWithRange:NSMakeRange(3, data.length - 3)];
static const char xssiOne[5] = {')', ']', '}', '\'', '\n'};
static const char xssiTwo[3] = {']', ')', '}'};
if (data.length >= sizeof(xssiOne) && strncmp(data.bytes, xssiOne, sizeof(xssiOne)) == 0) {
return [data subdataWithRange:NSMakeRange(sizeof(xssiOne), data.length - sizeof(xssiOne))];
}
if (data.length >= sizeof(xssiTwo) && strncmp(data.bytes, xssiTwo, sizeof(xssiTwo)) == 0) {
return [data subdataWithRange:NSMakeRange(sizeof(xssiTwo), data.length - sizeof(xssiTwo))];
}
return data;
}
- (BOOL)fetchXSRFToken {

View File

@@ -74,8 +74,7 @@
/// Array of bundle IDs to find binaries for.
@property NSArray *bundleBinaryRequests;
/// The header value for ContentEncoding when sending compressed content.
/// Either "deflate" (default) or "zlib".
@property(copy) NSString *compressedContentEncoding;
/// The content-encoding to use for the client uploads during the sync session.
@property SNTSyncContentEncoding contentEncoding;
@end

View File

@@ -39,6 +39,10 @@
}
@end
@interface SNTSyncStage (XSSI)
- (NSData *)stripXssi:(NSData *)data;
@end
@interface SNTSyncTest : XCTestCase
@property SNTSyncState *syncState;
@property id<SNTDaemonControlXPC> daemonConnRop;
@@ -156,6 +160,27 @@
#pragma mark - SNTSyncStage Tests
- (void)testStripXssi {
SNTSyncStage *sut = [[SNTSyncStage alloc] initWithState:self.syncState];
char wantChar[3] = {'"', 'a', '"'};
NSData *want = [NSData dataWithBytes:wantChar length:3];
char dOne[8] = {')', ']', '}', '\'', '\n', '"', 'a', '"'};
XCTAssertEqualObjects([sut stripXssi:[NSData dataWithBytes:dOne length:8]], want, @"");
char dTwo[6] = {']', ')', '}', '"', 'a', '"'};
XCTAssertEqualObjects([sut stripXssi:[NSData dataWithBytes:dTwo length:6]], want, @"");
char dThree[5] = {')', ']', '}', '\'', '\n'};
XCTAssertEqualObjects([sut stripXssi:[NSData dataWithBytes:dThree length:5]], [NSData data], @"");
char dFour[3] = {']', ')', '}'};
XCTAssertEqualObjects([sut stripXssi:[NSData dataWithBytes:dFour length:3]], [NSData data], @"");
XCTAssertEqualObjects([sut stripXssi:want], want, @"");
}
- (void)testBaseFetchXSRFTokenSuccess {
// NOTE: This test only works if the other tests don't return a 403 and run before this test.
// The XSRF fetching code is inside a dispatch_once.

View File

@@ -30,6 +30,7 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| EnableBadSignatureProtection | Bool | Enable bad signature protection, defaults to NO. If this flag is set to YES, binaries with a bad signing chain will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
| EnablePageZeroProtection | Bool | Enable `__PAGEZERO` protection, defaults to YES. If this flag is set to YES, 32-bit binaries that are missing the `__PAGEZERO` segment will be blocked even in MONITOR mode, **unless** the binary is allowed by an explicit rule. |
| EnableSilentMode | Bool | If true, Santa will not post any GUI notifications. This can be a very confusing experience for users, use with caution. Defaults to NO. |
| EnableSilentTTYMode | Bool | If true, Santa will not post any TTY notifications. This can be a very confusing experience for users, use with caution. Defaults to NO. |
| AboutText | String | The text to display when the user opens Santa.app. If unset, the default text will be displayed. |
| MoreInfoURL | String | The URL to open when the user clicks "More Info..." when opening Santa.app. If unset, the button will not be displayed. |
| EventDetailURL | String | See the [EventDetailURL](#eventdetailurl) section below. |
@@ -71,6 +72,7 @@ also known as mobileconfig files, which are in an Apple-specific XML format.
| RemountUSBMode | Array | Array of strings for arguments to pass to mount -o (any of "rdonly", "noexec", "nosuid", "nobrowse", "noowners", "nodev", "async", "-j"). when forcibly remounting devices. No default. |
| FileAccessPolicyPlist | String | (BETA) Path to a file access configuration plist. |
| FileAccessPolicyUpdateIntervalSec | Integer | (BETA) Number of seconds between re-reading the file access policy config and policies/monitored paths updated. |
| SyncClientContentEncoding | String | Sets the Content-Encoding header for requests sent to the sync service. Acceptable values are "deflate", "gzip", "none" (Defaults to deflate.) |
\*overridable by the sync server: run `santactl status` to check the current