Compare commits

...

10 Commits

Author SHA1 Message Date
Russell Hancox
1d9af01353 Project: Bump dependency versions, fix reload command (#554)
* Project: Bump dependency versions, fix reload command

The reload command would fail if you used multiple compilation modes for
building as it would try extracting the versions from both comp modes.

The dependency bump includes a fix for #553
2021-05-03 21:37:42 -04:00
Adam Sindelar
9c6af7fc03 Docs: Add job posting link to README 2021-04-26 12:16:35 -04:00
Tom Burgin
543b1a29fe add default provisioning profile rules (#548) 2021-04-19 17:18:18 -04:00
Tom Burgin
625ec67789 handle PHONE_REGISTRATION_ERROR (#549) 2021-04-19 17:16:53 -04:00
Tom Burgin
c5696d71e7 add build release rule (#547) 2021-04-19 13:58:59 -04:00
Tom Burgin
5f3cef52de cleanup (#546) 2021-04-19 13:37:21 -04:00
Tom Burgin
eeed0b5aa6 santactl: migrate from fcmstream to fcmconnection (#545) 2021-04-19 11:51:32 -04:00
Russell Hancox
9ef171e663 Docs: Fix more broken docs links (#543) 2021-04-19 11:17:13 -04:00
Russell Hancox
ad1868a50f santad: Fix transitive rules when using the sysx cache feature (#540)
This fixes transitive allowlisting when `EnableSysxCache` is turned on, reduces the deadline timer to fire 5s before the ES deadline, remaps our DEBUG logs to NOTICE so they can be more easily seen in Console and prevents transitive rules being created for paths under /dev/.
2021-03-04 09:47:32 -05:00
Russell Hancox
78643d3c49 fileinfo: Don't use non-bundle dirs as possible ancestors (#537) 2021-02-01 11:09:32 -05:00
21 changed files with 822 additions and 73 deletions

View File

@@ -18,8 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Build Userspace
run: bazel build --apple_generate_dsym -c opt :release
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
- name: Build Driver
run: bazel build --apple_generate_dsym -c opt :release_driver
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
- name: Test
run: bazel test :unit_tests
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci

18
BUILD
View File

@@ -15,6 +15,20 @@ apple_bundle_version(
short_version_string = SANTA_VERSION,
)
# Used to detect release builds
config_setting(
name = "release_build",
values = {"define": "SANTA_BUILD_TYPE=release"},
visibility = [":santa_package_group"],
)
# Used to detect CI builds
config_setting(
name = "ci_build",
values = {"define": "SANTA_BUILD_TYPE=ci"},
visibility = [":santa_package_group"],
)
# Used to detect optimized builds
config_setting(
name = "opt_build",
@@ -60,9 +74,9 @@ set -e
rm -rf /tmp/bazel_santa_reload
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*/bin/Source/santa_driver/santa_driver.zip >/dev/null
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa_driver/santa_driver.zip >/dev/null
unzip -d /tmp/bazel_santa_reload \
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*/bin/Source/santa/Santa.zip >/dev/null
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/santa/Santa.zip >/dev/null
echo "You may be asked for your password for sudo"
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh

View File

@@ -15,6 +15,10 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
# We're hiring!
Want to work on Santa at Google NYC? [Apply on our Careers page!](https://goo.gle/3tO060z)
# Docs
The Santa docs are stored in the

View File

@@ -102,6 +102,8 @@
C7CDA6FE233E73ED0013622B /* SNTXPCNotifierInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658ACF2322B84F00F36578 /* SNTXPCNotifierInterface.m */; };
C7CDA6FF233E80160013622B /* SNTLogging.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658AD82322B84F00F36578 /* SNTLogging.m */; };
C7CDA700233EB21A0013622B /* SNTXPCBundleServiceInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7658ACC2322B84F00F36578 /* SNTXPCBundleServiceInterface.m */; };
C7CF38D3262DCD1800B0ABA7 /* SNTCommandSyncFCM.m in Sources */ = {isa = PBXBuildFile; fileRef = C7CF38D1262DCD1800B0ABA7 /* SNTCommandSyncFCM.m */; };
C7CF38EB262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = C7CF38E9262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm */; };
C7D35DE42322C99B000C5EB4 /* SantaDecisionManager.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7658B002322B84F00F36578 /* SantaDecisionManager.cc */; };
C7D35DE52322C99E000C5EB4 /* SantaDriver.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7658AFF2322B84F00F36578 /* SantaDriver.cc */; };
C7D35DE62322C9A1000C5EB4 /* SantaDriverClient.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7658AFA2322B84F00F36578 /* SantaDriverClient.cc */; };
@@ -378,6 +380,10 @@
C7A8308022F0F81F00F856AC /* com.google.santa.daemon.systemextension */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = com.google.santa.daemon.systemextension; sourceTree = BUILT_PRODUCTS_DIR; };
C7CC458F24B6184F0018C05C /* SNTXPCSyncdInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTXPCSyncdInterface.h; sourceTree = "<group>"; };
C7CC459024B6184F0018C05C /* SNTXPCSyncdInterface.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SNTXPCSyncdInterface.m; sourceTree = "<group>"; };
C7CF38D1262DCD1800B0ABA7 /* SNTCommandSyncFCM.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncFCM.m; sourceTree = "<group>"; };
C7CF38D2262DCD1800B0ABA7 /* SNTCommandSyncFCM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncFCM.h; sourceTree = "<group>"; };
C7CF38E9262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SNTCachingEndpointSecurityManager.mm; sourceTree = "<group>"; };
C7CF38EA262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCachingEndpointSecurityManager.h; sourceTree = "<group>"; };
C7D35DDA2322C902000C5EB4 /* santa-driver.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "santa-driver.kext"; sourceTree = BUILT_PRODUCTS_DIR; };
C7F5C1A7233E72BC00A3F7FD /* santabundleservice */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = santabundleservice; sourceTree = BUILT_PRODUCTS_DIR; };
C7FA383324B60F3300D192F9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
@@ -468,6 +474,8 @@
0D3715F9233E680700BB624A /* EventProviders */ = {
isa = PBXGroup;
children = (
C7CF38EA262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.h */,
C7CF38E9262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm */,
C7658A632322B84F00F36578 /* SNTDriverManager.h */,
C7658A742322B84F00F36578 /* SNTDriverManager.m */,
C72ED2B42324962400255555 /* SNTEndpointSecurityManager.h */,
@@ -592,6 +600,8 @@
C7658AA22322B84F00F36578 /* SNTCommandSyncConstants.m */,
C7658A982322B84F00F36578 /* SNTCommandSyncEventUpload.h */,
C7658AA12322B84F00F36578 /* SNTCommandSyncEventUpload.m */,
C7CF38D2262DCD1800B0ABA7 /* SNTCommandSyncFCM.h */,
C7CF38D1262DCD1800B0ABA7 /* SNTCommandSyncFCM.m */,
C7658A9E2322B84F00F36578 /* SNTCommandSyncManager.h */,
C7658AA92322B84F00F36578 /* SNTCommandSyncManager.m */,
C7658A9D2322B84F00F36578 /* SNTCommandSyncPostflight.h */,
@@ -1231,6 +1241,7 @@
C7658B492322C18500F36578 /* SNTCommandCacheHistogram.m in Sources */,
C7658B472322C17D00F36578 /* SNTCommandController.m in Sources */,
C7658B5D2322C2B800F36578 /* SNTFileInfo.m in Sources */,
C7CF38D3262DCD1800B0ABA7 /* SNTCommandSyncFCM.m in Sources */,
C7658B542322C1A400F36578 /* SNTCommandSyncEventUpload.m in Sources */,
C7658B5F2322C2C700F36578 /* SNTStoredEvent.m in Sources */,
C7658B4F2322C19700F36578 /* SNTCommandStatus.m in Sources */,
@@ -1285,6 +1296,7 @@
files = (
C7CC459224B6188B0018C05C /* SNTXPCSyncdInterface.m in Sources */,
C7658B332322C08B00F36578 /* SNTRule.m in Sources */,
C7CF38EB262DD07100B0ABA7 /* SNTCachingEndpointSecurityManager.mm in Sources */,
C7658B1D2322BFFA00F36578 /* main.m in Sources */,
0D9F577C2342650F005D9AA8 /* SNTPrefixTree.cc in Sources */,
C7658B282322C02300F36578 /* SNTDriverManager.m in Sources */,

View File

@@ -349,6 +349,26 @@
///
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
///
/// Contains the FCM project name.
///
@property(readonly, nonatomic) NSString *fcmProject;
///
/// Contains the FCM project entity.
///
@property(readonly, nonatomic) NSString *fcmEntity;
///
/// Contains the FCM project API key.
///
@property(readonly, nonatomic) NSString *fcmAPIKey;
///
/// True if fcmProject, fcmEntity and fcmAPIKey are all set. Defaults to false.
///
@property(readonly, nonatomic) BOOL fcmEnabled;
///
/// Retrieve an initialized singleton configurator object using the default file path.
///

View File

@@ -87,6 +87,10 @@ static NSString *const kEnableDebugLogging = @"EnableDebugLogging";
static NSString *const kEnableBackwardsCompatibleContentEncoding = @"EnableBackwardsCompatibleContentEncoding";
static NSString *const kFCMProject = @"FCMProject";
static NSString *const kFCMEntity = @"FCMEntity";
static NSString *const kFCMAPIKey = @"FCMAPIKey";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kEnableTransitiveRulesKey = @"EnableTransitiveRules";
@@ -163,6 +167,9 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kIgnoreOtherEndpointSecurityClients : number,
kEnableDebugLogging : number,
kEnableBackwardsCompatibleContentEncoding : number,
kFCMProject : string,
kFCMEntity : string,
kFCMAPIKey : string,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -354,6 +361,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmProject {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmEntity {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmAPIKey {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingFcmEnabled {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -614,6 +637,22 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (NSString *)fcmProject {
return self.configState[kFCMProject];
}
- (NSString *)fcmEntity {
return self.configState[kFCMEntity];
}
- (NSString *)fcmAPIKey {
return self.configState[kFCMAPIKey];
}
- (BOOL)fcmEnabled {
return (self.fcmProject.length && self.fcmEntity.length && self.fcmAPIKey.length);
}
#pragma mark Private
///

View File

@@ -362,7 +362,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
while (pathComponents.count > 1) {
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if ([bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
if (!ancestor ||
if ((!ancestor && bndl.bundlePath.pathExtension.length) ||
[[self allowedAncestorExtensions] containsObject:bndl.bundlePath.pathExtension]) {
bundle = bndl;
}

View File

@@ -88,7 +88,10 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
break;
case LOG_LEVEL_DEBUG:
levelName = "D";
syslogLevel = ASL_LEVEL_DEBUG;
// Log debug messages at the same ASL level as INFO.
// While it would make sense to use DEBUG, watching debug-level logs
// in Console means enabling all debug logs, which is absurdly noisy.
syslogLevel = ASL_LEVEL_NOTICE;
break;
}

View File

@@ -55,6 +55,10 @@ macos_application(
bundle_name = "Santa",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":SantaGUI_lib"],

View File

@@ -46,6 +46,7 @@ objc_library(
sdk_dylibs = ["libz"],
sdk_frameworks = ["IOKit"],
deps = [
":FCM_lib",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
@@ -64,11 +65,20 @@ objc_library(
"@FMDB",
"@MOLAuthenticatingURLSession",
"@MOLCodesignChecker",
"@MOLFCMClient",
"@MOLXPCConnection",
],
)
objc_library(
name = "FCM_lib",
srcs = ["Commands/sync/SNTCommandSyncFCM.m"],
hdrs = ["Commands/sync/SNTCommandSyncFCM.h"],
sdk_frameworks = ["SystemConfiguration"],
deps = [
"@MOLAuthenticatingURLSession",
],
)
macos_command_line_application(
name = "santactl",
bundle_id = "com.google.santa.ctl",
@@ -130,6 +140,7 @@ santa_unit_test(
],
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
@@ -140,7 +151,6 @@ santa_unit_test(
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCSyncdInterface",
"@MOLAuthenticatingURLSession",
"@MOLFCMClient",
"@MOLXPCConnection",
"@OCMock",
],

View File

@@ -0,0 +1,115 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import <Foundation/Foundation.h>
/** A block that takes a NSString object as an argument. */
typedef void (^SNTCommandSyncFCMTokenHandler)(NSString *);
/** A block that takes a NSDictionary object as an argument. */
typedef void (^SNTCommandSyncFCMMessageHandler)(NSDictionary *);
/** A block that takes a NSHTTPURLResponse and NSError object as an argument. */
typedef void (^SNTCommandSyncFCMConnectionErrorHandler)(NSHTTPURLResponse *, NSError *);
/** A block that takes a NSDictionary and NSError object as arguments. */
typedef void (^SNTCommandSyncFCMAcknowledgeErrorHandler)(NSDictionary *, NSError *);
@interface SNTCommandSyncFCM : NSObject
/** Returns YES if connected to FCM. */
@property(readonly, nonatomic) BOOL isConnected;
/** A block to be executed when the FCM token changes */
@property(copy) SNTCommandSyncFCMTokenHandler tokenHandler;
/** A block to be executed when there is an issue with acknowledging a message. */
@property(copy) SNTCommandSyncFCMAcknowledgeErrorHandler acknowledgeErrorHandler;
/** A block to be executed when there is a non-recoverable issue with the FCM Connection. */
@property(copy) SNTCommandSyncFCMConnectionErrorHandler connectionErrorHandler;
- (instancetype)init NS_UNAVAILABLE;
/**
* The designated initializer.
*
* @param project FCM project
* @param entity FCM entity
* @param apiKey FCM apiKey
* @param connectDelayMax Optional, max delay (seconds) when calling connect
* @param backoffMax Optional, max backoff (seconds) when the connection is interrupted
* @param fatalCodes Optional, do not attempt to reconnect if a fatal code is returned
* @param sessionConfiguration Optional, the desired NSURLSessionConfiguration
* @param messageHandler The block to be called for every message received
*
* @note If the host argument is nil, https://fcm-stream.googleapis.com will be used.
* @note If the connectDelayMax argument is 0, a default value of 10 will be used.
* @note If the backoffMax argument is 0, a default value of 900 will be used.
* @note If the fatalCodes argument is nil, @[@302, @400, @403] will be used.
* @note If the sessionConfiguration argument is nil, defaultSessionConfiguration will be used.
*
* @return An initialized SNTCommandSyncFCM object
*/
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
connectDelayMax:(uint32_t)connectDelayMax
backoffMax:(uint32_t)backoffMax
fatalCodes:(NSArray<NSNumber *> *)fatalCodes
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler
NS_DESIGNATED_INITIALIZER;
/** A convenience initializer. Optional args will use their zero values. */
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler;
/** A convenience initializer. Optional args will use their zero values. */
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler;
/**
* Opens a connection to FCM and starts listening for messages.
*
* @note A random delay will occur before the connection is made.
* @note If there is a failure in the connection, reconnection will occur once FCM is reachable.
* Failed reconnections will backoff exponentially up to the defined max.
*/
- (void)connect;
/**
* Acknowledges a FCM message. Each message received must be acknowledged.
*
* @param message A FCM message
*
* @note Calls the acknowledgeErrorHandler block property when an acknowledge error occurs.
*/
- (void)acknowledgeMessage:(NSDictionary *)message;
/**
* Closes all FCM connections. Stops Reachability. Outstanding tasks will be canceled.
*
* @note After disconnect is called the receiver is considered dead. A new MOLFCMClient object
* will need to be created to begin listening for messages.
* @note After disconnect the receiver can hold a reference to itself for up to 15 minutes.
*/
- (void)disconnect;
@end

View File

@@ -0,0 +1,480 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import <SystemConfiguration/SystemConfiguration.h>
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#ifdef DEBUG
#define LOGD(format, ...) NSLog(format, ##__VA_ARGS__);
#else // DEBUG
#define LOGD(format, ...)
#endif // DEBUG
/** FCM checkin and register components */
static NSString *const kFCMCheckinHost = @"https://android.clients.google.com";
static NSString *const kFCMCheckin = @"/checkin";
static NSString *const kFCMCheckinBody = @"{'checkin':{}, 'version':3}";
static NSString *const kFCMRegister = @"/c2dm/register3";
/** FCM connect and ack components */
static NSString *const kFCMConnectHost = @"https://fcmconnection.googleapis.com";
static NSString *const kFCMConnect = @"/v1alpha1:connectDownstream";
static NSString *const kFCMAck = @"/v1alpha1:ack";
/** FCM client keys */
static NSString *const kAndroidIDKey = @"android_id";
static NSString *const kVersionInfoKey = @"version_info";
static NSString *const kSecurityTokenKey = @"security_token";
/** HTTP Header Constants */
static NSString *const kFCMApplicationForm = @"application/x-www-form-urlencoded";
static NSString *const kFCMApplicationJSON = @"application/json";
static NSString *const kFCMContentType = @"Content-Type";
/** Default 15 minute backoff maximum */
static const uint32_t kDefaultBackoffMaxSeconds = 900;
/** Default 10 sec connect delay maximum */
static const uint32_t kDefaultConnectDelayMaxSeconds = 10;
#pragma mark MOLFCMClient Extension
@interface SNTCommandSyncFCM () {
/** URL components for client registration, receiving and acknowledging messages. */
NSURLComponents *_checkinComponents;
NSURLComponents *_registerComponents;
NSURLComponents *_connectComponents;
NSURLComponents *_ackComponents;
/** Holds the NSURLSession object generated by the MOLAuthenticatingURLSession object. */
NSURLSession *_session;
/** Holds the current backoff seconds. */
uint32_t _backoffSeconds;
/** Holds the max connect and backoff seconds. */
uint32_t _connectDelayMaxSeconds;
uint32_t _backoffMaxSeconds;
NSArray<NSNumber *> *_fatalHTTPStatusCodes;
}
/** NSURLSession wrapper used for https communication with the FCM service. */
@property(nonatomic) MOLAuthenticatingURLSession *authSession;
/** The block to be called for every message. */
@property(copy, nonatomic) SNTCommandSyncFCMMessageHandler messageHandler;
/** Is used throughout the class to reconnect to FCM after a connection loss. */
@property SCNetworkReachabilityRef reachability;
/** FCM client identities. */
@property(nonatomic, readonly) NSString *project;
@property(nonatomic, readonly) NSString *entity;
@property(nonatomic, readonly) NSString *apiKey;
/** FCM client checkin data */
@property NSString *androidID;
@property NSString *versionInfo;
@property NSString *securityToken;
/** Called by the reachability handler when the host becomes reachable. */
- (void)reachabilityRestored;
@end
#pragma mark SCNetworkReachabilityCallBack
/** Called when the network state changes. */
static void reachabilityHandler(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags,
void *info) {
dispatch_async(dispatch_get_main_queue(), ^{
if (flags & kSCNetworkReachabilityFlagsReachable) {
SNTCommandSyncFCM *FCMClient = (__bridge SNTCommandSyncFCM *)info;
SEL s = @selector(reachabilityRestored);
[NSObject cancelPreviousPerformRequestsWithTarget:FCMClient selector:s object:nil];
[FCMClient performSelector:s withObject:nil afterDelay:1];
}
});
}
@implementation SNTCommandSyncFCM
#pragma mark init/dealloc methods
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
connectDelayMax:(uint32_t)connectDelayMax
backoffMax:(uint32_t)backoffMax
fatalCodes:(NSArray<NSNumber *> *)fatalCodes
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
self = [super init];
if (self) {
_project = project;
_entity = entity;
_apiKey = apiKey;
_checkinComponents = [NSURLComponents componentsWithString:kFCMCheckinHost];
_checkinComponents.path = kFCMCheckin;
_registerComponents = [NSURLComponents componentsWithString:kFCMCheckinHost];
_registerComponents.path = kFCMRegister;
_connectComponents = [NSURLComponents componentsWithString:kFCMConnectHost];
_connectComponents.path = kFCMConnect;
_ackComponents = [NSURLComponents componentsWithString:kFCMConnectHost];
_ackComponents.path = kFCMAck;
_messageHandler = messageHandler;
_authSession = [[MOLAuthenticatingURLSession alloc]
initWithSessionConfiguration:sessionConfiguration
?: [NSURLSessionConfiguration
defaultSessionConfiguration]];
_authSession.dataTaskDidReceiveDataBlock = [self dataTaskDidReceiveDataBlock];
_authSession.taskDidCompleteWithErrorBlock = [self taskDidCompleteWithErrorBlock];
_session = _authSession.session;
_connectDelayMaxSeconds = connectDelayMax ?: kDefaultConnectDelayMaxSeconds;
_backoffMaxSeconds = backoffMax ?: kDefaultBackoffMaxSeconds;
_fatalHTTPStatusCodes = fatalCodes ?: @[ @302, @400, @401, @403, @404 ];
}
return self;
}
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
sessionConfiguration:(NSURLSessionConfiguration *)sessionConfiguration
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
return [self initWithProject:project
entity:entity
apiKey:apiKey
connectDelayMax:0
backoffMax:0
fatalCodes:nil
sessionConfiguration:sessionConfiguration
messageHandler:messageHandler];
}
- (instancetype)initWithProject:(NSString *)project
entity:(NSString *)entity
apiKey:(NSString *)apiKey
messageHandler:(SNTCommandSyncFCMMessageHandler)messageHandler {
return [self initWithProject:project
entity:entity
apiKey:apiKey
connectDelayMax:0
backoffMax:0
fatalCodes:nil
sessionConfiguration:nil
messageHandler:messageHandler];
}
/** Before this object is released ensure reachability release. */
- (void)dealloc {
[self stopReachability];
}
#pragma mark property methods
- (BOOL)isConnected {
if (!self.androidID || !self.androidID || !self.securityToken) return NO;
for (NSURLSessionDataTask *dataTask in [self dataTasks]) {
if (dataTask.state == NSURLSessionTaskStateRunning) return YES;
}
return NO;
}
#pragma mark reachability methods
- (void)reachabilityRestored {
LOGD(@"Reachability restored. Reconnect after a backoff of %i seconds", _backoffSeconds);
[self stopReachability];
dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, _backoffSeconds * NSEC_PER_SEC);
dispatch_after(t, dispatch_get_main_queue(), ^{
[self connectHelper];
});
}
/** Start listening for network state changes on a background thread. */
- (void)startReachability {
if (self.reachability) return;
LOGD(@"Reachability started.");
self.reachability =
SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, kFCMConnectHost.UTF8String);
SCNetworkReachabilityContext context = {.info = (__bridge void *)self};
if (SCNetworkReachabilitySetCallback(self.reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
self.reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
}
}
/** Stop listening for network state changes. */
- (void)stopReachability {
if (self.reachability) {
SCNetworkReachabilitySetDispatchQueue(self.reachability, NULL);
if (self.reachability) CFRelease(self.reachability);
self.reachability = NULL;
}
}
#pragma mark message methods
- (void)connect {
uint32_t ms = arc4random_uniform(_connectDelayMaxSeconds * 1000);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, ms * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
[self connectHelper];
});
}
- (void)connectHelper {
LOGD(@"Connecting...");
[self cancelConnections];
// Reuse checkin credentials / FCM token if allready registered.
if (!self.androidID || !self.androidID || !self.securityToken) return [self checkin];
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_connectComponents.URL];
[URLRequest addValue:kFCMApplicationJSON forHTTPHeaderField:kFCMContentType];
URLRequest.HTTPMethod = @"GET";
[self setCheckinAuthorization:URLRequest];
[[_session dataTaskWithRequest:URLRequest] resume];
}
- (void)checkin {
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_checkinComponents.URL];
[URLRequest addValue:kFCMApplicationJSON forHTTPHeaderField:kFCMContentType];
URLRequest.HTTPMethod = @"POST";
URLRequest.HTTPBody = [kFCMCheckinBody dataUsingEncoding:NSUTF8StringEncoding];
[[_session dataTaskWithRequest:URLRequest] resume];
}
- (void)checkinDataHandler:(NSData *)data {
id jo = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
LOGD(@"checkin: %@", jo);
NSDictionary *checkin = [self extractCheckinFrom:jo];
if (!checkin) return;
self.androidID = [(NSNumber *)checkin[kAndroidIDKey] stringValue];
self.versionInfo = checkin[kVersionInfoKey];
self.securityToken = [(NSNumber *)checkin[kSecurityTokenKey] stringValue];
}
- (NSDictionary *)extractCheckinFrom:(id)jo {
if (!jo) return nil;
if (![jo isKindOfClass:[NSDictionary class]]) return nil;
if (!jo[kAndroidIDKey]) return nil;
if (![jo[kAndroidIDKey] isKindOfClass:[NSNumber class]]) return nil;
if (!jo[kVersionInfoKey]) return nil;
if (![jo[kVersionInfoKey] isKindOfClass:[NSString class]]) return nil;
if (!jo[kSecurityTokenKey]) return nil;
if (![jo[kSecurityTokenKey] isKindOfClass:[NSNumber class]]) return nil;
return jo;
}
- (void)register {
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_registerComponents.URL];
URLRequest.HTTPMethod = @"POST";
URLRequest.HTTPBody = [kFCMCheckinBody dataUsingEncoding:NSUTF8StringEncoding];
NSURLComponents *params = [[NSURLComponents alloc] init];
params.queryItems = @[
[NSURLQueryItem queryItemWithName:@"app" value:self.project],
[NSURLQueryItem queryItemWithName:@"info" value:self.versionInfo],
[NSURLQueryItem queryItemWithName:@"sender" value:self.entity],
[NSURLQueryItem queryItemWithName:@"device" value:self.androidID],
[NSURLQueryItem queryItemWithName:@"X-scope" value:@"*"],
];
URLRequest.HTTPBody = [params.query dataUsingEncoding:NSUTF8StringEncoding];
[URLRequest addValue:kFCMApplicationForm forHTTPHeaderField:kFCMContentType];
NSString *aid = [NSString stringWithFormat:@"AidLogin %@:%@", self.androidID, self.securityToken];
[URLRequest addValue:aid forHTTPHeaderField:@"Authorization"];
[[_session dataTaskWithRequest:URLRequest] resume];
}
- (void)registerDataHandler:(NSData *)data {
NSString *t = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSArray *c = [t componentsSeparatedByString:@"="];
if (c.count == 2) {
NSString *tok = c[1];
if ([tok isEqualToString:@"PHONE_REGISTRATION_ERROR"]) {
LOGD(@"register: PHONE_REGISTRATION_ERROR - retrying");
sleep(1);
return [self register];
}
if (self.tokenHandler) self.tokenHandler(tok);
}
}
- (void)acknowledgeMessage:(NSDictionary *)message {
if (!message[@"messageId"]) return;
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:_ackComponents.URL];
URLRequest.HTTPMethod = @"POST";
[URLRequest addValue:kFCMApplicationJSON forHTTPHeaderField:kFCMContentType];
[self setCheckinAuthorization:URLRequest];
NSDictionary *b = @{@"ack" : @{@"messageId" : message[@"messageId"]}};
NSData *body = [NSJSONSerialization dataWithJSONObject:b options:0 error:NULL];
URLRequest.HTTPBody = body;
[[_session dataTaskWithRequest:URLRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (((NSHTTPURLResponse *)response).statusCode != 200) {
if (self.acknowledgeErrorHandler) {
self.acknowledgeErrorHandler(message, error);
}
}
}] resume];
}
- (void)disconnect {
[self stopReachability];
[_session invalidateAndCancel];
_session = nil;
}
- (void)cancelConnections {
for (NSURLSessionDataTask *dataTask in [self dataTasks]) {
[dataTask cancel];
}
}
- (NSArray<NSURLSessionDataTask *> *)dataTasks {
__block NSArray *dataTasks;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[_session getTasksWithCompletionHandler:^(NSArray *data, NSArray *upload, NSArray *download) {
dataTasks = data;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return dataTasks;
}
/**
* Parse FCM data; extract and call self.messageHandler for each message.
*
* Expected format:
* [{
* "noOp": {}
* }
* , <-- start of new chunk
* {
* "noOp": {}
* }
*
*/
- (void)processMessagesFromData:(NSData *)data {
if (!data) return;
NSMutableString *raw = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (raw.length < 2) return;
// Add an opening bracket if this is a message in the middle of the stream.
[raw replaceOccurrencesOfString:@",\r" withString:@"[" options:0 range:NSMakeRange(0, 2)];
// Always add a closing bracket.
[raw appendString:@"]"];
NSError *err;
id jo = [NSJSONSerialization JSONObjectWithData:[raw dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&err];
if (!jo) {
if (err) LOGD(@"processMessagesFromData err: %@", err);
return;
}
LOGD(@"processMessagesFromData: %@", jo);
if (![jo isKindOfClass:[NSArray class]]) return;
for (id md in jo) {
if (![md isKindOfClass:[NSDictionary class]]) continue;
NSDictionary *m = md[@"message"];
if ([m isKindOfClass:[NSDictionary class]]) self.messageHandler(m);
}
}
- (void)handleHTTPReponse:(NSHTTPURLResponse *)HTTPResponse error:(NSError *)error {
if (HTTPResponse.statusCode == 200) {
_backoffSeconds = 0;
if ([HTTPResponse.URL.path isEqualToString:kFCMCheckin]) {
// If checkin was successful, start listening for messages and continue to register.
[self connectHelper];
return [self register];
} else if ([HTTPResponse.URL.path isEqualToString:kFCMConnect]) {
// connect will re-connect.
return [self connectHelper];
} // register may be called more than once, don't do anything in response.
} else if ([_fatalHTTPStatusCodes containsObject:@(HTTPResponse.statusCode)]) {
if (self.connectionErrorHandler) self.connectionErrorHandler(HTTPResponse, error);
} else {
// If no backoff is set, start out with 5 - 15 seconds.
// If a backoff is already set, double it, with a max of kBackoffMaxSeconds.
_backoffSeconds = _backoffSeconds * 2 ?: arc4random_uniform(11) + 5;
if (_backoffSeconds > _backoffMaxSeconds) _backoffSeconds = _backoffMaxSeconds;
if (error) LOGD(@"handleHTTPReponse err: %@", error);
[self startReachability];
}
}
- (void)setCheckinAuthorization:(NSMutableURLRequest *)URLRequest {
NSString *a = [NSString
stringWithFormat:@"checkin %@:%@ %@", self.androidID, self.securityToken, self.versionInfo];
[URLRequest addValue:a forHTTPHeaderField:@"Authorization"];
[URLRequest addValue:self.apiKey forHTTPHeaderField:@"X-Goog-Api-Key"];
}
#pragma mark NSURLSession block property and methods
/**
* MOLAuthenticatingURLSession is the NSURLSessionDelegate. It will call this block every time
* the URLSession:task:didCompleteWithError: is called. This allows MOLFCMClient to be notified
* when a task ends while using delegate methods.
*/
- (void (^)(NSURLSession *, NSURLSessionDataTask *, NSData *))dataTaskDidReceiveDataBlock {
__weak __typeof(self) weakSelf = self;
return ^(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data) {
__typeof(self) strongSelf = weakSelf;
NSString *path = dataTask.originalRequest.URL.path;
if ([path isEqualToString:kFCMCheckin]) {
return [strongSelf checkinDataHandler:data];
} else if ([path isEqualToString:kFCMRegister]) {
return [strongSelf registerDataHandler:data];
} else if ([dataTask.originalRequest.URL.path isEqualToString:kFCMConnect]) {
[strongSelf processMessagesFromData:data];
}
};
}
/**
* MOLAuthenticatingURLSession is the NSURLSessionDataDelegate. It will call this block every time
* the URLSession:dataTask:didReceiveData: is called. This allows for message data chunks to be
* processed as they appear in the FCM buffer. For Content-Type: text/html there is a 512 byte
* buffer that must be filled before data is returned. Content-Type: application/json does not use
* a buffer and data is returned as soon as it is available.
*
* TODO:(bur) Follow up with FCM on Content-Type: application/json. Currently FCM returns data with
* Content-Type: text/html. Messages under 512 bytes will not be processed until the connection
* drains.
*/
- (void (^)(NSURLSession *, NSURLSessionTask *, NSError *))taskDidCompleteWithErrorBlock {
__weak __typeof(self) weakSelf = self;
return ^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
__typeof(self) strongSelf = weakSelf;
// task.response can be nil when an NSURLError* occurs
if (task.response && ![task.response isKindOfClass:[NSHTTPURLResponse class]]) {
if (strongSelf.connectionErrorHandler) strongSelf.connectionErrorHandler(nil, error);
return;
}
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)task.response;
[strongSelf handleHTTPReponse:HTTPResponse error:error];
};
}
@end

View File

@@ -15,7 +15,6 @@
#import "Source/santactl/Commands/sync/SNTCommandSyncManager.h"
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <MOLFCMClient/MOLFCMClient.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <SystemConfiguration/SystemConfiguration.h>
@@ -28,6 +27,7 @@
#import "Source/common/SNTXPCSyncdInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncFCM.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
@@ -61,7 +61,8 @@ static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property MOLFCMClient *FCMClient;
@property SNTCommandSyncFCM *FCMClient;
@property NSString *FCMToken;
@property(nonatomic) MOLXPCConnection *daemonConn;
@@ -185,40 +186,46 @@ static void reachabilityHandler(
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
if ([self.FCMClient.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Continue with the current FCMToken");
if ([self.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Already listening for push notifications");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
self.FCMClient = [[MOLFCMClient alloc] initWithFCMToken:syncState.FCMToken
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || [message isEqual:@{}]) return;
SNTConfigurator *config = [SNTConfigurator configurator];
self.FCMClient = [[SNTCommandSyncFCM alloc] initWithProject:config.fcmProject
entity:config.fcmEntity
apiKey:config.fcmAPIKey
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || message[@"noOp"]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.tokenHandler = ^(NSString *t) {
STRONGIFY(self);
LOGD(@"tokenHandler: %@", t);
self.FCMToken = t;
[self preflightOnly:YES];
};
self.FCMClient.connectionErrorHandler = ^(NSHTTPURLResponse *response, NSError *error) {
STRONGIFY(self);
if (response) LOGE(@"FCM fatal response: %@", response);
if (error) LOGE(@"FCM fatal error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.FCMToken = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"FCMClient: %@", log);
};
[self.FCMClient connect];
}
@@ -337,6 +344,10 @@ static void reachabilityHandler(
#pragma mark syncing chain
- (void)preflight {
[self preflightOnly:NO];
}
- (void)preflightOnly:(BOOL)preflightOnly {
LOGD(@"Preflight starting");
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
@@ -349,18 +360,19 @@ static void reachabilityHandler(
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && syncState.FCMToken) {
if (syncState.daemon && [SNTConfigurator configurator].fcmEnabled) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCMToken not provided. Sync every %lu min.", syncState.fullSyncInterval / 60);
LOGD(@"FCM not enabled. Sync every %lu min.", syncState.fullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
self.fullSyncInterval = syncState.fullSyncInterval;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.fullSyncInterval];
}
if (preflightOnly) return;
return [self eventUploadWithSyncState:syncState];
} else {
if (!syncState.daemon) {
@@ -489,6 +501,8 @@ static void reachabilityHandler(
syncState.compressedContentEncoding =
config.enableBackwardsCompatibleContentEncoding ? @"zlib" : @"deflate";
syncState.FCMToken = self.FCMToken;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}

View File

@@ -39,6 +39,7 @@
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kPrimaryUser] = self.syncState.machineOwner;
if (self.syncState.FCMToken) requestDict[kFCMToken] = self.syncState.FCMToken;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
@@ -102,7 +103,6 @@
}];
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
self.syncState.FCMToken = resp[kFCMToken];
// Don't let these go too low
NSUInteger FCMIntervalValue = [resp[kFCMFullSyncInterval] unsignedIntegerValue];

View File

@@ -80,6 +80,10 @@ macos_bundle(
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santad_lib"],

View File

@@ -17,7 +17,7 @@
#import "Source/common/SantaCache.h"
#import "Source/common/SNTLogging.h"
#include <atomic>
#include <bsm/libbsm.h>
#include <EndpointSecurity/EndpointSecurity.h>
uint64_t GetCurrentUptime() {
@@ -28,7 +28,6 @@ template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t
}
@implementation SNTCachingEndpointSecurityManager {
std::atomic<bool> _compilerPIDs[PID_MAX];
SantaCache<santa_vnode_id_t, uint64_t> *_decisionCache;
}
@@ -56,10 +55,20 @@ template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t
// If item is in cache but hasn't received a response yet, sleep for a bit.
// If item is not in cache, break out of loop and forward request to callback.
if (RESPONSE_VALID(return_action)) {
if (return_action == ACTION_RESPOND_ALLOW) {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
} else {
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
switch (return_action) {
case ACTION_RESPOND_ALLOW:
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, true);
break;
case ACTION_RESPOND_ALLOW_COMPILER: {
pid_t pid = audit_token_to_pid(m->process->audit_token);
[self setIsCompilerPID:pid];
// Don't let ES cache compilers
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_ALLOW, false);
break;
}
default:
es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false);
break;
}
return YES;
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
@@ -70,7 +79,7 @@ template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t
}
}
[self addToCache:vnode_id decision:ACTION_REQUEST_BINARY timeout:0];
[self addToCache:vnode_id decision:ACTION_REQUEST_BINARY currentTicks:0];
return NO;
}
@@ -79,25 +88,24 @@ template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t
es_respond_result_t ret;
switch (action) {
case ACTION_RESPOND_ALLOW_COMPILER:
if (sm.pid >= PID_MAX) {
LOGE(@"Unable to watch compiler pid=%d >= pid_max=%d", sm.pid, PID_MAX);
} else {
LOGD(@"Watching compiler pid=%d path=%s", sm.pid, sm.path);
self->_compilerPIDs[sm.pid].store(true);
}
// Allow the exec, but don't cache the decision so subsequent execs of the compiler get
// marked appropriately.
[self setIsCompilerPID:sm.pid];
// Allow the exec and cache in our internal cache but don't let ES cache, because then
// we won't see future execs of the compiler in order to record the PID.
[self addToCache:sm.vnode_id
decision:ACTION_RESPOND_ALLOW_COMPILER
currentTicks:GetCurrentUptime()];
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
ES_AUTH_RESULT_ALLOW, false);
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_ALLOW_PENDING_TRANSITIVE:
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_ALLOW timeout:GetCurrentUptime()];
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_ALLOW currentTicks:GetCurrentUptime()];
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
ES_AUTH_RESULT_ALLOW, true);
break;
case ACTION_RESPOND_DENY:
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_DENY timeout:GetCurrentUptime()];
[self addToCache:sm.vnode_id decision:ACTION_RESPOND_DENY currentTicks:GetCurrentUptime()];
OS_FALLTHROUGH;
case ACTION_RESPOND_TOOLONG:
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
@@ -114,7 +122,7 @@ template<> uint64_t SantaCacheHasher<santa_vnode_id_t>(santa_vnode_id_t const& t
- (void)addToCache:(santa_vnode_id_t)identifier
decision:(santa_action_t)decision
timeout:(uint64_t)microsecs {
currentTicks:(uint64_t)microsecs {
switch (decision) {
case ACTION_REQUEST_BINARY:
_decisionCache->set(identifier, (uint64_t)ACTION_REQUEST_BINARY << 56, 0);

View File

@@ -25,6 +25,10 @@ const pid_t PID_MAX = 99999;
@interface SNTEndpointSecurityManager : NSObject<SNTEventProvider>
- (santa_vnode_id_t)vnodeIDForFile:(es_file_t *)file;
- (BOOL)isCompilerPID:(pid_t)pid;
- (void)setIsCompilerPID:(pid_t)pid;
- (void)setNotCompilerPID:(pid_t)pid;
@property(readonly, nonatomic) es_client_t *client;
@end

View File

@@ -25,7 +25,9 @@
#include <libproc.h>
@interface SNTEndpointSecurityManager ()
@interface SNTEndpointSecurityManager () {
std::atomic<bool> _compilerPIDs[PID_MAX];
}
@property(nonatomic) SNTPrefixTree *prefixTree;
@property(nonatomic, copy) void (^decisionCallback)(santa_message_t);
@@ -36,9 +38,7 @@
@end
@implementation SNTEndpointSecurityManager {
std::atomic<bool> _compilerPIDs[PID_MAX];
}
@implementation SNTEndpointSecurityManager
- (instancetype)init API_AVAILABLE(macos(10.15)) {
self = [super init];
@@ -107,7 +107,7 @@
[self removeCacheEntryForVnodeID:[self vnodeIDForFile:m->event.close.target]];
// Create a transitive rule if the file was modified by a running compiler
if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) {
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [self populateBufferFromESFile:m->event.close.target
buffer:sm.path
@@ -117,6 +117,9 @@
sm.path, pid);
break;
}
if ([@(sm.path) hasPrefix:@"/dev/"]) {
break;
}
sm.action = ACTION_NOTIFY_WHITELIST;
sm.pid = pid;
sm.pidversion = pidversion;
@@ -129,7 +132,7 @@
}
case ES_EVENT_TYPE_NOTIFY_RENAME: {
// Create a transitive rule if the file was renamed by a running compiler
if (pid && pid < PID_MAX && self->_compilerPIDs[pid].load()) {
if ([self isCompilerPID:pid]) {
santa_message_t sm = {};
BOOL truncated = [self populateRenamedNewPathFromESMessage:m->event.rename
buffer:sm.path
@@ -139,6 +142,9 @@
sm.path, pid);
break;
}
if ([@(sm.path) hasPrefix:@"/dev/"]) {
break;
}
sm.action = ACTION_NOTIFY_WHITELIST;
sm.pid = pid;
sm.pidversion = pidversion;
@@ -151,7 +157,7 @@
}
case ES_EVENT_TYPE_NOTIFY_EXIT: {
// Update the set of running compiler PIDs
if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false);
[self setNotCompilerPID:pid];
// Skip the standard pipeline and just log.
if (![config enableForkAndExitLogging]) return;
@@ -191,12 +197,12 @@
switch (m->action_type) {
case ES_ACTION_TYPE_AUTH: {
// Create a timer to deny the execution 2 seconds before the deadline,
// Create a timer to deny the execution 5 seconds before the deadline,
// if a response hasn't already been sent. This block will still be enqueued if
// the the deadline - 2 secs is < DISPATCH_TIME_NOW.
// As of 10.15.2, a typical deadline is 60 seconds.
// the the deadline - 5 secs is < DISPATCH_TIME_NOW.
// As of 10.15.5, a typical deadline is 60 seconds.
auto responded = std::make_shared<std::atomic<bool>>(false);
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -2), self.esAuthQueue, ^(void) {
dispatch_after(dispatch_time(m->deadline, NSEC_PER_SEC * -5), self.esAuthQueue, ^(void) {
if (responded->load()) return;
LOGE(@"Deadline reached: deny pid=%d ret=%d",
pid, es_respond_auth_result(self.client, m, ES_AUTH_RESULT_DENY, false));
@@ -446,12 +452,8 @@
es_respond_result_t ret;
switch (action) {
case ACTION_RESPOND_ALLOW_COMPILER:
if (sm.pid >= PID_MAX) {
LOGE(@"Unable to watch compiler pid=%d >= pid_max=%d", sm.pid, PID_MAX);
} else {
LOGD(@"Watching compiler pid=%d path=%s", sm.pid, sm.path);
self->_compilerPIDs[sm.pid].store(true);
}
[self setIsCompilerPID:sm.pid];
// Allow the exec, but don't cache the decision so subsequent execs of the compiler get
// marked appropriately.
ret = es_respond_auth_result(self.client, (es_message_t *)sm.es_message,
@@ -564,4 +566,23 @@
};
}
- (BOOL)isCompilerPID:(pid_t)pid {
return (pid && pid < PID_MAX && self->_compilerPIDs[pid].load());
}
- (void)setIsCompilerPID:(pid_t)pid {
if (pid < 1) {
LOGE(@"Unable to watch compiler pid=%d", pid);
} else if (pid >= PID_MAX) {
LOGE(@"Unable to watch compiler pid=%d >= PID_MAX(%d)", pid, PID_MAX);
} else {
self->_compilerPIDs[pid].store(true);
LOGD(@"Watching compiler pid=%d", pid);
}
}
- (void)setNotCompilerPID:(pid_t)pid {
if (pid && pid < PID_MAX) self->_compilerPIDs[pid].store(false);
}
@end

View File

@@ -14,12 +14,15 @@ git_repository(
load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies")
apple_rules_dependencies()
load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies")
apple_support_dependencies()
# Macops MOL* dependencies
git_repository(
name = "MOLAuthenticatingURLSession",
remote = "https://github.com/google/macops-molauthenticatingurlsession.git",
tag = "v2.9",
tag = "v3.0",
)
git_repository(
@@ -34,16 +37,10 @@ git_repository(
tag = "v2.2",
)
git_repository(
name = "MOLFCMClient",
remote = "https://github.com/google/macops-molfcmclient.git",
tag = "v2.0",
)
git_repository(
name = "MOLXPCConnection",
remote = "https://github.com/google/macops-molxpcconnection.git",
tag = "v2.0",
tag = "v2.1",
)
# FMDB
@@ -51,7 +48,7 @@ git_repository(
new_git_repository(
name = "FMDB",
remote = "https://github.com/ccgus/fmdb.git",
tag = "v2.7",
tag = "2.7.7",
build_file_content = """
objc_library(
name = "FMDB",
@@ -69,7 +66,7 @@ objc_library(
new_git_repository(
name = "OCMock",
remote = "https://github.com/erikdoe/ocmock",
tag = "v3.4.3",
tag = "v3.8.1",
build_file_content = """
objc_library(
name = "OCMock",

View File

@@ -365,11 +365,11 @@ Recursive lookups of an application or directory is a soon to be added feature
##### rule
The rule command is covered in the [rules.md](rules.md) document.
The rule command is covered in the [Rules](rules.md) document.
##### sync
The sync command is covered in the [syncing.md](syncing.md) document.
The sync command is covered in the [Syncing Overview](syncing-overview.md) document.
##### Debug Commands

View File

@@ -1,3 +1,3 @@
"""The version for all Santa components."""
SANTA_VERSION = "2021.2"
SANTA_VERSION = "2021.5"