Compare commits

...

7 Commits
1.0.3 ... 1.1

Author SHA1 Message Date
Russell Hancox
99ee0af178 Project: bump version to 1.1 (#436) 2020-02-12 11:17:44 -05:00
Russell Hancox
bf6f78df09 common: Eliminate VLA usage in SNTFileInfo (#435)
VLAs complicate static analysis and bloat stack size. Replace VLA allocation with calls to malloc and free
2020-02-11 10:55:57 -05:00
Russell Hancox
c05806916b santad: Add config flag to block all binaries with bad signatures. (#434)
* santad: Add option to block all binaries with bad signatures.
2020-02-10 13:45:22 -05:00
Russell Hancox
e48ce0cfe3 santad: Move signature fetching into SNTPolicyProcessor (#433)
This also removes an unnecessary hash, checks code signatures on non-MachO files (which is rare but possible) and fixes a rare crash in EndpointSecurityManager
2020-02-07 14:32:00 -05:00
Tom Burgin
eabca469b9 update readme with a note about system extension (#431) 2020-02-06 12:50:33 -05:00
Russell Hancox
f6dc36e812 santactl/sync: Skip event upload for clean sync
This lets a clean sync clear out the existing events without attempting to upload them.
2020-01-13 14:56:01 -05:00
Russell Hancox
ac7cbdfd16 Project: update apple rules to 0.19.0 2020-01-13 14:25:38 -05:00
14 changed files with 155 additions and 126 deletions

View File

@@ -10,7 +10,7 @@
</p>
Santa is a binary whitelisting/blacklisting system for macOS. It consists of a
kernel extension that monitors for executions, a userland daemon that makes
kernel extension (or a system extension on macOS 10.15+) that monitors for executions, a userland daemon that makes
execution decisions based on the contents of a SQLite database, a GUI agent
that notifies the user in case of a block decision and a command-line utility
for managing the system and synchronizing the database with a server.

View File

@@ -17,6 +17,8 @@
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
@class MOLCertificate;
///
/// Store information about executions from decision making for later logging.
///
@@ -26,8 +28,11 @@
@property SNTEventState decision;
@property NSString *decisionExtra;
@property NSString *sha256;
@property NSString *certSHA256;
@property NSString *certCommonName;
@property NSArray<MOLCertificate *> *certChain;
@property NSString *quarantineURL;
@property NSString *customMsg;

View File

@@ -130,6 +130,14 @@
///
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
///
/// Enable bad signature protection, defaults to NO.
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary whitelist
/// rule exists.
///
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
///
/// Defines how event logs are stored. Options are:
/// SNTEventLogTypeSyslog: Sent to ASL or ULS (if built with the 10.12 SDK or later).

View File

@@ -66,6 +66,7 @@ static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString *const kEnableBadSignatureProtectionKey = @"EnableBadSignatureProtection";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
static NSString *const kFileChangesPrefixFiltersKey = @"FileChangesPrefixFilters";
@@ -112,6 +113,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kWhitelistRegexKey : re,
kBlacklistRegexKey : re,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey: number,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
@@ -381,6 +383,11 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : YES;
}
- (BOOL)enableBadSignatureProtection {
NSNumber *number = self.configState[kEnableBadSignatureProtectionKey];
return number ? [number boolValue] : NO;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
}

View File

@@ -143,67 +143,71 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (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[chunkSize];
char *chunk = malloc(chunkSize);
CC_SHA1_CTX c1;
CC_SHA256_CTX c256;
@try {
CC_SHA1_CTX c1;
CC_SHA256_CTX c256;
if (sha1) CC_SHA1_Init(&c1);
if (sha256) CC_SHA256_Init(&c256);
if (sha1) CC_SHA1_Init(&c1);
if (sha256) CC_SHA256_Init(&c256);
int fd = self.fileHandle.fileDescriptor;
int fd = self.fileHandle.fileDescriptor;
fcntl(fd, F_RDAHEAD, 1);
struct radvisory radv;
radv.ra_offset = 0;
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
fcntl(fd, F_RDADVISE, &radv);
ssize_t bytesRead;
fcntl(fd, F_RDAHEAD, 1);
struct radvisory radv;
radv.ra_offset = 0;
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
fcntl(fd, F_RDADVISE, &radv);
ssize_t bytesRead;
for (uint64_t offset = 0; offset < _fileSize;) {
bytesRead = pread(fd, chunk, chunkSize, offset);
if (bytesRead > 0) {
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
offset += bytesRead;
} else if (bytesRead == -1 && errno == EINTR) {
continue;
} else {
return;
for (uint64_t offset = 0; offset < _fileSize;) {
bytesRead = pread(fd, chunk, chunkSize, offset);
if (bytesRead > 0) {
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
offset += bytesRead;
} else if (bytesRead == -1 && errno == EINTR) {
continue;
} else {
return;
}
}
}
// We turn off Read Ahead that we turned on
fcntl(fd, F_RDAHEAD, 0);
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]];
}
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";
// We turn off Read Ahead that we turned on
fcntl(fd, F_RDAHEAD, 0);
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]];
}
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 = [[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]];
}
} @finally {
free(chunk);
}
}

