Skip to content

Commit

Permalink
Fix santactl rule --check (#1262)
Browse files Browse the repository at this point in the history
* Fix santactl rule check to only strictly show rule info

* Reorganized to make more testable, added tests
  • Loading branch information
mlw committed Jan 3, 2024
1 parent f8a20d3 commit ce2777a
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 57 deletions.
12 changes: 12 additions & 0 deletions Source/santactl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ objc_library(
"Commands/SNTCommandFileInfo.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/SNTCommandRule.h",
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandSync.m",
Expand Down Expand Up @@ -147,11 +148,22 @@ santa_unit_test(
],
)

santa_unit_test(
name = "SNTCommandRuleTest",
srcs = ["Commands/SNTCommandRuleTest.mm"],
deps = [
":santactl_lib",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTRule",
],
)

test_suite(
name = "unit_tests",
tests = [
":SNTCommandFileInfoTest",
":SNTCommandMetricsTest",
":SNTCommandRuleTest",
],
visibility = ["//:santa_package_group"],
)
20 changes: 20 additions & 0 deletions Source/santactl/Commands/SNTCommandRule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// Copyright 2024 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.

#import <Foundation/Foundation.h>

#import "Source/santactl/SNTCommand.h"

@interface SNTCommandRule : SNTCommand <SNTCommandProtocol>
@end
140 changes: 83 additions & 57 deletions Source/santactl/Commands/SNTCommandRule.m
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/SNTCommandRule.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"

@interface SNTCommandRule : SNTCommand <SNTCommandProtocol>
@end

@implementation SNTCommandRule

REGISTER_COMMAND_NAME(@"rule")
Expand Down Expand Up @@ -286,72 +284,100 @@ - (void)runWithArguments:(NSArray *)arguments {
}];
}

// IMPORTANT: This method makes no attempt to validate whether or not the data
// in a rule is valid. It merely constructs a string with the given data.
// E.g., TeamID compiler rules are not currently supproted, but if a test rule
// is provided with that state, an appropriate string will be returned.
+ (NSString *)stringifyRule:(SNTRule *)rule withColor:(BOOL)colorize {
NSMutableString *output;
// Rule state is saved as eventState for output colorization down below
SNTEventState eventState = SNTEventStateUnknown;

switch (rule.state) {
case SNTRuleStateUnknown:
output = [@"No rule exists with the given parameters" mutableCopy];
break;
case SNTRuleStateAllow: OS_FALLTHROUGH;
case SNTRuleStateAllowCompiler: OS_FALLTHROUGH;
case SNTRuleStateAllowTransitive:
output = [@"Allowed" mutableCopy];
eventState = SNTEventStateAllow;
break;
case SNTRuleStateBlock: OS_FALLTHROUGH;
case SNTRuleStateSilentBlock:
output = [@"Blocked" mutableCopy];
eventState = SNTEventStateBlock;
break;
case SNTRuleStateRemove: OS_FALLTHROUGH;
default:
output = [NSMutableString stringWithFormat:@"Unexpected rule state: %ld", rule.state];
break;
}

if (rule.state == SNTRuleStateUnknown) {
// No more output to append
return output;
}

[output appendString:@" ("];

switch (rule.type) {
case SNTRuleTypeUnknown: [output appendString:@"Unknown"]; break;
case SNTRuleTypeBinary: [output appendString:@"Binary"]; break;
case SNTRuleTypeSigningID: [output appendString:@"SigningID"]; break;
case SNTRuleTypeCertificate: [output appendString:@"Certificate"]; break;
case SNTRuleTypeTeamID: [output appendString:@"TeamID"]; break;
default:
output = [NSMutableString stringWithFormat:@"Unexpected rule type: %ld", rule.type];
break;
}

// Add additional attributes
switch (rule.state) {
case SNTRuleStateAllowCompiler: [output appendString:@", Compiler"]; break;
case SNTRuleStateAllowTransitive: [output appendString:@", Transitive"]; break;
case SNTRuleStateSilentBlock: [output appendString:@", Silent"]; break;
default: break;
}

[output appendString:@")"];

// Colorize
if (colorize) {
if ((SNTEventStateAllow & eventState)) {
[output insertString:@"\033[32m" atIndex:0];
[output appendString:@"\033[0m"];
} else if ((SNTEventStateBlock & eventState)) {
[output insertString:@"\033[31m" atIndex:0];
[output appendString:@"\033[0m"];
} else {
[output insertString:@"\033[33m" atIndex:0];
[output appendString:@"\033[0m"];
}
}

if (rule.state == SNTRuleStateAllowTransitive) {
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:rule.timestamp];
[output appendString:[NSString stringWithFormat:@"\nlast access date: %@", [date description]]];
}
return output;
}

