Skip to content

Commit

Permalink
Improve handling of sync response default values (#1395)
Browse files Browse the repository at this point in the history
* Improve default value handling for sync proto processing

* Fix capitalization of new enum values in comments

* Fix/add tests, update some docs

* Update more docs

* Lint

* Remove comment. Add LEGACY_NAMES tag for the linter
  • Loading branch information
mlw committed Jul 16, 2024
1 parent 52ffe5f commit bbeb653
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 111 deletions.
3 changes: 2 additions & 1 deletion Source/common/SNTConfigurator.m
Original file line number Diff line number Diff line change
Expand Up @@ -1059,7 +1059,8 @@ - (SNTOverrideFileAccessAction)overrideFileAccessAction {
}
}

if ([action isEqualToString:@"auditonly"]) {
// Note: `auditonly` without an underscore is a deprecated, but still accepted form.
if ([action isEqualToString:@"audit_only"] || [action isEqualToString:@"auditonly"]) {
return SNTOverrideFileAccessActionAuditOnly;
} else if ([action isEqualToString:@"disable"]) {
return SNTOverrideFileAccessActionDiable;
Expand Down
131 changes: 82 additions & 49 deletions Source/santasyncservice/SNTSyncPreflight.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,42 @@
using santa::NSStringToUTF8String;
using santa::StringToNSString;

// Ignoring warning regarding deprecated declarations because of large number of
// reported issues due to checking deprecated proto fields.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

/*
Clean Sync Implementation Notes
The clean sync implementation seems a bit complex at first glance, but boils
down to the following rules:
1. If the server says to do a "clean" sync, a "clean" sync is performed, unless the
client specified a "clean all" sync, in which case "clean all" is performed.
2. If the server responded that it is performing a "clean all" sync, a "clean all" is performed.
3. All other server responses result in a "normal" sync.
1. If the server says to do a "CLEAN" sync, a "CLEAN" sync is performed, unless the
client specified a "CLEAN_ALL" sync, in which case "CLEAN_ALL" is performed.
2. If the server responded that it is performing a "CLEAN_ALL" sync, a "CLEAN_ALL" is performed.
3. All other server responses result in a "NORMAL" sync.
The following table expands upon the above logic to list most of the permutations:
| Client Sync State | Clean Sync Request? | Server Response | Sync Type Performed |
| ----------------- | ------------------- | ------------------ | ------------------- |
| normal | No | normal OR <empty> | normal |
| normal | No | clean | clean |
| normal | No | clean_all | clean_all |
| normal | No | clean_sync (dep) | clean |
| normal | Yes | New AND Dep Key | Dep key ignored |
| clean | Yes | normal OR <empty> | normal |
| clean | Yes | clean | clean |
| clean | Yes | clean_all | clean_all |
| clean | Yes | clean_sync (dep) | clean |
| clean | Yes | New AND Dep Key | Dep key ignored |
| clean_all | Yes | normal OR <empty> | normal |
| clean_all | Yes | clean | clean_all |
| clean_all | Yes | clean_all | clean_all |
| clean_all | Yes | clean_sync (dep) | clean_all |
| clean_all | Yes | New AND Dep Key | Dep key ignored |
| NORMAL | No | NORMAL OR <empty> | NORMAL |
| NORMAL | No | CLEAN | CLEAN |
| NORMAL | No | CLEAN_ALL | CLEAN_ALL |
| NORMAL | No | clean_sync (dep) | CLEAN |
| NORMAL | Yes | New AND Dep Key | Dep key ignored |
| CLEAN | Yes | NORMAL OR <empty> | NORMAL |
| CLEAN | Yes | CLEAN | CLEAN |
| CLEAN | Yes | CLEAN_ALL | CLEAN_ALL |
| CLEAN | Yes | clean_sync (dep) | CLEAN |
| CLEAN | Yes | New AND Dep Key | Dep key ignored |
| CLEAN_ALL | Yes | NORMAL OR <empty> | NORMAL |
| CLEAN_ALL | Yes | CLEAN | CLEAN_ALL |
| CLEAN_ALL | Yes | CLEAN_ALL | CLEAN_ALL |
| CLEAN_ALL | Yes | clean_sync (dep) | CLEAN_ALL |
| CLEAN_ALL | Yes | New AND Dep Key | Dep key ignored |
*/
@implementation SNTSyncPreflight
Expand Down Expand Up @@ -130,12 +135,27 @@ - (BOOL)sync {
return NO;
}

self.syncState.enableBundles = @(resp.enable_bundles() || resp.deprecated_bundles_enabled());
self.syncState.enableTransitiveRules =
@(resp.enable_transitive_rules() || resp.deprecated_enabled_transitive_whitelisting() ||
resp.deprecated_transitive_whitelisting_enabled());
self.syncState.enableAllEventUpload = @(resp.enable_all_event_upload());
self.syncState.disableUnknownEventUpload = @(resp.disable_unknown_event_upload());
if (resp.has_enable_bundles()) {
self.syncState.enableBundles = @(resp.enable_bundles());
} else if (resp.has_deprecated_bundles_enabled()) {
self.syncState.enableBundles = @(resp.deprecated_bundles_enabled());
}

if (resp.has_enable_transitive_rules()) {
self.syncState.enableTransitiveRules = @(resp.enable_transitive_rules());
} else if (resp.has_deprecated_enabled_transitive_whitelisting()) {
self.syncState.enableTransitiveRules = @(resp.deprecated_enabled_transitive_whitelisting());
} else if (resp.has_deprecated_transitive_whitelisting_enabled()) {
self.syncState.enableTransitiveRules = @(resp.deprecated_transitive_whitelisting_enabled());
}

if (resp.has_enable_all_event_upload()) {
self.syncState.enableAllEventUpload = @(resp.enable_all_event_upload());
}

if (resp.has_disable_unknown_event_upload()) {
self.syncState.disableUnknownEventUpload = @(resp.disable_unknown_event_upload());
}

self.syncState.eventBatchSize = kDefaultEventBatchSize;
if (resp.batch_size() > 0) {
Expand Down Expand Up @@ -179,50 +199,58 @@ - (BOOL)sync {

if (resp.has_block_usb_mount()) {
self.syncState.blockUSBMount = @(resp.block_usb_mount());
}

self.syncState.remountUSBMode = [NSMutableArray array];
for (const std::string &mode : resp.remount_usb_mode()) {
[(NSMutableArray *)self.syncState.remountUSBMode addObject:StringToNSString(mode)];
self.syncState.remountUSBMode = [NSMutableArray array];
for (const std::string &mode : resp.remount_usb_mode()) {
[(NSMutableArray *)self.syncState.remountUSBMode addObject:StringToNSString(mode)];
}
}

if (resp.has_override_file_access_action()) {
self.syncState.overrideFileAccessAction = StringToNSString(resp.override_file_access_action());
switch (resp.override_file_access_action()) {
case ::pbv1::NONE: self.syncState.overrideFileAccessAction = @"NONE"; break;
case ::pbv1::AUDIT_ONLY: self.syncState.overrideFileAccessAction = @"AUDIT_ONLY"; break;
case ::pbv1::DISABLE: self.syncState.overrideFileAccessAction = @"DISABLE"; break;
case ::pbv1::FILE_ACCESS_ACTION_UNSPECIFIED: // Intentional fallthrough
default: self.syncState.overrideFileAccessAction = nil; break;
}
}

// Default sync type is SNTSyncTypeNormal
//
// Logic overview:
// The requested sync type (clean or normal) is merely informative. The server
// can choose to respond with a normal, clean or clean_all.
// can choose to respond with a NORMAL, CLEAN or CLEAN_ALL.
//
// If the server responds that it will perform a clean sync, santa will
// treat it as either a clean or clean_all depending on which was requested.
// treat it as either a clean or CLEAN_ALL depending on which was requested.
//
// The server can also "override" the requested clean operation. If a normal
// sync was requested, but the server responded that it was doing a clean or
// clean_all sync, that will take precedence. Similarly, if only a clean sync
// was requested, the server can force a "clean_all" operation to take place.
self.syncState.syncType = SNTSyncTypeNormal;
// CLEAN_ALL sync, that will take precedence. Similarly, if only a clean sync
// was requested, the server can force a "CLEAN_ALL" operation to take place.

// If kSyncType response key exists, it overrides the kCleanSyncDeprecated value
// First check if the kSyncType reponse key exists. If so, it takes precedence
// over the kCleanSyncDeprecated key.
std::string responseSyncType = resp.sync_type();
if (!responseSyncType.empty()) {
// If the client wants to Clean All, this takes precedence. The server
// cannot override the client wanting to remove all rules.
if (responseSyncType == "clean") {
SLOGD(@"Clean sync requested by server");
if (requestSyncType == SNTSyncTypeCleanAll) {
self.syncState.syncType = SNTSyncTypeCleanAll;
} else {
self.syncState.syncType = SNTSyncTypeClean;
}
} else if (responseSyncType == "clean_all") {
self.syncState.syncType = SNTSyncTypeCleanAll;
if (resp.has_sync_type()) {
switch (resp.sync_type()) {
case ::pbv1::CLEAN:
// If the client wants to Clean All, this takes precedence. The server
// cannot override the client wanting to remove all rules.
SLOGD(@"Clean sync requested by server");
if (requestSyncType == SNTSyncTypeCleanAll) {
self.syncState.syncType = SNTSyncTypeCleanAll;
} else {
self.syncState.syncType = SNTSyncTypeClean;
}
break;
case ::pbv1::CLEAN_ALL: self.syncState.syncType = SNTSyncTypeCleanAll; break;
case ::pbv1::SYNC_TYPE_UNSPECIFIED: // Intentional fallthrough
case ::pbv1::NORMAL: // Intentional fallthrough
default: self.syncState.syncType = SNTSyncTypeNormal; break;
}
} else if (resp.deprecated_clean_sync()) {
} else if (resp.has_deprecated_clean_sync()) {
// If the deprecated key is set, the type of sync clean performed should be
// the type that was requested. This must be set appropriately so that it
// can be propagated during the Rule Download stage so SNTRuleTable knows
Expand All @@ -233,9 +261,14 @@ - (BOOL)sync {
} else {
self.syncState.syncType = SNTSyncTypeClean;
}
} else {
// Fallback if unspecified is a normal sync
self.syncState.syncType = SNTSyncTypeNormal;
}

return YES;
}

@end

#pragma clang diagnostic pop
1 change: 1 addition & 0 deletions Source/santasyncservice/SNTSyncRuleDownload.mm
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ - (SNTRule *)ruleFromProtoRule:(::pbv1::Rule)rule {

r.identifier = StringToNSString(rule.identifier());
if (!r.identifier.length) r.identifier = StringToNSString(rule.deprecated_sha256());
if (!r.identifier.length) return nil;

SNTRuleState state;
switch (rule.policy()) {
Expand Down
Loading

0 comments on commit bbeb653

Please sign in to comment.