Skip to content

Commit

Permalink
Add ability to override File Access actions via config and sync setti…
Browse files Browse the repository at this point in the history
…ngs (#1175)

* Support new config (and sync config) option to override file access action.

* Adopt override action config in file access client

* Add sync service and file access client tests

* Require override action to be specific values. Add new sync setting to docs.
  • Loading branch information
mlw authored Sep 13, 2023
1 parent 3be45fd commit ff6bf07
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 11 deletions.
6 changes: 6 additions & 0 deletions Source/common/SNTCommonEnums.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeMonarchJSON,
};

typedef NS_ENUM(NSInteger, SNTOverrideFileAccessAction) {
SNTOverrideFileAccessActionNone,
SNTOverrideFileAccessActionAuditOnly,
SNTOverrideFileAccessActionDiable,
};

#ifdef __cplusplus
enum class FileAccessPolicyDecision {
kNoPolicy,
Expand Down
19 changes: 19 additions & 0 deletions Source/common/SNTConfigurator.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,25 @@
///
@property(nonatomic) NSArray<NSString *> *remountUSBMode;

///
/// If set, will override the action taken when a file access rule violation
/// occurs. This setting will apply across all rules in the file access policy.
///
/// Possible values are
/// * "AuditOnly": When a rule is violated, it will be logged, but the access
/// will not be blocked
/// * "Disable": No access will be logged or blocked.
///
/// If not set, no override will take place and the file acces spolicy will
/// apply as configured.
///
@property(readonly, nonatomic) SNTOverrideFileAccessAction overrideFileAccessAction;

///
/// Set the action that will override file access policy config action
///
- (void)setSyncServerOverrideFileAccessAction:(NSString *)action;

///
/// If set, this over-rides the default machine ID used for syncing.
///
Expand Down
35 changes: 34 additions & 1 deletion Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
/// limitations under the License.

#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTCommonEnums.h"

#include <sys/stat.h>

Expand Down Expand Up @@ -130,6 +129,7 @@ @implementation SNTConfigurator
static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
static NSString *const kEnableAllEventUploadKey = @"EnableAllEventUpload";
static NSString *const kDisableUnknownEventUploadKey = @"DisableUnknownEventUpload";
static NSString *const kOverrideFileAccessActionKey = @"OverrideFileAccessAction";

static NSString *const kMetricFormat = @"MetricFormat";
static NSString *const kMetricURL = @"MetricURL";
Expand Down Expand Up @@ -166,6 +166,7 @@ - (instancetype)init {
kRuleSyncLastSuccess : date,
kSyncCleanRequired : number,
kEnableAllEventUploadKey : number,
kOverrideFileAccessActionKey : string,
};
_forcedConfigKeyTypes = @{
kClientModeKey : number,
Expand Down Expand Up @@ -236,6 +237,7 @@ - (instancetype)init {
kMetricExtraLabels : dictionary,
kEnableAllEventUploadKey : number,
kDisableUnknownEventUploadKey : number,
kOverrideFileAccessActionKey : string,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
Expand Down Expand Up @@ -519,6 +521,10 @@ + (NSSet *)keyPathsForValuesAffectingUsbBlockMessage {
return [self configStateSet];
}

+ (NSSet *)keyPathsForValuesAffectingOverrideFileAccessActionKey {
return [self syncAndConfigStateSet];
}

#pragma mark Public Interface

- (SNTClientMode)clientMode {
Expand Down Expand Up @@ -951,6 +957,33 @@ - (BOOL)blockUSBMount {
return [self.configState[kBlockUSBMountKey] boolValue];
}

- (void)setSyncServerOverrideFileAccessAction:(NSString *)action {
NSString *a = [action lowercaseString];
if ([a isEqualToString:@"auditonly"] || [a isEqualToString:@"disable"] ||
[a isEqualToString:@"none"] || [a isEqualToString:@""]) {
[self updateSyncStateForKey:kOverrideFileAccessActionKey value:action];
}
}

- (SNTOverrideFileAccessAction)overrideFileAccessAction {
NSString *action = [self.syncState[kOverrideFileAccessActionKey] lowercaseString];

if (!action) {
action = [self.configState[kOverrideFileAccessActionKey] lowercaseString];
if (!action) {
return SNTOverrideFileAccessActionNone;
}
}

if ([action isEqualToString:@"auditonly"]) {
return SNTOverrideFileAccessActionAuditOnly;
} else if ([action isEqualToString:@"disable"]) {
return SNTOverrideFileAccessActionDiable;
} else {
return SNTOverrideFileAccessActionNone;
}
}

///
/// Returns YES if all of the necessary options are set to export metrics, NO
/// otherwise.
Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTSyncConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ extern NSString *const kEnableTransitiveRulesDeprecated;
extern NSString *const kEnableTransitiveRulesSuperDeprecated;
extern NSString *const kEnableAllEventUpload;
extern NSString *const kDisableUnknownEventUpload;
extern NSString *const kOverrideFileAccessAction;

extern NSString *const kEvents;
extern NSString *const kFileSHA256;
Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTSyncConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
NSString *const kFCMToken = @"fcm_token";
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
NSString *const kOverrideFileAccessAction = @"override_file_access_action";

NSString *const kEnableBundles = @"enable_bundles";
NSString *const kEnableBundlesDeprecated = @"bundles_enabled";
Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTXPCControlInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
- (void)setEnableTransitiveRules:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setEnableAllEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setDisableUnknownEventUpload:(BOOL)enabled reply:(void (^)(void))reply;
- (void)setOverrideFileAccessAction:(NSString *)action reply:(void (^)(void))reply;

///
/// Syncd Ops
Expand Down
1 change: 1 addition & 0 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,7 @@ santa_unit_test(
":WatchItems",
"//Source/common:Platform",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:TestUtils",
"@MOLCertificate",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,40 @@ es_auth_result_t FileAccessPolicyDecisionToESAuthResult(FileAccessPolicyDecision
}
}

bool IsBlockDecision(FileAccessPolicyDecision decision) {
return decision == FileAccessPolicyDecision::kDenied ||
decision == FileAccessPolicyDecision::kDeniedInvalidSignature;
}

FileAccessPolicyDecision ApplyOverrideToDecision(FileAccessPolicyDecision decision,
SNTOverrideFileAccessAction overrideAction) {
switch (overrideAction) {
// When no override should be applied, return the decision unmodified
case SNTOverrideFileAccessActionNone: return decision;

// When the decision should be overridden to be audit only, only change the
// decision if it was going to deny the operation.
case SNTOverrideFileAccessActionAuditOnly:
if (IsBlockDecision(decision)) {
return FileAccessPolicyDecision::kAllowedAuditOnly;
} else {
return decision;
}

// If the override action is to disable policy, return a decision that will
// be treated as if no policy applied to the operation.
case SNTOverrideFileAccessActionDiable: return FileAccessPolicyDecision::kNoPolicy;

default:
// This is a programming error. Bail.
LOGE(@"Invalid override file access action encountered: %d",
static_cast<int>(overrideAction));
[NSException
raise:@"Invalid SNTOverrideFileAccessAction"
format:@"Invalid SNTOverrideFileAccessAction: %d", static_cast<int>(overrideAction)];
}
}

bool ShouldLogDecision(FileAccessPolicyDecision decision) {
switch (decision) {
case FileAccessPolicyDecision::kDenied: return true;
Expand Down Expand Up @@ -345,6 +379,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
}

@interface SNTEndpointSecurityFileAccessAuthorizer ()
@property SNTConfigurator *configurator;
@property SNTDecisionCache *decisionCache;
@property bool isSubscribed;
@end
Expand Down Expand Up @@ -382,6 +417,8 @@ @implementation SNTEndpointSecurityFileAccessAuthorizer {
_ttyWriter = std::move(ttyWriter);
_metrics = std::move(metrics);

_configurator = [SNTConfigurator configurator];

_rateLimiter = RateLimiter::Create(_metrics, santa::santad::Processor::kFileAccessAuthorizer,
kDefaultRateLimitQPS);

Expand Down Expand Up @@ -565,7 +602,7 @@ - (FileAccessPolicyDecision)applyPolicy:

// If the process is signed but has an invalid signature, it is denied
if (((msg->process->codesigning_flags & (CS_SIGNED | CS_VALID)) == CS_SIGNED) &&
[[SNTConfigurator configurator] enableBadSignatureProtection]) {
[self.configurator enableBadSignatureProtection]) {
// TODO(mlw): Think about how to make stronger guarantees here to handle
// programs becoming invalid after first being granted access. Maybe we
// should only allow things that have hardened runtime flags set?
Expand Down Expand Up @@ -621,10 +658,10 @@ - (FileAccessPolicyDecision)handleMessage:(const Message &)msg
target:(const PathTarget &)target
policy:
(std::optional<std::shared_ptr<WatchItemPolicy>>)optionalPolicy
policyVersion:(const std::string &)policyVersion {
FileAccessPolicyDecision policyDecision = [self applyPolicy:optionalPolicy
forTarget:target
toMessage:msg];
policyVersion:(const std::string &)policyVersion
overrideAction:(SNTOverrideFileAccessAction)overrideAction {
FileAccessPolicyDecision policyDecision = ApplyOverrideToDecision(
[self applyPolicy:optionalPolicy forTarget:target toMessage:msg], overrideAction);

// Note: If ShouldLogDecision, it shouldn't be possible for optionalPolicy
// to not have a value. Performing the check just in case to prevent a crash.
Expand Down Expand Up @@ -704,7 +741,8 @@ - (FileAccessPolicyDecision)handleMessage:(const Message &)msg
return policyDecision;
}

- (void)processMessage:(const Message &)msg {
- (void)processMessage:(const Message &)msg
overrideAction:(SNTOverrideFileAccessAction)overrideAction {
std::vector<PathTarget> targets;
targets.reserve(2);
PopulatePathTargets(msg, targets);
Expand All @@ -726,7 +764,8 @@ - (void)processMessage:(const Message &)msg {
FileAccessPolicyDecision curDecision = [self handleMessage:msg
target:targets[i]
policy:versionAndPolicies.second[i]
policyVersion:versionAndPolicies.first];
policyVersion:versionAndPolicies.first
overrideAction:overrideAction];

policyResult =
CombinePolicyResults(policyResult, FileAccessPolicyDecisionToESAuthResult(curDecision));
Expand All @@ -751,6 +790,16 @@ - (void)processMessage:(const Message &)msg {

- (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg
recordEventMetrics:(void (^)(EventDisposition))recordEventMetrics {
SNTOverrideFileAccessAction overrideAction = [self.configurator overrideFileAccessAction];

// If the override action is set to Disable, return immediately.
if (overrideAction == SNTOverrideFileAccessActionDiable) {
if (esMsg->action_type == ES_ACTION_TYPE_AUTH) {
[self respondToMessage:esMsg withAuthResult:ES_AUTH_RESULT_ALLOW cacheable:false];
}
return;
}

if (esMsg->event_type == ES_EVENT_TYPE_AUTH_OPEN &&
!(esMsg->event.open.fflag & kOpenFlagsIndicatingWrite)) {
if (self->_readsCache.Exists(esMsg->process, ^std::pair<pid_t, pid_t> {
Expand All @@ -769,7 +818,7 @@ - (void)handleMessage:(santa::santad::event_providers::endpoint_security::Messag

[self processMessage:std::move(esMsg)
handler:^(const Message &msg) {
[self processMessage:msg];
[self processMessage:msg overrideAction:overrideAction];
recordEventMetrics(EventDisposition::kProcessed);
}];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <sys/fcntl.h>
#include <sys/types.h>
#include <cstring>
#include <utility>

#include <array>
#include <cstddef>
Expand All @@ -32,6 +33,7 @@

#include "Source/common/Platform.h"
#include "Source/common/SNTCachedDecision.h"
#include "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#include "Source/common/TestUtils.h"
#include "Source/santad/DataLayer/WatchItemPolicy.h"
Expand Down Expand Up @@ -60,6 +62,9 @@
extern bool ShouldLogDecision(FileAccessPolicyDecision decision);
extern bool ShouldNotifyUserDecision(FileAccessPolicyDecision decision);
extern es_auth_result_t CombinePolicyResults(es_auth_result_t result1, es_auth_result_t result2);
extern bool IsBlockDecision(FileAccessPolicyDecision decision);
extern FileAccessPolicyDecision ApplyOverrideToDecision(FileAccessPolicyDecision decision,
SNTOverrideFileAccessAction overrideAction);

static inline std::pair<dev_t, ino_t> FileID(const es_file_t &file) {
return std::make_pair(file.stat.st_dev, file.stat.st_ino);
Expand Down Expand Up @@ -261,6 +266,63 @@ - (void)testShouldNotifyUserDecision {
}
}

- (void)testIsBlockDecision {
std::map<FileAccessPolicyDecision, bool> policyDecisionToIsBlockDecision = {
{FileAccessPolicyDecision::kNoPolicy, false},
{FileAccessPolicyDecision::kDenied, true},
{FileAccessPolicyDecision::kDeniedInvalidSignature, true},
{FileAccessPolicyDecision::kAllowed, false},
{FileAccessPolicyDecision::kAllowedReadAccess, false},
{FileAccessPolicyDecision::kAllowedAuditOnly, false},
{(FileAccessPolicyDecision)123, false},
};

for (const auto &kv : policyDecisionToIsBlockDecision) {
XCTAssertEqual(ShouldNotifyUserDecision(kv.first), kv.second);
}
}

- (void)testApplyOverrideToDecision {
std::map<std::pair<FileAccessPolicyDecision, SNTOverrideFileAccessAction>,
FileAccessPolicyDecision>
decisionAndOverrideToDecision = {
// Override action: None - Policy shouldn't be changed
{{FileAccessPolicyDecision::kNoPolicy, SNTOverrideFileAccessActionNone},
FileAccessPolicyDecision::kNoPolicy},
{{FileAccessPolicyDecision::kDenied, SNTOverrideFileAccessActionNone},
FileAccessPolicyDecision::kDenied},

// Override action: AuditOnly - Policy should be changed only on blocked decisions
{{FileAccessPolicyDecision::kNoPolicy, SNTOverrideFileAccessActionAuditOnly},
FileAccessPolicyDecision::kNoPolicy},
{{FileAccessPolicyDecision::kAllowedAuditOnly, SNTOverrideFileAccessActionAuditOnly},
FileAccessPolicyDecision::kAllowedAuditOnly},
{{FileAccessPolicyDecision::kAllowedReadAccess, SNTOverrideFileAccessActionAuditOnly},
FileAccessPolicyDecision::kAllowedReadAccess},
{{FileAccessPolicyDecision::kDenied, SNTOverrideFileAccessActionAuditOnly},
FileAccessPolicyDecision::kAllowedAuditOnly},
{{FileAccessPolicyDecision::kDeniedInvalidSignature, SNTOverrideFileAccessActionAuditOnly},
FileAccessPolicyDecision::kAllowedAuditOnly},

// Override action: Disable - Always changes the decision to be no policy applied
{{FileAccessPolicyDecision::kAllowed, SNTOverrideFileAccessActionDiable},
FileAccessPolicyDecision::kNoPolicy},
{{FileAccessPolicyDecision::kDenied, SNTOverrideFileAccessActionDiable},
FileAccessPolicyDecision::kNoPolicy},
{{FileAccessPolicyDecision::kAllowedReadAccess, SNTOverrideFileAccessActionDiable},
FileAccessPolicyDecision::kNoPolicy},
{{FileAccessPolicyDecision::kAllowedAuditOnly, SNTOverrideFileAccessActionDiable},
FileAccessPolicyDecision::kNoPolicy},
};

for (const auto &kv : decisionAndOverrideToDecision) {
XCTAssertEqual(ApplyOverrideToDecision(kv.first.first, kv.first.second), kv.second);
}

XCTAssertThrows(
ApplyOverrideToDecision(FileAccessPolicyDecision::kAllowed, (SNTOverrideFileAccessAction)123));
}

- (void)testCombinePolicyResults {
// Ensure that the combined result is ES_AUTH_RESULT_DENY if both or either
// input result is ES_AUTH_RESULT_DENY.
Expand Down Expand Up @@ -717,7 +779,7 @@ - (void)testDisable {
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
}

- (void)testGetPathTargets {
- (void)testPopulatePathTargets {
// This test ensures that the `GetPathTargets` functions returns the
// expected combination of targets for each handled event variant
es_file_t testFile1 = MakeESFile("test_file_1", MakeStat(100));
Expand Down
5 changes: 5 additions & 0 deletions Source/santad/SNTDaemonControlController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ - (void)setRemountUSBMode:(NSArray *)remountUSBMode reply:(void (^)(void))reply
reply();
}

- (void)setOverrideFileAccessAction:(NSString *)action reply:(void (^)(void))reply {
[[SNTConfigurator configurator] setSyncServerOverrideFileAccessAction:action];
reply();
}

- (void)enableBundles:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].enableBundles);
}
Expand Down
6 changes: 6 additions & 0 deletions Source/santasyncservice/SNTSyncPostflight.m
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ - (BOOL)sync {
}];
}

if (self.syncState.overrideFileAccessAction) {
[rop setOverrideFileAccessAction:self.syncState.overrideFileAccessAction
reply:^{
}];
}

// Update last sync success
[rop setFullSyncLastSuccess:[NSDate date]
reply:^{
Expand Down
Loading

0 comments on commit ff6bf07

Please sign in to comment.