Skip to content

Commit

Permalink
Merge 97b9c6a into b77b014
Browse files Browse the repository at this point in the history
  • Loading branch information
tnek committed Oct 15, 2021
2 parents b77b014 + 97b9c6a commit e86d828
Show file tree
Hide file tree
Showing 20 changed files with 136 additions and 53 deletions.
1 change: 1 addition & 0 deletions Source/common/SNTCachedDecision.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
@property NSString *certSHA256;
@property NSString *certCommonName;
@property NSArray<MOLCertificate *> *certChain;
@property NSString *teamID;

@property NSString *quarantineURL;

Expand Down
3 changes: 3 additions & 0 deletions Source/common/SNTCommonEnums.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {

SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
SNTRuleTypeTeamID = 3,
};

typedef NS_ENUM(NSInteger, SNTRuleState) {
Expand Down Expand Up @@ -55,6 +56,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,

// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
Expand All @@ -64,6 +66,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
SNTEventStateAllowTeamID = 1 << 31,

// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
Expand Down
1 change: 1 addition & 0 deletions Source/common/SNTXPCControlInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply;

///
Expand Down
9 changes: 9 additions & 0 deletions Source/santactl/Commands/SNTCommandRule.m
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ + (NSString *)longHelpText {
@" --sha256 {sha256}: hash to add/remove/check\n"
@"\n"
@" Optionally:\n"
@" --teamid: add or check a team ID rule instead of binary\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
#ifdef DEBUG
@" --force: allow manual changes even when SyncBaseUrl is set\n"
Expand Down Expand Up @@ -111,6 +112,8 @@ - (void)runWithArguments:(NSArray *)arguments {
check = YES;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--teamid"] == NSOrderedSame) {
newRule.type = SNTRuleTypeTeamID;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--path requires an argument"];
Expand Down Expand Up @@ -186,6 +189,7 @@ - (void)runWithArguments:(NSArray *)arguments {
- (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)daemonConn {
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.shasum : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.shasum : nil;
NSString *teamID = (rule.type == SNTRuleTypeTeamID) ? rule.shasum : nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block NSMutableString *output;
Expand Down Expand Up @@ -219,6 +223,10 @@ - (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)da
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID:
[output appendString:@" (TeamID)"];
break;
default: output = @"None".mutableCopy; break;
}
if (isatty(STDOUT_FILENO)) {
Expand All @@ -244,6 +252,7 @@ - (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)da
[[daemonConn remoteObjectProxy]
databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateAllowTransitive) {
NSDate *date =
Expand Down
3 changes: 2 additions & 1 deletion Source/santad/DataLayer/SNTRuleTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@
/// if it exists. If not, the certificate rule will be returned if it exists.
///
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256;
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID;

///
/// Add an array of rules to the database. The rules will be added within a transaction and the
Expand Down
24 changes: 13 additions & 11 deletions Source/santad/DataLayer/SNTRuleTable.m
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,19 @@ - (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
}

- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256 {
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID {
__block SNTRule *rule;

// NOTE: This code is written with the intention that the binary rule is searched for first
// as Santa is designed to go with the most-specific rule possible. As such the query
// should have "ORDER BY type DESC" before the LIMIT, to ensure that is the case. However,
// in all tested versions of SQLite that ORDER BY clause is unnecessary: the query is
// performed 'as written' by doing 2 separate lookups in the index and the second is skipped
// if the first returns a result. That behavior can be checked here:
// http://sqlfiddle.com/#!5/09c8a/2
// as Santa is designed to go with the most-specific rule possible.
//
// The intended order of precedence is Binaries > Certificates > Team IDs.
//
// As such the query should have "ORDER BY type DESC" before the LIMIT, to ensure that is the
// case. However, in all tested versions of SQLite that ORDER BY clause is unnecessary: the query
// is performed 'as written' by doing separate lookups in the index and the later lookups are if
// the first returns a result. That behavior can be checked here: http://sqlfiddle.com/#!5/cdc42/1
//
// Adding the ORDER BY clause slows down this query, particularly in a database where
// the number of binary rules outweighs the number of certificate rules because:
Expand All @@ -203,10 +206,9 @@ - (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
// There is a test for this in SNTRuleTableTests in case SQLite behavior changes in the future.
//
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:
@"SELECT * FROM rules WHERE (shasum=? and type=1) OR (shasum=? AND type=2) LIMIT 1",
binarySHA256, certificateSHA256];
FMResultSet *rs = [db executeQuery:@"SELECT * FROM rules WHERE (shasum=? and type=1) OR "
@"(shasum=? AND type=2) OR (shasum=? AND type=3) LIMIT 1",
binarySHA256, certificateSHA256, teamID];
if ([rs next]) {
rule = [self ruleFromResultSet:rs];
}
Expand Down
50 changes: 42 additions & 8 deletions Source/santad/DataLayer/SNTRuleTableTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ - (void)setUp {
self.sut = [[SNTRuleTable alloc] initWithDatabaseQueue:self.dbq];
}

- (SNTRule *)_exampleTeamIDRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"teamID";
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeTeamID;
r.customMsg = @"A teamID rule";
return r;
}

- (SNTRule *)_exampleBinaryRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
Expand Down Expand Up @@ -116,12 +125,12 @@ - (void)testFetchBinaryRule {
cleanSlate:NO
error:nil];

SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary);

r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil];
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil teamID:nil];
XCTAssertNil(r);
}

Expand All @@ -130,26 +139,51 @@ - (void)testFetchCertificateRule {
cleanSlate:NO
error:nil];

SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b"];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b" teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"b");
XCTAssertEqual(r.type, SNTRuleTypeCertificate);

r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a"];
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil];
XCTAssertNil(r);
}