- (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)daemonConn {
id<SNTDaemonControlXPC> rop = [daemonConn synchronousRemoteObjectProxy];
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.identifier : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.identifier : nil;
NSString *teamID = (rule.type == SNTRuleTypeTeamID) ? rule.identifier : nil;
NSString *signingID = (rule.type == SNTRuleTypeSigningID) ? rule.identifier : nil;
__block NSMutableString *output;
[rop decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
signingID:signingID
reply:^(SNTEventState s) {
output =
(SNTEventStateAllow & s) ? @"Allowed".mutableCopy : @"Blocked".mutableCopy;
switch (s) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown: [output appendString:@" (Unknown)"]; break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary: [output appendString:@" (Binary)"]; break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
case SNTEventStateAllowCompiler:
[output appendString:@" (Compiler)"];
break;
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID: [output appendString:@" (TeamID)"]; break;
case SNTEventStateAllowSigningID:
case SNTEventStateBlockSigningID:
[output appendString:@" (SigningID)"];
break;
default: output = @"None".mutableCopy; break;
}
if (isatty(STDOUT_FILENO)) {
if ((SNTEventStateAllow & s)) {
[output insertString:@"\033[32m" atIndex:0];
[output appendString:@"\033[0m"];
} else if ((SNTEventStateBlock & s)) {
[output insertString:@"\033[31m" atIndex:0];
[output appendString:@"\033[0m"];
} else {
[output insertString:@"\033[33m" atIndex:0];
[output appendString:@"\033[0m"];
}
}
}];
__block NSString *output;

[rop databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
signingID:signingID
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateAllowTransitive) {
NSDate *date =
[NSDate dateWithTimeIntervalSinceReferenceDate:r.timestamp];
[output appendString:[NSString
stringWithFormat:@"\nlast access date: %@",
[date description]]];
}
output = [SNTCommandRule stringifyRule:r
withColor:(isatty(STDOUT_FILENO) == 1)];
}];

printf("%s\n", output.UTF8String);
Expand Down
96 changes: 96 additions & 0 deletions Source/santactl/Commands/SNTCommandRuleTest.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/// Copyright 2024 Google LLC
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// https://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.

#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

#include <map>
#include <utility>

#import "Source/common/SNTRule.h"
#import "Source/santactl/Commands/SNTCommandRule.h"

@interface SNTCommandRule (Testing)
+ (NSString *)stringifyRule:(SNTRule *)rule withColor:(BOOL)colorize;
@end

@interface SNTRule ()
@property(readwrite) NSUInteger timestamp;
@end

@interface SNTCommandRuleTest : XCTestCase
@end

@implementation SNTCommandRuleTest