View File

@@ -52,14 +52,16 @@
if (event.idx) [eventIds addObject:event.idx];
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
}
if (!self.syncState.cleanSync) {
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
if (!r) return NO;
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
if (!r) return NO;
// A list of bundle hashes that require their related binary events to be uploaded.
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
// A list of bundle hashes that require their related binary events to be uploaded.
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
LOGI(@"Uploaded %lu events", uploadEvents.count);
LOGI(@"Uploaded %lu events", uploadEvents.count);
}
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];

View File

@@ -231,9 +231,10 @@
NSMutableArray *args = [NSMutableArray arrayWithCapacity:argCount];
for (int i = 0; i < argCount; ++i) {
es_string_token_t arg = es_exec_arg(&(m->event.exec), i);
[args addObject:[[NSString alloc] initWithBytes:arg.data
length:arg.length
encoding:NSUTF8StringEncoding]];
NSString *argStr = [[NSString alloc] initWithBytes:arg.data
length:arg.length
encoding:NSUTF8StringEncoding];
if (argStr.length) [args addObject:argStr];
}
sm.args_array = (void *)CFBridgingRetain(args);
callback = self.logCallback;

View File

@@ -153,8 +153,7 @@ double watchdogRAMPeak = 0;
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply {
reply([self.policyProcessor decisionForFilePath:filePath
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256].decision);
fileSHA256:fileSHA256].decision);
}
#pragma mark Config Ops

View File

@@ -115,50 +115,29 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
[self.eventProvider postAction:ACTION_RESPOND_ACK forMessage:message];
}
// If the binary is a critical system binary, don't check its signature.
// The binary was validated by santad at startup.
SNTCachedDecision *cd = self.ruleTable.criticalSystemBinaries[binInfo.SHA256];
MOLCodesignChecker *csInfo; // Needed further down in this scope.
if (!cd) {
// Get codesigning info about the file but only if it's a Mach-O.
if (binInfo.isMachO) {
NSError *csError;
csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
fileDescriptor:binInfo.fileHandle.fileDescriptor
error:&csError];
// Ignore codesigning if there are any errors with the signature.
if (csError) csInfo = nil;
}
// Actually make the decision (and refresh rule access timestamp).
cd = [self.policyProcessor decisionForFileInfo:binInfo
fileSHA256:nil
certificateSHA256:csInfo.leafCertificate.SHA256];
cd.certCommonName = csInfo.leafCertificate.commonName;
}
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo];
cd.vnodeId = message.vnode_id;
// Formulate an initial action from the decision.
santa_action_t action =
(SNTEventStateAllow & cd.decision) ? ACTION_RESPOND_ALLOW : ACTION_RESPOND_DENY;
// Upgrade the action to ACTION_RESPOND_ALLOW_COMPILER when appropriate, because we want the
// kernel to track this information in its decision cache.
if (cd.decision == SNTEventStateAllowCompiler) {
action = ACTION_RESPOND_ALLOW_COMPILER;
}
// Save decision details for logging the execution later. For transitive rules, we also use
// the shasum stored in the decision details to update the rule's timestamp whenever an
// ACTION_NOTIFY_EXEC message related to the transitive rule is received.
NSString *ttyPath;
if (action == ACTION_RESPOND_ALLOW || action == ACTION_RESPOND_ALLOW_COMPILER) {
if (action == ACTION_RESPOND_ALLOW) {
[_eventLog cacheDecision:cd];
} else {
ttyPath = [self ttyPathForPID:message.ppid];
}
// Upgrade the action to ACTION_RESPOND_ALLOW_COMPILER when appropriate, because we want the
// kernel to track this information in its decision cache.
if (cd.decision == SNTEventStateAllowCompiler) {
action = ACTION_RESPOND_ALLOW_COMPILER;
}
// Send the decision to the kernel.
[self.eventProvider postAction:action forMessage:message];
@@ -174,7 +153,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
se.filePath = binInfo.path;
se.decision = cd.decision;
se.signingChain = csInfo.certificates;
se.signingChain = cd.certChain;
se.pid = @(message.pid);
se.ppid = @(message.ppid);
se.parentName = @(message.pname);

View File

@@ -48,7 +48,6 @@
OCMStub([self.mockCodesignChecker alloc]).andReturn(self.mockCodesignChecker);
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY
fileDescriptor:0
error:[OCMArg setTo:NULL]])
.andReturn(self.mockCodesignChecker);
@@ -61,6 +60,8 @@
OCMStub([self.mockFileInfo alloc]).andReturn(self.mockFileInfo);
OCMStub([self.mockFileInfo initWithPath:OCMOCK_ANY
error:[OCMArg setTo:nil]]).andReturn(self.mockFileInfo);
OCMStub([self.mockFileInfo codesignCheckerWithError:[OCMArg setTo:nil]])
.andReturn(self.mockCodesignChecker);
self.mockRuleDatabase = OCMClassMock([SNTRuleTable class]);
self.mockEventDatabase = OCMClassMock([SNTEventTable class]);

