From 7ead088a8bb98f45b82c77a67147d07bf16e98fc Mon Sep 17 00:00:00 2001 From: Pete Markowsky Date: Sun, 3 Mar 2024 11:14:14 -0500 Subject: [PATCH] Update SNTPolicyProcessor to use a map instead of a giant switch statement. --- Source/santad/BUILD | 3 +- ...olicyProcessor.m => SNTPolicyProcessor.mm} | 257 ++++++++---------- 2 files changed, 113 insertions(+), 147 deletions(-) rename Source/santad/{SNTPolicyProcessor.m => SNTPolicyProcessor.mm} (65%) diff --git a/Source/santad/BUILD b/Source/santad/BUILD index 7f719b21e..8d514187b 100644 --- a/Source/santad/BUILD +++ b/Source/santad/BUILD @@ -193,7 +193,7 @@ objc_library( objc_library( name = "SNTPolicyProcessor", - srcs = ["SNTPolicyProcessor.m"], + srcs = ["SNTPolicyProcessor.mm"], hdrs = ["SNTPolicyProcessor.h"], deps = [ ":SNTRuleTable", @@ -209,6 +209,7 @@ objc_library( "@MOLCertificate", "@MOLCodesignChecker", "@MOLXPCConnection", + "@com_google_absl//absl/container:flat_hash_map", ], ) diff --git a/Source/santad/SNTPolicyProcessor.m b/Source/santad/SNTPolicyProcessor.mm similarity index 65% rename from Source/santad/SNTPolicyProcessor.m rename to Source/santad/SNTPolicyProcessor.mm index ae67249cc..93c3d920e 100644 --- a/Source/santad/SNTPolicyProcessor.m +++ b/Source/santad/SNTPolicyProcessor.mm @@ -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, SNTEventState> decisions; @end @implementation SNTPolicyProcessor @@ -40,10 +42,102 @@ - (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable { if (self) { _ruleTable = ruleTable; _configurator = [SNTConfigurator configurator]; + _decisions = absl::flat_hash_map, 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{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 @@ -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; @@ -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 @@ -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; } }