- (void)testStringifyRule {
std::map<std::pair<SNTRuleType, SNTRuleState>, NSString *> ruleCheckToString = {
{{SNTRuleTypeUnknown, SNTRuleStateUnknown}, @"No rule exists with the given parameters"},
{{SNTRuleTypeUnknown, SNTRuleStateAllow}, @"Allowed (Unknown)"},
{{SNTRuleTypeUnknown, SNTRuleStateBlock}, @"Blocked (Unknown)"},
{{SNTRuleTypeUnknown, SNTRuleStateSilentBlock}, @"Blocked (Unknown, Silent)"},
{{SNTRuleTypeUnknown, SNTRuleStateRemove}, @"Unexpected rule state: 4 (Unknown)"},
{{SNTRuleTypeUnknown, SNTRuleStateAllowCompiler}, @"Allowed (Unknown, Compiler)"},
{{SNTRuleTypeUnknown, SNTRuleStateAllowTransitive},
@"Allowed (Unknown, Transitive)\nlast access date: 2023-03-08 20:26:40 +0000"},

{{SNTRuleTypeBinary, SNTRuleStateUnknown}, @"No rule exists with the given parameters"},
{{SNTRuleTypeBinary, SNTRuleStateAllow}, @"Allowed (Binary)"},
{{SNTRuleTypeBinary, SNTRuleStateBlock}, @"Blocked (Binary)"},
{{SNTRuleTypeBinary, SNTRuleStateSilentBlock}, @"Blocked (Binary, Silent)"},
{{SNTRuleTypeBinary, SNTRuleStateRemove}, @"Unexpected rule state: 4 (Binary)"},
{{SNTRuleTypeBinary, SNTRuleStateAllowCompiler}, @"Allowed (Binary, Compiler)"},
{{SNTRuleTypeBinary, SNTRuleStateAllowTransitive},
@"Allowed (Binary, Transitive)\nlast access date: 2023-03-08 20:26:40 +0000"},

{{SNTRuleTypeSigningID, SNTRuleStateUnknown}, @"No rule exists with the given parameters"},
{{SNTRuleTypeSigningID, SNTRuleStateAllow}, @"Allowed (SigningID)"},
{{SNTRuleTypeSigningID, SNTRuleStateBlock}, @"Blocked (SigningID)"},
{{SNTRuleTypeSigningID, SNTRuleStateSilentBlock}, @"Blocked (SigningID, Silent)"},
{{SNTRuleTypeSigningID, SNTRuleStateRemove}, @"Unexpected rule state: 4 (SigningID)"},
{{SNTRuleTypeSigningID, SNTRuleStateAllowCompiler}, @"Allowed (SigningID, Compiler)"},
{{SNTRuleTypeSigningID, SNTRuleStateAllowTransitive},
@"Allowed (SigningID, Transitive)\nlast access date: 2023-03-08 20:26:40 +0000"},

{{SNTRuleTypeCertificate, SNTRuleStateUnknown}, @"No rule exists with the given parameters"},
{{SNTRuleTypeCertificate, SNTRuleStateAllow}, @"Allowed (Certificate)"},
{{SNTRuleTypeCertificate, SNTRuleStateBlock}, @"Blocked (Certificate)"},
{{SNTRuleTypeCertificate, SNTRuleStateSilentBlock}, @"Blocked (Certificate, Silent)"},
{{SNTRuleTypeCertificate, SNTRuleStateRemove}, @"Unexpected rule state: 4 (Certificate)"},
{{SNTRuleTypeCertificate, SNTRuleStateAllowCompiler}, @"Allowed (Certificate, Compiler)"},
{{SNTRuleTypeCertificate, SNTRuleStateAllowTransitive},
@"Allowed (Certificate, Transitive)\nlast access date: 2023-03-08 20:26:40 +0000"},

{{SNTRuleTypeTeamID, SNTRuleStateUnknown}, @"No rule exists with the given parameters"},
{{SNTRuleTypeTeamID, SNTRuleStateAllow}, @"Allowed (TeamID)"},
{{SNTRuleTypeTeamID, SNTRuleStateBlock}, @"Blocked (TeamID)"},
{{SNTRuleTypeTeamID, SNTRuleStateSilentBlock}, @"Blocked (TeamID, Silent)"},
{{SNTRuleTypeTeamID, SNTRuleStateRemove}, @"Unexpected rule state: 4 (TeamID)"},
{{SNTRuleTypeTeamID, SNTRuleStateAllowCompiler}, @"Allowed (TeamID, Compiler)"},
{{SNTRuleTypeTeamID, SNTRuleStateAllowTransitive},
@"Allowed (TeamID, Transitive)\nlast access date: 2023-03-08 20:26:40 +0000"},
};

SNTRule *rule = [[SNTRule alloc] init];
rule.timestamp = 700000000; // time interval since reference date

for (const auto &[typeAndState, want] : ruleCheckToString) {
rule.type = typeAndState.first;
rule.state = typeAndState.second;

NSString *got = [SNTCommandRule stringifyRule:rule withColor:NO];
XCTAssertEqualObjects(got, want);
}
}
@end

0 comments on commit ce2777a

Please sign in to comment.