Skip to content

Commit

Permalink
Update SNTPolicyProcessor to use a map instead of a giant switch stat…
Browse files Browse the repository at this point in the history
…ement.
  • Loading branch information
pmarkowsky committed Mar 8, 2024
1 parent f2ce926 commit 7ead088
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 147 deletions.
3 changes: 2 additions & 1 deletion Source/santad/BUILD
Expand Up @@ -193,7 +193,7 @@ objc_library(

objc_library(
name = "SNTPolicyProcessor",
srcs = ["SNTPolicyProcessor.m"],
srcs = ["SNTPolicyProcessor.mm"],
hdrs = ["SNTPolicyProcessor.h"],
deps = [
":SNTRuleTable",
Expand All @@ -209,6 +209,7 @@ objc_library(
"@MOLCertificate",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@com_google_absl//absl/container:flat_hash_map",
],
)

Expand Down
Expand Up @@ -27,10 +27,12 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/santad/DataLayer/SNTRuleTable.h"
#include "absl/container/flat_hash_map.h"

@interface SNTPolicyProcessor ()
@property SNTRuleTable *ruleTable;
@property SNTConfigurator *configurator;
@property absl::flat_hash_map<std::pair<SNTRuleType, SNTRuleState>, SNTEventState> decisions;
@end

@implementation SNTPolicyProcessor
Expand All @@ -40,10 +42,102 @@ - (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable {
if (self) {
_ruleTable = ruleTable;
_configurator = [SNTConfigurator configurator];
_decisions = absl::flat_hash_map<std::pair<SNTRuleType, SNTRuleState>, SNTEventState>{
{{SNTRuleTypeCDHash, SNTRuleStateAllow}, SNTEventStateAllowCDHash},
{{SNTRuleTypeCDHash, SNTRuleStateAllowCompiler}, SNTEventStateAllowCompiler},
{{SNTRuleTypeCDHash, SNTRuleStateAllowTransitive}, SNTEventStateAllowTransitive},
{{SNTRuleTypeCDHash, SNTRuleStateBlock}, SNTEventStateBlockCDHash},
{{SNTRuleTypeBinary, SNTRuleStateAllow}, SNTEventStateAllowBinary},
{{SNTRuleTypeBinary, SNTRuleStateAllowTransitive}, SNTEventStateAllowTransitive},
{{SNTRuleTypeBinary, SNTRuleStateAllowCompiler}, SNTEventStateAllowCompiler},
{{SNTRuleTypeBinary, SNTRuleStateSilentBlock}, SNTEventStateBlockBinary},
{{SNTRuleTypeBinary, SNTRuleStateBlock}, SNTEventStateBlockBinary},
{{SNTRuleTypeSigningID, SNTRuleStateAllow}, SNTEventStateAllowSigningID},
{{SNTRuleTypeSigningID, SNTRuleStateAllowCompiler}, SNTEventStateAllowCompiler},
{{SNTRuleTypeSigningID, SNTRuleStateSilentBlock}, SNTEventStateBlockSigningID},
{{SNTRuleTypeSigningID, SNTRuleStateBlock}, SNTEventStateBlockSigningID},
{{SNTRuleTypeCertificate, SNTRuleStateAllow}, SNTEventStateAllowCertificate},
{{SNTRuleTypeCertificate, SNTRuleStateSilentBlock}, SNTEventStateBlockCertificate},
{{SNTRuleTypeCertificate, SNTRuleStateBlock}, SNTEventStateBlockCertificate},
{{SNTRuleTypeTeamID, SNTRuleStateAllow}, SNTEventStateAllowTeamID},
{{SNTRuleTypeTeamID, SNTRuleStateSilentBlock}, SNTEventStateBlockTeamID},
{{SNTRuleTypeTeamID, SNTRuleStateBlock}, SNTEventStateBlockTeamID},
};
}
return self;
}

// This method applies the rules to the cached decision object.
- (nonnull SNTCachedDecision *)updateCachedDecision:(SNTCachedDecision *)cd
forRule:(SNTRule *)rule {
cd.decision = _decisions[std::pair<SNTRuleType, SNTRuleState>{rule.type, rule.state}];

if (rule.state == SNTRuleStateSilentBlock) {
cd.silentBlock = YES;
} else if (rule.state == SNTRuleStateAllowCompiler) {
if (![self.configurator enableTransitiveRules]) {
cd.decision = SNTEventStateAllowBinary;
}
} else if (rule.state == SNTRuleStateAllowTransitive) {
if (![self.configurator enableTransitiveRules]) {
// check operating mode.
SNTClientMode mode = [self.configurator clientMode];
if (mode == SNTClientModeMonitor) {
cd.decision = SNTEventStateAllowUnknown;
} else {
cd.decision = SNTEventStateBlockUnknown;
}
}
}

if (rule.customMsg) {
cd.customMsg = rule.customMsg;
}

if (rule.customURL) {
cd.customURL = rule.customURL;
}

return cd;
}

- (nonnull SNTCachedDecision *)updateCachedDecision:(SNTCachedDecision *)cd
withSigningInfo:(MOLCodesignChecker *)csInfo
withEntitlementFilterCallback:
(NSDictionary *_Nullable (^_Nullable)(NSDictionary *_Nullable entitlements))
entitlementsFilterCallback {
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.certChain = csInfo.certificates;
// Check if we need to get teamID from code signing.
if (!cd.teamID) {
cd.teamID =
[csInfo.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoTeamIdentifier];
}

// Ensure that if no teamID exists that the signing info confirms it is a
// platform binary. If not, remove the signingID.
if (!cd.teamID && cd.signingID) {
id platformID =
[csInfo.signingInformation objectForKey:(__bridge NSString *)kSecCodeInfoPlatformIdentifier];
if (![platformID isKindOfClass:[NSNumber class]] || [platformID intValue] == 0) {
cd.signingID = nil;
}
}

NSDictionary *entitlements =
csInfo.signingInformation[(__bridge NSString *)kSecCodeInfoEntitlementsDict];

if (entitlementsFilterCallback) {
cd.entitlements = entitlementsFilterCallback(entitlements);
cd.entitlementsFiltered = (cd.entitlements.count == entitlements.count);
} else {
cd.entitlements = [entitlements sntDeepCopy];
cd.entitlementsFiltered = NO;
}
return cd;
}

- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
cdhash:(nullable NSString *)cdhash
fileSHA256:(nullable NSString *)fileSHA256
Expand All @@ -54,23 +148,26 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn
entitlementsFilterCallback:
(NSDictionary *_Nullable (^_Nullable)(
NSDictionary *_Nullable entitlements))entitlementsFilterCallback {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.cdhash = cdhash;
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
cd.teamID = teamID;
cd.signingID = signingID;

// Check the hash before allocating a SNTCachedDecision.
NSString *fileHash = fileSHA256 ?: fileInfo.SHA256;
SNTClientMode mode = [self.configurator clientMode];
cd.decisionClientMode = mode;

// If the binary is a critical system binary, don't check its signature.
// The binary was validated at startup when the rule table was initialized.
SNTCachedDecision *systemCd = self.ruleTable.criticalSystemBinaries[cd.sha256];
SNTCachedDecision *systemCd = self.ruleTable.criticalSystemBinaries[fileHash];
if (systemCd) {
systemCd.decisionClientMode = mode;
return systemCd;
}

// Allocate a new cached decision for the execution.
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.sha256 = fileHash;
cd.teamID = teamID;
cd.signingID = signingID;
cd.decisionClientMode = mode;
cd.quarantineURL = fileInfo.quarantineDataURL;

NSError *csInfoError;
if (certificateSHA256.length) {
cd.certSHA256 = certificateSHA256;
Expand All @@ -87,36 +184,11 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn
cd.signingID = nil;
cd.cdhash = nil;
} else {
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.certChain = csInfo.certificates;
cd.teamID = teamID
?: [csInfo.signingInformation
objectForKey:(__bridge NSString *)kSecCodeInfoTeamIdentifier];

// Ensure that if no teamID exists that the signing info confirms it is a
// platform binary. If not, remove the signingID.
if (!cd.teamID && cd.signingID) {
id platformID = [csInfo.signingInformation
objectForKey:(__bridge NSString *)kSecCodeInfoPlatformIdentifier];
if (![platformID isKindOfClass:[NSNumber class]] || [platformID intValue] == 0) {
cd.signingID = nil;
}
}

NSDictionary *entitlements =
csInfo.signingInformation[(__bridge NSString *)kSecCodeInfoEntitlementsDict];

if (entitlementsFilterCallback) {
cd.entitlements = entitlementsFilterCallback(entitlements);
cd.entitlementsFiltered = (cd.entitlements.count == entitlements.count);
} else {
cd.entitlements = [entitlements sntDeepCopy];
cd.entitlementsFiltered = NO;
}
cd = [self updateCachedDecision:cd
withSigningInfo:csInfo
withEntitlementFilterCallback:entitlementsFilterCallback];
}
}
cd.quarantineURL = fileInfo.quarantineDataURL;

// Do not evaluate TeamID/SigningID rules for dev-signed code based on the
// assumption that orgs are generally more relaxed about dev signed cert
Expand All @@ -138,116 +210,9 @@ - (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileIn
.certificateSHA256 = cd.certSHA256,
.teamID = cd.teamID}];
if (rule) {
switch (rule.type) {
case SNTRuleTypeCDHash:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowCDHash; return cd;
case SNTRuleStateAllowCompiler:
// If transitive rules are enabled, then SNTRuleStateAllowListCompiler rules
// become SNTEventStateAllowCompiler decisions. Otherwise we treat the rule as if
// it were SNTRuleStateAllowCDHash.
if ([self.configurator enableTransitiveRules]) {
cd.decision = SNTEventStateAllowCompiler;
} else {
cd.decision = SNTEventStateAllowCDHash;
}
return cd;
case SNTRuleStateSilentBlock:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.customURL = rule.customURL;
cd.decision = SNTEventStateBlockCDHash;
return cd;
default: break;
}
case SNTRuleTypeBinary:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowBinary; return cd;
case SNTRuleStateSilentBlock: cd.silentBlock = YES;
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.customURL = rule.customURL;
cd.decision = SNTEventStateBlockBinary;
return cd;
case SNTRuleStateAllowCompiler:
// If transitive rules are enabled, then SNTRuleStateAllowListCompiler rules
// become SNTEventStateAllowCompiler decisions. Otherwise we treat the rule as if
// it were SNTRuleStateAllow.
if ([self.configurator enableTransitiveRules]) {
cd.decision = SNTEventStateAllowCompiler;
} else {
cd.decision = SNTEventStateAllowBinary;
}
return cd;
case SNTRuleStateAllowTransitive:
// If transitive rules are enabled, then SNTRuleStateAllowTransitive
// rules become SNTEventStateAllowTransitive decisions. Otherwise, we treat the
// rule as if it were SNTRuleStateUnknown.
if ([self.configurator enableTransitiveRules]) {
cd.decision = SNTEventStateAllowTransitive;
return cd;
} else {
rule.state = SNTRuleStateUnknown;
}
default: break;
}
break;
case SNTRuleTypeSigningID:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowSigningID; return cd;
case SNTRuleStateAllowCompiler:
// If transitive rules are enabled, then SNTRuleStateAllowListCompiler rules
// become SNTEventStateAllowCompiler decisions. Otherwise we treat the rule as if
// it were SNTRuleStateAllowSigningID.
if ([self.configurator enableTransitiveRules]) {
cd.decision = SNTEventStateAllowCompiler;
} else {
cd.decision = SNTEventStateAllowSigningID;
}
return cd;
case SNTRuleStateSilentBlock:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.customURL = rule.customURL;
cd.decision = SNTEventStateBlockSigningID;
return cd;
default: break;
}
break;
case SNTRuleTypeCertificate:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowCertificate; return cd;
case SNTRuleStateSilentBlock:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.customURL = rule.customURL;
cd.decision = SNTEventStateBlockCertificate;
return cd;
default: break;
}
break;
case SNTRuleTypeTeamID:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowTeamID; return cd;
case SNTRuleStateSilentBlock:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.customURL = rule.customURL;
cd.decision = SNTEventStateBlockTeamID;
return cd;
default: break;
}
break;

default: break;
cd = [self updateCachedDecision:cd forRule:rule];
if (cd.decision != SNTEventStateBlockUnknown || cd.decision != SNTEventStateAllowUnknown) {
return cd;
}
}

Expand Down

0 comments on commit 7ead088

Please sign in to comment.