View File

@@ -18,6 +18,7 @@
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTKernelCommon.h"
@class MOLCodesignChecker;
@class SNTCachedDecision;
@class SNTFileInfo;
@class SNTRuleTable;
@@ -38,15 +39,12 @@
/// @param fileInfo A SNTFileInfo object.
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
/// be calculated by this method from the filePath.
/// @param certificateSHA256 A SHA256 hash of the signing certificate, can be nil.
/// @note If fileSHA256 and certificateSHA256 are both passed in, the most specific rule will be
/// returned. Binary rules take precedence over cert rules.
/// @note This method can also be used to generate a SNTCachedDecision object without any
/// artifacts on disk. Simply pass nil to fileInfo and pass in the desired SHA256s.
///
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nullable SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256;
/// Convenience initializer with a nil hash.
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo;
///
/// A wrapper for decisionForFileInfo:fileSHA256:certificateSHA256:. This method is slower as it
@@ -54,8 +52,7 @@
/// SNTFileInfo is not SecureCoding compliant. If the SHA256 hash of the file has already been
/// calculated, use the fileSHA256 parameter to save a second calculation of the hash.
///
- (nonnull SNTCachedDecision *)decisionForFilePath:(nullable NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256;
@end

View File

@@ -14,6 +14,8 @@
#import "Source/santad/SNTPolicyProcessor.h"
#import <MOLCodesignChecker/MOLCodesignChecker.h>
#include "Source/common/SNTLogging.h"
#import "Source/common/SNTCachedDecision.h"
@@ -36,13 +38,29 @@
return self;
}
- (SNTCachedDecision *)decisionForFileInfo:(SNTFileInfo *)fileInfo
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256 {
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256 {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
cd.certSHA256 = certificateSHA256;
// 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];
if (systemCd) return systemCd;
// Grab the code signature, if there's an error don't try to capture
// any of the signature details.
NSError *csInfoError;
MOLCodesignChecker *csInfo = [fileInfo codesignCheckerWithError:&csInfoError];
if (csInfoError) {
csInfo = nil;
cd.decisionExtra =
[NSString stringWithFormat:@"Signature ignored due to error: %ld", (long)csInfoError.code];
}
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.certChain = csInfo.certificates;
cd.quarantineURL = fileInfo.quarantineDataURL;
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
@@ -67,7 +85,7 @@
if ([[SNTConfigurator configurator] enableTransitiveWhitelisting]) {
cd.decision = SNTEventStateAllowCompiler;
} else {
cd.decision = SNTEventStateAllow;
cd.decision = SNTEventStateAllowBinary;
}
return cd;
case SNTRuleStateWhitelistTransitive:
@@ -90,6 +108,7 @@
return cd;
case SNTRuleStateSilentBlacklist:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlacklist:
cd.customMsg = rule.customMsg;
cd.decision = SNTEventStateBlockCertificate;
@@ -102,6 +121,14 @@
}
}
if ([[SNTConfigurator configurator] enableBadSignatureProtection] &&
csInfoError && csInfoError.code != errSecCSUnsigned) {
cd.decisionExtra =
[NSString stringWithFormat:@"Blocked due to signature error: %ld", (long)csInfoError.code];
cd.decision = SNTEventStateBlockCertificate;
return cd;
}
NSString *msg = [self fileIsScopeBlacklisted:fileInfo];
if (msg) {
cd.decisionExtra = msg;
@@ -129,18 +156,17 @@
}
}
- (SNTCachedDecision *)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256 {
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo {
return [self decisionForFileInfo:fileInfo fileSHA256:nil];
}
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256 {
SNTFileInfo *fileInfo;
if (filePath) {
NSError *error;
fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
}
return [self decisionForFileInfo:fileInfo
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256];
NSError *error;
fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
return [self decisionForFileInfo:fileInfo fileSHA256:fileSHA256];
}
///

View File

@@ -8,7 +8,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl",
git_repository(
name = "build_bazel_rules_apple",
remote = "https://github.com/bazelbuild/rules_apple.git",
commit = "97951dce38d",
tag = "0.19.0",
)
load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies")

View File

@@ -1,3 +1,3 @@
"""The version for all Santa components."""
SANTA_VERSION = "1.0.2"
SANTA_VERSION = "1.1"