Skip to content

Commit

Permalink
sync: Send a hash of all database rules at beginning and end of sync
Browse files Browse the repository at this point in the history
  • Loading branch information
russellhancox committed Aug 1, 2024
1 parent 6093118 commit 76ed896
Show file tree
Hide file tree
Showing 17 changed files with 153 additions and 40 deletions.
6 changes: 4 additions & 2 deletions Source/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,12 @@ objc_library(

objc_library(
name = "SNTFileInfo",
srcs = ["SNTFileInfo.m"],
srcs = ["SNTFileInfo.mm"],
hdrs = ["SNTFileInfo.h"],
deps = [
":SNTLogging",
":SantaVnode",
":String",
"@FMDB",
"@MOLCodesignChecker",
],
Expand Down Expand Up @@ -303,14 +304,15 @@ objc_library(

objc_library(
name = "SNTRule",
srcs = ["SNTRule.m"],
srcs = ["SNTRule.mm"],
hdrs = ["SNTRule.h"],
sdk_frameworks = [
"Foundation",
],
deps = [
":SNTCommonEnums",
":SNTSyncConstants",
":String",
],
)

Expand Down
25 changes: 4 additions & 21 deletions Source/common/SNTFileInfo.m → Source/common/SNTFileInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <sys/xattr.h>

#import "Source/common/SNTLogging.h"
#import "Source/common/String.h"

// Simple class to hold the data of a mach_header and the offset within the file
// in which that header was found.
Expand Down Expand Up @@ -64,8 +65,6 @@ @interface SNTFileInfo ()

@implementation SNTFileInfo

extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;

- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
struct stat fileStat;
if (path.length) {
Expand Down Expand Up @@ -165,7 +164,7 @@ - (instancetype)initWithPath:(NSString *)path {
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
const int MAX_CHUNK_SIZE = 256 * 1024; // 256 KB
const size_t chunkSize = _fileSize > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : _fileSize;
char *chunk = malloc(chunkSize);
char *chunk = (char *)malloc(chunkSize);

@try {
CC_SHA1_CTX c1;
Expand Down Expand Up @@ -202,28 +201,12 @@ - (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
if (sha1) {
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(digest, &c1);
NSString *const SHA1FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha1 = [[NSString alloc]
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19]];
*sha1 = santa::SHA1DigestToNSString(digest);
}
if (sha256) {
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";

*sha256 = [[NSString alloc]
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2], digest[3], digest[4],
digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
digest[11], digest[12], digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19], digest[20], digest[21], digest[22],
digest[23], digest[24], digest[25], digest[26], digest[27], digest[28],
digest[29], digest[30], digest[31]];
*sha256 = santa::SHA256DigestToNSString(digest);
}
} @finally {
free(chunk);
Expand Down
17 changes: 17 additions & 0 deletions Source/common/SNTRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,21 @@
///
- (NSDictionary *)dictionaryRepresentation;

///
/// Returns a SHA-256 digest of this rule.
/// The format of this digest must be consistent to allow possible reimplementation outside of
/// Santa The digest is a SHA-256 of the rule values separated by colons in the following order:
///
/// identifier:state:type:timestamp
///
/// The state and type are long integers. The timestamp is a long unsigned integer.
///
/// The custom URL and custom message fields are not part of the hash because:
/// a) They could potentially be very long and slow down hashing
/// b) They could be formatted in a very slightly different way causing hash values not to match
/// c) They are not part of the evaluation for a rule and so are not critical like the other
/// values are
///
- (NSString *)digest;

@end
16 changes: 14 additions & 2 deletions Source/common/SNTRule.m → Source/common/SNTRule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <os/base.h>

#import "Source/common/SNTSyncConstants.h"
#import "Source/common/String.h"

// https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/
static const NSUInteger kExpectedTeamIDLength = 10;
Expand Down Expand Up @@ -228,8 +229,8 @@ - (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_identifier = DECODE(NSString, @"identifier");
_state = [DECODE(NSNumber, @"state") intValue];
_type = [DECODE(NSNumber, @"type") intValue];
_state = (SNTRuleState)[DECODE(NSNumber, @"state") intValue];
_type = (SNTRuleType)[DECODE(NSNumber, @"type") intValue];
_customMsg = DECODE(NSString, @"custommsg");
_customURL = DECODE(NSString, @"customurl");
_timestamp = [DECODE(NSNumber, @"timestamp") unsignedIntegerValue];
Expand Down Expand Up @@ -306,4 +307,15 @@ - (void)resetTimestamp {
self.timestamp = (NSUInteger)[[NSDate date] timeIntervalSinceReferenceDate];
}

- (NSString *)digest {
NSString *ruleDigestFormat = [[NSString alloc]
initWithFormat:@"%@:%ld:%ld:%lu", self.identifier, self.state, self.type, self.timestamp];
NSData *ruleData = [ruleDigestFormat dataUsingEncoding:NSUTF8StringEncoding];

unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(ruleData.bytes, (CC_LONG)ruleData.length, (unsigned char *)&digest);

return santa::SHA256DigestToNSString(digest);
}

@end
17 changes: 9 additions & 8 deletions Source/common/SNTRuleTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,14 @@ - (void)testRuleStateToPolicyString {
XCTAssertEqualObjects(kRulePolicyRemove, [sut dictionaryRepresentation][kRulePolicy]);
}

/*
- (void)testRuleTypeToString {
SNTRule *sut = [[SNTRule alloc] init];
XCTAssertEqual(kRuleTypeBinary, [sut ruleTypeToString:@""]);//SNTRuleTypeBinary]);
XCTAssertEqual(kRuleTypeCertificate,[sut ruleTypeToString:SNTRuleTypeCertificate]);
XCTAssertEqual(kRuleTypeTeamID, [sut ruleTypeToString:SNTRuleTypeTeamID]);
XCTAssertEqual(kRuleTypeSigningID,[sut ruleTypeToString:SNTRuleTypeSigningID]);
}*/
- (void)testDigest {
SNTRule *sut = [[SNTRule alloc]
initWithIdentifier:@"84de9c61777ca36b13228e2446d53e966096e78db7a72c632b5c185b2ffe68a6"
state:SNTRuleStateAllow
type:SNTRuleTypeBinary
customMsg:nil];
XCTAssertEqualObjects(sut.digest,
@"9aa5d934be605e081fe3535909c4bfa230e5ae4d4dbde2d616b249ac0368ef11");
}

@end
1 change: 1 addition & 0 deletions Source/common/SNTXPCControlInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForIdentifiers:(SNTRuleIdentifiers *)identifiers
reply:(void (^)(SNTRule *))reply;
- (void)databaseRulesHash:(void (^)(NSString *hash))reply;
- (void)retrieveAllRules:(void (^)(NSArray<SNTRule *> *rules, NSError *error))reply;

///
Expand Down
22 changes: 22 additions & 0 deletions Source/common/String.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#ifndef SANTA__COMMON__STRING_H
#define SANTA__COMMON__STRING_H

#include <CommonCrypto/CommonDigest.h>
#include <EndpointSecurity/ESTypes.h>
#include <Foundation/Foundation.h>

Expand Down Expand Up @@ -53,6 +54,27 @@ static inline std::string_view StringTokenToStringView(es_string_token_t es_str)
return std::string_view(es_str.data, es_str.length);
}

static inline NSString *SHA1DigestToNSString(const unsigned char digest[CC_SHA1_DIGEST_LENGTH]) {
return [[NSString alloc]
initWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19]];
}

static inline NSString *SHA256DigestToNSString(
const unsigned char digest[CC_SHA256_DIGEST_LENGTH]) {
return [[NSString alloc]
initWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6],
digest[7], digest[8], digest[9], digest[10], digest[11], digest[12], digest[13],
digest[14], digest[15], digest[16], digest[17], digest[18], digest[19],
digest[20], digest[21], digest[22], digest[23], digest[24], digest[25],
digest[26], digest[27], digest[28], digest[29], digest[30], digest[31]];
}

} // namespace santa

#endif
6 changes: 4 additions & 2 deletions Source/santad/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ objc_library(

objc_library(
name = "SNTRuleTable",
srcs = ["DataLayer/SNTRuleTable.m"],
srcs = ["DataLayer/SNTRuleTable.mm"],
hdrs = ["DataLayer/SNTRuleTable.h"],
sdk_dylibs = [
"EndpointSecurity",
Expand All @@ -34,6 +34,7 @@ objc_library(
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTRuleIdentifiers",
"//Source/common:String",
"@MOLCertificate",
"@MOLCodesignChecker",
],
Expand Down Expand Up @@ -911,7 +912,7 @@ santa_unit_test(
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTDatabaseTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"DataLayer/SNTRuleTable.mm",
"DataLayer/SNTRuleTableTest.m",
],
sdk_dylibs = [
Expand All @@ -926,6 +927,7 @@ santa_unit_test(
"//Source/common:SNTLogging",
"//Source/common:SNTRule",
"//Source/common:SNTRuleIdentifiers",
"//Source/common:String",
"@FMDB",
"@MOLCertificate",
"@MOLCodesignChecker",
Expand Down
5 changes: 5 additions & 0 deletions Source/santad/DataLayer/SNTRuleTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@
///
- (NSArray<SNTRule *> *)retrieveAllRules;

///
/// Retrieve a hash of the hashes of all rules.
///
- (NSString *)hashOfHashes;

///
/// A map of a file hashes to cached decisions. This is used to pre-validate and whitelist
/// certain critical system binaries that are integral to Santa's functionality.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#import "Source/santad/DataLayer/SNTRuleTable.h"

#import <CommonCrypto/CommonDigest.h>
#import <EndpointSecurity/EndpointSecurity.h>
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/MOLCodesignChecker.h>
Expand All @@ -24,6 +25,7 @@
#import "Source/common/SNTFileInfo.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/common/String.h"

static const uint32_t kRuleTableCurrentVersion = 7;

Expand Down Expand Up @@ -310,8 +312,8 @@ - (int64_t)cdhashRuleCount {

- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
SNTRule *r = [[SNTRule alloc] initWithIdentifier:[rs stringForColumn:@"identifier"]
state:[rs intForColumn:@"state"]
type:[rs intForColumn:@"type"]
state:(SNTRuleState)[rs intForColumn:@"state"]
type:(SNTRuleType)[rs intForColumn:@"type"]
customMsg:[rs stringForColumn:@"custommsg"]
timestamp:[rs intForColumn:@"timestamp"]];
r.customURL = [rs stringForColumn:@"customurl"];
Expand Down Expand Up @@ -589,4 +591,25 @@ - (BOOL)fillError:(NSError **)error code:(SNTRuleTableError)code message:(NSStri
return rules;
}

- (NSString *)hashOfHashes {
__block CC_SHA256_CTX sha;
CC_SHA256_Init(&sha);

[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:@"SELECT * FROM rules WHERE type!=?", @(SNTRuleStateAllowTransitive)];
while ([rs next]) {
SNTRule *r = [self ruleFromResultSet:rs];
NSString *digest = r.digest;
CC_SHA256_Update(&sha, digest.UTF8String, (CC_LONG)digest.length);
}
[rs close];
}];

unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &sha);

return santa::SHA256DigestToNSString(digest);
}

@end
24 changes: 24 additions & 0 deletions Source/santad/DataLayer/SNTRuleTableTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#include "Source/common/SNTCommonEnums.h"

#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTRule.h"
Expand Down Expand Up @@ -513,4 +514,27 @@ - (void)testAddedRulesShouldFlushDecisionCacheWithRemoveRule {
XCTAssertEqual(YES, [self.sut addedRulesShouldFlushDecisionCache:@[ r ]]);
}

- (void)testHashOfHashes {
NSArray<SNTRule *> *rules = @[
[self _exampleCertRule],
[self _exampleBinaryRule],
[self _exampleTeamIDRule],
[self _exampleSigningIDRuleIsPlatform:NO],
];
[self.sut addRules:rules ruleCleanup:SNTRuleCleanupAll error:nil];

NSString *hash = [self.sut hashOfHashes];
XCTAssertEqual(hash.length, 64);
XCTAssertEqualObjects(hash, @"395142253ee4b4dc5a55a445b6b2cbffbd4e132480ab8990e84e50577da84b15");

SNTRule *removeRule = self._exampleBinaryRule;
removeRule.state = SNTRuleStateRemove;

[self.sut addRules:@[ removeRule ] ruleCleanup:SNTRuleCleanupNone error:nil];

hash = [self.sut hashOfHashes];
XCTAssertEqual(hash.length, 64);
XCTAssertEqualObjects(hash, @"a172a258906b951b2cec2d7aabfecbe8eb0b5d75e6f9d1e2884eede67903ddd4");
}

@end
4 changes: 4 additions & 0 deletions Source/santad/SNTDaemonControlController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ - (void)retrieveAllRules:(void (^)(NSArray<SNTRule *> *, NSError *))reply {
reply(rules, nil);
}

- (void)databaseRulesHash:(void (^)(NSString *hash))reply {
reply([[SNTDatabaseController ruleTable] hashOfHashes]);
}

#pragma mark Decision Ops

- (void)decisionForFilePath:(NSString *)filePath
Expand Down
9 changes: 7 additions & 2 deletions Source/santasyncservice/SNTSyncPostflight.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

#import <MOLXPCConnection/MOLXPCConnection.h>

#import "Source/common/SNTLogging.h"
#import "Source/common/SNTSyncConstants.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/String.h"
#import "Source/santasyncservice/SNTSyncLogging.h"
#import "Source/santasyncservice/SNTSyncState.h"

#include <google/protobuf/arena.h>
Expand All @@ -41,11 +43,14 @@ - (BOOL)sync {
req->set_rules_received(self.syncState.rulesReceived);
req->set_rules_processed(self.syncState.rulesProcessed);

id<SNTDaemonControlXPC> rop = [self.daemonConn synchronousRemoteObjectProxy];
[rop databaseRulesHash:^(NSString *hash) {
req->set_rules_hash(NSStringToUTF8String(hash));
}];

::pbv1::PostflightResponse response;
[self performRequest:[self requestWithMessage:req] intoMessage:&response timeout:30];

id<SNTDaemonControlXPC> rop = [self.daemonConn synchronousRemoteObjectProxy];

// Set client mode if it changed
if (self.syncState.clientMode) {
[rop setClientMode:self.syncState.clientMode
Expand Down
4 changes: 4 additions & 0 deletions Source/santasyncservice/SNTSyncPreflight.mm
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ - (BOOL)sync {
req->set_cdhash_rule_count(uint32(counts.cdhash));
}];

[rop databaseRulesHash:^(NSString *hash) {
req->set_rules_hash(NSStringToUTF8String(hash));
}];

[rop clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor: req->set_client_mode(::pbv1::MONITOR); break;
Expand Down
Loading

0 comments on commit 76ed896

Please sign in to comment.