- (void)testFetchRuleOrdering {
[self.sut addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule] ]
- (void)testFetchTeamIDRule {
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];

SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"teamID");
XCTAssertEqual(r.type, SNTRuleTypeTeamID);

r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"nonexistentTeamID"];
XCTAssertNil(r);
}

- (void)testFetchRuleOrdering {
[self.sut
addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];

// This test verifies that the implicit rule ordering we've been abusing is still working.
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b"];
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:teamID
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");

r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"unknowncert" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");

r = [self.sut ruleForBinarySHA256:@"unknown" certificateSHA256:@"b" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"b");
XCTAssertEqual(r.type, SNTRuleTypeCertificate, @"Implicit rule ordering failed");
}

- (void)testBadDatabase {
Expand Down
21 changes: 3 additions & 18 deletions Source/santad/SNTApplicationTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,31 +93,16 @@ - (void)checkBinaryExecution:(NSString *)binaryName
testPath, binaryName);
}

- (void)testBinaryRules {
- (void)testRules {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSDictionary *testCases = @{
@"badbinary" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"goodbinary" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"noop" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],

};
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];

for (NSString *binary in testCases) {
[self checkBinaryExecution:binary
testPath:fullTestPath
wantResult:[testCases[binary] intValue]];
}
}

- (void)testCertRules {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSDictionary *testCases = @{
@"banned_teamid" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"banned_teamid_allowed_binary" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"badcert" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"goodcert" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"noop" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
};
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
Expand Down
2 changes: 1 addition & 1 deletion Source/santad/SNTCompilerController.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ - (void)createTransitiveRule:(santa_message_t)message {
// Check if there is an existing (non-transitive) rule for this file. We leave existing rules
// alone, so that a allowlist or blocklist rule can't be overwritten by a transitive one.
SNTRuleTable *ruleTable = [SNTDatabaseController ruleTable];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil teamID:nil];
if (!prevRule || prevRule.state == SNTRuleStateAllowTransitive) {
// Construct a new transitive allowlist rule for the executable.
SNTRule *rule = [[SNTRule alloc] initWithShasum:fi.SHA256
Expand Down
4 changes: 3 additions & 1 deletion Source/santad/SNTDaemonControlController.m
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,11 @@ - (void)databaseRemoveEventsWithIDs:(NSArray *)ids {

- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] ruleForBinarySHA256:binarySHA256
certificateSHA256:certificateSHA256]);
certificateSHA256:certificateSHA256
teamID:teamID]);
}

#pragma mark Decision Ops
Expand Down
1 change: 1 addition & 0 deletions Source/santad/SNTExecutionController.m
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ - (void)validateBinaryWithMessage:(santa_message_t)message {
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
return;
}

NSError *fileInfoError;
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
if (unlikely(!binInfo)) {
Expand Down
24 changes: 16 additions & 8 deletions Source/santad/SNTExecutionControllerTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ - (void)testBinaryAllowRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -118,7 +119,8 @@ - (void)testBinaryBlockRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateBlock;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -135,7 +137,8 @@ - (void)testCertificateAllowRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeCertificate;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -152,7 +155,8 @@ - (void)testCertificateBlockRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateBlock;
rule.type = SNTRuleTypeCertificate;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -167,7 +171,8 @@ - (void)testBinaryAllowCompilerRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -183,7 +188,8 @@ - (void)testBinaryAllowCompilerRuleDisabled {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -198,7 +204,8 @@ - (void)testBinaryAllowTransitiveRule {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand All @@ -214,7 +221,8 @@ - (void)testBinaryAllowTransitiveRuleDisabled {
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);

[self.sut validateBinaryWithMessage:[self getMessage]];

Expand Down
3 changes: 2 additions & 1 deletion Source/santad/SNTPolicyProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
///
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID;

/// Convenience initializer with nil hashes for both the file and certificate.
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo;
Expand Down
Loading

0 comments on commit e86d828

Please sign in to comment.