mirror of
https://github.com/google/santa.git
synced 2026-01-21 20:18:11 -05:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91aefe25c4 | ||
|
|
a8c11097d9 | ||
|
|
92ba4a3ae9 | ||
|
|
7c5d382010 | ||
|
|
f8fbaefd86 | ||
|
|
181b37296a | ||
|
|
2ab61cfa12 | ||
|
|
1b0e9b14ef | ||
|
|
2aacc9266f | ||
|
|
d648d477bb | ||
|
|
6f91c1a1d3 | ||
|
|
aa1aca24b7 | ||
|
|
6a0867172f | ||
|
|
f025a4b2fb | ||
|
|
8871f36a92 | ||
|
|
f17490edad | ||
|
|
b360e782c6 | ||
|
|
8d94324dd6 | ||
|
|
2818609412 | ||
|
|
270a2e69d4 | ||
|
|
d1d9762e29 | ||
|
|
1666e8b127 | ||
|
|
08dfad208b | ||
|
|
b5921f95f3 | ||
|
|
2063bc3db3 | ||
|
|
4380016d52 | ||
|
|
5e3ceabe46 | ||
|
|
8e7936275b | ||
|
|
4b967239fa | ||
|
|
92945c384c | ||
|
|
79d93c4ecf | ||
|
|
76b6f25b0c | ||
|
|
aadce4890a | ||
|
|
0e95a98fc2 | ||
|
|
9483437e8f | ||
|
|
59542f8aef | ||
|
|
e29f7332f5 |
@@ -1,6 +1,8 @@
|
||||
---
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
cache:
|
||||
- bundler
|
||||
- cocoapods
|
||||
sudo: false
|
||||
osx_image: xcode7
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ sleep 1
|
||||
sleep 1
|
||||
|
||||
# Create hopefully useful symlink for santactl
|
||||
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "$user" ]] && exit 0
|
||||
|
||||
@@ -36,6 +36,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
# Copy new files.
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
|
||||
|
||||
26
Conf/uninstall.sh
Executable file
26
Conf/uninstall.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Uninstalls Santa from the boot volume, clearing up everything but logs/configs.
|
||||
# Unloads the kernel extension, services, and deletes component files.
|
||||
# If a user is logged in, also unloads the GUI agent.
|
||||
|
||||
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
# and to clean out the log config, although it won't write after wiping the binary
|
||||
/usr/bin/killall -HUP syslogd
|
||||
# delete artifacts on-disk
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /usr/local/santactl # just a symlink
|
||||
#uncomment to remove the config file and all databases, log files
|
||||
#/bin/rm -rf /var/db/santa
|
||||
#/bin/rm -f /var/log/santa*
|
||||
exit 0
|
||||
@@ -2,8 +2,8 @@ PODS:
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (1.8):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- MOLAuthenticatingURLSession (2.1):
|
||||
- MOLCertificate (~> 1.5)
|
||||
- MOLCertificate (1.5)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
@@ -18,7 +18,7 @@ DEPENDENCIES:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: d04d93e7fe209533befb3d0e70a6675aa7f21d5a
|
||||
MOLAuthenticatingURLSession: 2f0fd35f641bc857ee1b026021dbd759955adaa3
|
||||
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
OCMock: f3f61e6eaa16038c30caa5798c5e49d3307b6f22
|
||||
|
||||
@@ -121,6 +121,9 @@ A tool like Santa doesn't really lend itself to screenshots, so here's a video i
|
||||
|
||||
Building
|
||||
========
|
||||
Firstly, make sure you're using Xcode 7.3.1 as currently we do not support
|
||||
building with Xcode 8.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/google/santa
|
||||
cd santa
|
||||
|
||||
@@ -176,7 +176,13 @@
|
||||
580046F725A5D0874B970A17 /* libPods-Santa.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */; };
|
||||
79C1556E6EAC94038762EF36 /* libPods-santactl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 556108C12FC29E329D82D4CB /* libPods-santactl.a */; };
|
||||
A60673DE57680AC450A3B0B2 /* libPods-santad.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9BE438428F17C09C6A9D0802 /* libPods-santad.a */; };
|
||||
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */; };
|
||||
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; };
|
||||
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */; };
|
||||
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
|
||||
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
|
||||
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
|
||||
EFD8E30D32F6128B9E833D64 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -410,7 +416,10 @@
|
||||
9BE438428F17C09C6A9D0802 /* libPods-santad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santad.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B2B9044B79DD2E4DEC5D3B7A /* libPods-Santa.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Santa.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
BE53E1EAE84D54E7FCB22FD5 /* libPods-santactl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santactl.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFileInfoTest.m; sourceTree = "<group>"; };
|
||||
C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandCheckCache.m; sourceTree = "<group>"; };
|
||||
C795ED8E1D80A5BE007CFF42 /* SNTPolicyProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTPolicyProcessor.h; sourceTree = "<group>"; };
|
||||
C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTPolicyProcessor.m; sourceTree = "<group>"; };
|
||||
D227889DF327E7D3532FE00B /* Pods-Santa.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -432,6 +441,7 @@
|
||||
0D3AFBF618FB4C7E0087BCEE /* Cocoa.framework in Frameworks */,
|
||||
0D3AFBF818FB4C870087BCEE /* IOKit.framework in Frameworks */,
|
||||
29C490B1720D4FD576F93519 /* libPods-LogicTests.a in Frameworks */,
|
||||
EFD8E30D32F6128B9E833D64 /* libPods-LogicTests.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -497,6 +507,7 @@
|
||||
0D91BCB6174E8A7E00131A7D /* Frameworks */,
|
||||
0D260DB018B68E12002A0B55 /* Resources */,
|
||||
0D2E1E621CEFA6C30039B2C4 /* SantaCacheTest.mm */,
|
||||
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */,
|
||||
0D202D181CDD2EE500A88F16 /* SNTCommandSyncTest.m */,
|
||||
0D41DAD31A7C28C800A890FE /* SNTEventTableTest.m */,
|
||||
0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */,
|
||||
@@ -731,6 +742,8 @@
|
||||
0D8E18CC19107B56000F89B8 /* SNTDaemonControlController.m */,
|
||||
0D63DD5A1906FCB400D346C4 /* SNTDatabaseController.h */,
|
||||
0D63DD5B1906FCB400D346C4 /* SNTDatabaseController.m */,
|
||||
C795ED8E1D80A5BE007CFF42 /* SNTPolicyProcessor.h */,
|
||||
C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */,
|
||||
0D7D01851774F93A005DBAB4 /* SNTDriverManager.h */,
|
||||
0D7D01861774F93A005DBAB4 /* SNTDriverManager.m */,
|
||||
0D536ED91B94E9230039A26D /* SNTEventLog.h */,
|
||||
@@ -1277,6 +1290,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */,
|
||||
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */,
|
||||
0D88680C1AC48A1400B86659 /* SNTSystemInfo.m in Sources */,
|
||||
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */,
|
||||
@@ -1296,6 +1311,8 @@
|
||||
0D202D1A1CDD464B00A88F16 /* SNTCommandSyncPreflight.m in Sources */,
|
||||
0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */,
|
||||
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */,
|
||||
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */,
|
||||
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */,
|
||||
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */,
|
||||
0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */,
|
||||
0DE5B54C1C92722300C00603 /* SNTNotificationQueue.m in Sources */,
|
||||
@@ -1411,6 +1428,7 @@
|
||||
0D37C10F18F6029A0069BC61 /* SNTDatabaseTable.m in Sources */,
|
||||
0D42D2B819D2042900955F08 /* SNTConfigurator.m in Sources */,
|
||||
0DCD605519115D17006B445C /* SNTXPCControlInterface.m in Sources */,
|
||||
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */,
|
||||
0D536EDB1B94E9230039A26D /* SNTEventLog.m in Sources */,
|
||||
0DCD604F19115A06006B445C /* SNTXPCNotifierInterface.m in Sources */,
|
||||
0DE5B54B1C926E3300C00603 /* SNTNotificationQueue.m in Sources */,
|
||||
@@ -1785,7 +1803,7 @@
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = c99;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
LLVM_LTO = YES;
|
||||
LLVM_LTO = NO;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.9;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PROVISIONING_PROFILE = "";
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr, *versionStr;
|
||||
if (config.eventDetailBundleURL && event.fileBundleID) {
|
||||
if (config.eventDetailBundleURL.length && event.fileBundleID) {
|
||||
formatStr = config.eventDetailBundleURL;
|
||||
versionStr = event.fileBundleVersion;
|
||||
if (!versionStr) versionStr = event.fileBundleVersionString;
|
||||
|
||||
@@ -41,19 +41,25 @@ typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateUnknown,
|
||||
// Bits 0-15 bits store non-decision types
|
||||
SNTEventStateUnknown = 0,
|
||||
SNTEventStateBundleBinary = 1,
|
||||
|
||||
SNTEventStateAllowUnknown = 1,
|
||||
SNTEventStateAllowBinary = 2,
|
||||
SNTEventStateAllowCertificate = 3,
|
||||
SNTEventStateAllowScope = 4,
|
||||
// Bits 16-23 store deny decision types
|
||||
SNTEventStateBlockUnknown = 1 << 16,
|
||||
SNTEventStateBlockBinary = 1 << 17,
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
|
||||
SNTEventStateBlockUnknown = 5,
|
||||
SNTEventStateBlockBinary = 6,
|
||||
SNTEventStateBlockCertificate = 7,
|
||||
SNTEventStateBlockScope = 8,
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
SNTEventStateAllowBinary = 1 << 25,
|
||||
SNTEventStateAllowCertificate = 1 << 26,
|
||||
SNTEventStateAllowScope = 1 << 27,
|
||||
|
||||
SNTEventStateBundleBinary = 9,
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
SNTEventStateAllow = 0xFF << 24
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
|
||||
@@ -100,7 +100,13 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (SNTClientMode)clientMode {
|
||||
NSInteger cm = [self.configData[kClientModeKey] longValue];
|
||||
NSInteger cm = SNTClientModeUnknown;
|
||||
|
||||
id mode = self.configData[kClientModeKey];
|
||||
if ([mode respondsToSelector:@selector(longLongValue)]) {
|
||||
cm = (NSInteger)[mode longLongValue];
|
||||
}
|
||||
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return (SNTClientMode)cm;
|
||||
} else {
|
||||
|
||||
@@ -76,9 +76,7 @@
|
||||
});
|
||||
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
if (fd > 0) close(fd);
|
||||
close(fd);
|
||||
});
|
||||
|
||||
dispatch_resume(self.source);
|
||||
@@ -87,13 +85,7 @@
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.source) return;
|
||||
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
dispatch_source_set_event_handler_f(self.source, NULL);
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
close(fd);
|
||||
});
|
||||
|
||||
dispatch_source_cancel(self.source);
|
||||
self.source = nil;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
@@ -46,6 +49,22 @@
|
||||
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
|
||||
certificateSHA256:(NSString *)certificateSHA256
|
||||
reply:(void (^)(SNTRule *))reply;
|
||||
///
|
||||
/// Decision ops
|
||||
///
|
||||
|
||||
///
|
||||
/// @param filePath A Path to the file, can be nil.
|
||||
/// @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 signingCertificate A MOLCertificate object, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
///
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate
|
||||
reply:(void (^)(SNTEventState))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
#define likely(x) __builtin_expect((x), 1)
|
||||
#define unlikely(x) __builtin_expect((x), 0)
|
||||
|
||||
#ifdef KERNEL
|
||||
#include <IOKit/IOLib.h>
|
||||
#else // KERNEL
|
||||
|
||||
@@ -205,10 +205,13 @@ void SantaDecisionManager::AddToCache(
|
||||
if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
|
||||
decision_cache_->remove(identifier);
|
||||
}
|
||||
|
||||
wakeup((void *)identifier);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
|
||||
decision_cache_->remove(identifier);
|
||||
wakeup((void *)identifier);
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::CacheCount() const {
|
||||
@@ -299,17 +302,22 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
while (true) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id);
|
||||
|
||||
// If item was in cache return it.
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
return return_action;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY) {
|
||||
msleep((void *)vnode_id, NULL, 0, "", &ts_);
|
||||
return FetchDecision(cred, vp, vnode_id);
|
||||
// If item was in cache with a valid response, return it.
|
||||
// If item is in cache but hasn't received a response yet, sleep for a bit.
|
||||
// If item is not in cache, break out of loop to send request to daemon.
|
||||
if (RESPONSE_VALID(return_action)) {
|
||||
return return_action;
|
||||
} else if (return_action == ACTION_REQUEST_BINARY) {
|
||||
msleep((void *)vnode_id, NULL, 0, "", &ts_);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Get path
|
||||
@@ -324,7 +332,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
message->action = ACTION_REQUEST_BINARY;
|
||||
message->vnode_id = vnode_id;
|
||||
proc_name(message->ppid, message->pname, sizeof(message->pname));
|
||||
return_action = GetFromDaemon(message, vnode_id);
|
||||
auto return_action = GetFromDaemon(message, vnode_id);
|
||||
delete message;
|
||||
return return_action;
|
||||
}
|
||||
@@ -382,9 +390,6 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
|
||||
@@ -425,6 +430,8 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (!ClientConnected() || proc_selfpid() == client_pid_) return;
|
||||
|
||||
if (vp) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
@@ -451,10 +458,7 @@ void SantaDecisionManager::FileOpCallback(
|
||||
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (client_pid_ > 0 &&
|
||||
proc_selfpid() != client_pid_ &&
|
||||
!strprefix(path, "/.") &&
|
||||
!strprefix(path, "/dev")) {
|
||||
if (!strprefix(path, "/.") && !strprefix(path, "/dev")) {
|
||||
auto message = NewMessage(nullptr);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
@@ -494,6 +498,11 @@ extern "C" int fileop_scope_callback(
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("fileop_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
char *path = nullptr;
|
||||
char *new_path = nullptr;
|
||||
@@ -525,24 +534,28 @@ extern "C" int fileop_scope_callback(
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
if (action & KAUTH_VNODE_ACCESS || idata == nullptr) {
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
if (action & KAUTH_VNODE_EXECUTE) {
|
||||
if (unlikely(sdm == nullptr)) {
|
||||
LOGE("vnode_scope_callback called with no decision manager");
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
|
||||
|
||||
// We only care about regular files.
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
if ((action & KAUTH_VNODE_EXECUTE) && !(action & KAUTH_VNODE_ACCESS)) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
reinterpret_cast<vnode_t>(arg1),
|
||||
vp,
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
} else if (action & KAUTH_VNODE_WRITE_DATA) {
|
||||
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
sdm->IncrementListenerInvocations();
|
||||
char path[MAXPATHLEN];
|
||||
int pathlen = MAXPATHLEN;
|
||||
|
||||
@@ -110,99 +110,76 @@ IOReturn SantaDriverClient::clientMemoryForType(
|
||||
|
||||
#pragma mark Callable Methods
|
||||
|
||||
IOReturn SantaDriverClient::open() {
|
||||
if (isInactive()) return kIOReturnNotAttached;
|
||||
IOReturn SantaDriverClient::open(
|
||||
OSObject *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
if (!myProvider->open(this)) {
|
||||
if (me->isInactive()) return kIOReturnNotAttached;
|
||||
if (!me->myProvider->open(me)) {
|
||||
LOGW("A second client tried to connect.");
|
||||
return kIOReturnExclusiveAccess;
|
||||
}
|
||||
|
||||
LOGI("Client connected.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_open(
|
||||
SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->open();
|
||||
}
|
||||
IOReturn SantaDriverClient::allow_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
|
||||
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
|
||||
|
||||
IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) {
|
||||
decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
|
||||
wakeup((void *)vnode_id);
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_allow_binary(
|
||||
SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
|
||||
IOReturn SantaDriverClient::deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
return target->allow_binary(
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
|
||||
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
|
||||
decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
|
||||
wakeup((void *)vnode_id);
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_deny_binary(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
|
||||
IOReturn SantaDriverClient::clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
return target->deny_binary(
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
me->decisionManager->ClearCache();
|
||||
|
||||
IOReturn SantaDriverClient::clear_cache() {
|
||||
decisionManager->ClearCache();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_clear_cache(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->clear_cache();
|
||||
}
|
||||
IOReturn SantaDriverClient::cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
IOReturn SantaDriverClient::cache_count(uint64_t *output) {
|
||||
*output = decisionManager->CacheCount();
|
||||
arguments->scalarOutput[0] = me->decisionManager->CacheCount();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_cache_count(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->cache_count(&(arguments->scalarOutput[0]));
|
||||
}
|
||||
IOReturn SantaDriverClient::check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
|
||||
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
|
||||
if (!me) return kIOReturnBadArgument;
|
||||
|
||||
uint64_t input = *arguments->scalarInput;
|
||||
arguments->scalarOutput[0] = me->decisionManager->GetFromCache(input);
|
||||
|
||||
IOReturn SantaDriverClient::check_cache(uint64_t vnode_id, uint64_t *output) {
|
||||
*output = decisionManager->GetFromCache(vnode_id);
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::static_check_cache(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
return target->check_cache(reinterpret_cast<uint64_t>(*arguments->scalarInput),
|
||||
&(arguments->scalarOutput[0]));
|
||||
}
|
||||
|
||||
#pragma mark Method Resolution
|
||||
|
||||
IOReturn SantaDriverClient::externalMethod(
|
||||
@@ -214,67 +191,22 @@ IOReturn SantaDriverClient::externalMethod(
|
||||
/// Array of methods callable by clients. The order of these must match the
|
||||
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
|
||||
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(&SantaDriverClient::static_open),
|
||||
0, // input scalar
|
||||
0, // input struct
|
||||
0, // output scalar
|
||||
0 // output struct
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_allow_binary),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_deny_binary),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_clear_cache),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_cache_count),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0
|
||||
},
|
||||
{
|
||||
reinterpret_cast<IOExternalMethodAction>(
|
||||
&SantaDriverClient::static_check_cache),
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0
|
||||
}
|
||||
// Function ptr, input scalar count, input struct size, output scalar count, output struct size
|
||||
{ &SantaDriverClient::open, 0, 0, 0, 0 },
|
||||
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
|
||||
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
|
||||
{ &SantaDriverClient::clear_cache, 0, 0, 0, 0 },
|
||||
{ &SantaDriverClient::cache_count, 0, 0, 1, 0 },
|
||||
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }
|
||||
};
|
||||
|
||||
if (selector < static_cast<UInt32>(kSantaUserClientNMethods)) {
|
||||
dispatch = &(sMethods[selector]);
|
||||
if (!target) target = this;
|
||||
} else {
|
||||
if (selector > static_cast<UInt32>(kSantaUserClientNMethods)) {
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
return super::externalMethod(selector,
|
||||
arguments,
|
||||
dispatch,
|
||||
target,
|
||||
reference);
|
||||
dispatch = &(sMethods[selector]);
|
||||
if (!target) target = this;
|
||||
return super::externalMethod(selector, arguments, dispatch, target, reference);
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
@@ -72,52 +72,33 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
|
||||
///
|
||||
/// The userpsace callable methods are below. Each method corresponds
|
||||
/// to an entry in SantaDriverMethods. Each method has a static version
|
||||
/// which just calls the method on the provided target.
|
||||
/// to an entry in SantaDriverMethods.
|
||||
///
|
||||
|
||||
/// Called during client connection.
|
||||
IOReturn open();
|
||||
static IOReturn static_open(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn open(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to allow a binary.
|
||||
IOReturn allow_binary(uint64_t vnode_id);
|
||||
static IOReturn static_allow_binary(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn allow_binary(
|
||||
OSObject *target, void *reference,IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to deny a binary.
|
||||
IOReturn deny_binary(uint64_t vnode_id);
|
||||
static IOReturn static_deny_binary(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn deny_binary(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to empty the cache.
|
||||
IOReturn clear_cache();
|
||||
static IOReturn static_clear_cache(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn clear_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out how many items are in the cache
|
||||
IOReturn cache_count(uint64_t *output);
|
||||
static IOReturn static_cache_count(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn cache_count(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
/// The daemon calls this to find out the status of a vnode_id in the cache.
|
||||
/// Output will be a santa_action_t.
|
||||
IOReturn check_cache(uint64_t vnode_id, uint64_t *output);
|
||||
static IOReturn static_check_cache(
|
||||
com_google_SantaDriverClient *target,
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments);
|
||||
static IOReturn check_cache(
|
||||
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
|
||||
|
||||
private:
|
||||
com_google_SantaDriver *myProvider;
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
@@ -49,6 +50,18 @@ static NSString *const kValidUntil = @"Valid Until";
|
||||
static NSString *const kSHA256 = @"SHA-256";
|
||||
static NSString *const kSHA1 = @"SHA-1";
|
||||
|
||||
// global json output flag
|
||||
static BOOL json = NO;
|
||||
|
||||
BOOL PrettyOutput() {
|
||||
static int tty = 0;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
tty = isatty(STDOUT_FILENO);
|
||||
});
|
||||
return (tty && !json);
|
||||
}
|
||||
|
||||
#pragma mark SNTCommandFileInfo
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject<SNTCommand>
|
||||
@@ -81,14 +94,11 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
|
||||
@property(readonly, copy, nonatomic) SNTAttributeBlock signingChain;
|
||||
|
||||
// Mapping between property string keys and SNTAttributeBlocks
|
||||
@property(readonly, nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
|
||||
@property(nonatomic) NSMutableDictionary<NSString *, SNTAttributeBlock> *propertyMap;
|
||||
|
||||
// Common Date Formatter
|
||||
@property(nonatomic) NSDateFormatter *dateFormatter;
|
||||
|
||||
// CLI option
|
||||
@property(nonatomic) BOOL jsonOutput;
|
||||
|
||||
// Block Helpers
|
||||
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi;
|
||||
|
||||
@@ -99,13 +109,11 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
|
||||
REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath
|
||||
daemonConnection:(SNTXPCConnection *)daemonConn
|
||||
jsonOutput:(BOOL)jsonOutput {
|
||||
daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_daemonConn = daemonConn;
|
||||
_jsonOutput = jsonOutput;
|
||||
_dateFormatter = [[NSDateFormatter alloc] init];
|
||||
_dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
_propertyMap = @{ kPath : self.path,
|
||||
@@ -122,7 +130,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
kPageZero : self.pageZero,
|
||||
kCodeSigned : self.codeSigned,
|
||||
kRule : self.rule,
|
||||
kSigningChain : self.signingChain };
|
||||
kSigningChain : self.signingChain }.mutableCopy;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -133,9 +141,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
if (!_fileInfo) {
|
||||
_fileInfo = [[SNTFileInfo alloc] initWithPath:self.filePath];
|
||||
if (!_fileInfo) {
|
||||
if (isatty(STDOUT_FILENO) && !self.jsonOutput) {
|
||||
printf("\rInvalid or empty file: %s\n", self.filePath.UTF8String);
|
||||
}
|
||||
fprintf(stderr, "\rInvalid or empty file: %s\n", self.filePath.UTF8String);
|
||||
}
|
||||
}
|
||||
return _fileInfo;
|
||||
@@ -224,7 +230,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
- (SNTAttributeBlock)codeSigned {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:self.filePath error:&error];
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
@@ -261,51 +267,62 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
- (SNTAttributeBlock)rule {
|
||||
return ^id (SNTCommandFileInfo *fi) {
|
||||
__block SNTRule *r;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
__block SNTEventState s;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[fi.daemonConn resume];
|
||||
});
|
||||
dispatch_group_enter(group);
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
if (!fi.csc) {
|
||||
NSError *error;
|
||||
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
|
||||
}
|
||||
NSString *leafCertSHA = [[fi.csc.certificates firstObject] SHA256];
|
||||
[[fi.daemonConn remoteObjectProxy] databaseRuleForBinarySHA256:fi.fileInfo.SHA256
|
||||
certificateSHA256:leafCertSHA
|
||||
reply:^(SNTRule *rule) {
|
||||
if (rule) r = rule;
|
||||
dispatch_group_leave(group);
|
||||
[[fi.daemonConn remoteObjectProxy] decisionForFilePath:fi.filePath
|
||||
fileSHA256:fi.propertyMap[kSHA256](fi)
|
||||
signingCertificate:fi.csc.leafCertificate
|
||||
reply:^(SNTEventState state) {
|
||||
if (state) s = state;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
return @"Cannot communicate with daemon";
|
||||
} else {
|
||||
NSString *output;
|
||||
switch (r.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
output = @"Whitelisted";
|
||||
if (isatty(STDOUT_FILENO) && !fi.jsonOutput) {
|
||||
output = @"\033[32mWhitelisted\033[0m";
|
||||
}
|
||||
return output;
|
||||
NSMutableString *output =
|
||||
(SNTEventStateAllow & s) ? @"Whitelisted".mutableCopy : @"Blacklisted".mutableCopy;
|
||||
switch (s) {
|
||||
case SNTEventStateAllowUnknown:
|
||||
case SNTEventStateBlockUnknown:
|
||||
[output appendString:@" (Unknown)"];
|
||||
break;
|
||||
case SNTRuleStateBlacklist:
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
output = @"Blacklisted";
|
||||
if (isatty(STDOUT_FILENO) && !fi.jsonOutput) {
|
||||
output = @"\033[31mBlacklisted\033[0m";
|
||||
}
|
||||
return output;
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateBlockBinary:
|
||||
[output appendString:@" (Binary)"];
|
||||
break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateBlockCertificate:
|
||||
[output appendString:@" (Certificate)"];
|
||||
break;
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateBlockScope:
|
||||
[output appendString:@" (Scope)"];
|
||||
break;
|
||||
default:
|
||||
output = @"None";
|
||||
if (isatty(STDOUT_FILENO) && !fi.jsonOutput) {
|
||||
output = @"\033[33mNone\033[0m";
|
||||
}
|
||||
return output;
|
||||
output = @"None".mutableCopy;
|
||||
break;
|
||||
}
|
||||
if (PrettyOutput()) {
|
||||
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"];
|
||||
}
|
||||
}
|
||||
return output.copy;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -387,13 +404,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
#ifdef DEBUG
|
||||
NSDate *startTime = [NSDate date];
|
||||
#endif
|
||||
|
||||
if (!arguments.count) [self printErrorUsageAndExit:@"No arguments"];
|
||||
|
||||
BOOL jsonOutput = NO;
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
NSArray *filePaths;
|
||||
@@ -401,23 +413,25 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
[self parseArguments:arguments
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
jsonOutput:&json
|
||||
filePaths:&filePaths];
|
||||
|
||||
__block NSMutableArray *outputHashes = [[NSMutableArray alloc] init];
|
||||
// Only access outputHashes from the outputHashesQueue
|
||||
__block NSMutableArray *outputHashes = [[NSMutableArray alloc] initWithCapacity:filePaths.count];
|
||||
dispatch_group_t outputHashesGroup = dispatch_group_create();
|
||||
dispatch_queue_t outputHashesQueue =
|
||||
dispatch_queue_create("com.google.santa.outputhashes", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
__block NSOperationQueue *hashQueue = [[NSOperationQueue alloc] init];
|
||||
hashQueue.maxConcurrentOperationCount = 15;
|
||||
|
||||
__block NSUInteger hashed = 0;
|
||||
|
||||
[filePaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
NSBlockOperation *hashOperation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
if (isatty(STDOUT_FILENO) && !jsonOutput) {
|
||||
printf("\rCalculating %lu/%lu", ++hashed, filePaths.count);
|
||||
}
|
||||
if (PrettyOutput()) printf("\rCalculating %lu/%lu", ++hashed, filePaths.count);
|
||||
|
||||
SNTCommandFileInfo *fi = [[self alloc] initWithFilePath:obj
|
||||
daemonConnection:daemonConn
|
||||
jsonOutput:jsonOutput];
|
||||
SNTCommandFileInfo *fi = [[self alloc] initWithFilePath:obj daemonConnection:daemonConn];
|
||||
if (!fi.fileInfo) return;
|
||||
|
||||
__block NSMutableDictionary *outputHash = [[NSMutableDictionary alloc] init];
|
||||
@@ -451,26 +465,36 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSString *sha1, *sha256;
|
||||
[fi.fileInfo hashSHA1:&sha1 SHA256:&sha256];
|
||||
fi.propertyMap[kSHA1] = ^id (SNTCommandFileInfo *fi) { return sha1; };
|
||||
fi.propertyMap[kSHA256] = ^id (SNTCommandFileInfo *fi) { return sha256; };
|
||||
[fi.propertyMap enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
||||
SNTAttributeBlock block = fi.propertyMap[key];
|
||||
SNTAttributeBlock block = obj;
|
||||
outputHash[key] = block(fi);
|
||||
}];
|
||||
}
|
||||
if (outputHash.count) [outputHashes addObject:outputHash];
|
||||
if (outputHash.count) {
|
||||
dispatch_group_async(outputHashesGroup, outputHashesQueue, ^{
|
||||
[outputHashes addObject:outputHash];
|
||||
});
|
||||
}
|
||||
}];
|
||||
|
||||
hashOperation.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
[hashQueue addOperation:hashOperation];
|
||||
}];
|
||||
|
||||
// Wait for all the calculating threads to finish
|
||||
[hashQueue waitUntilAllOperationsAreFinished];
|
||||
printf("\33[2K\r");
|
||||
if (outputHashes.count) [self printOutputHashes:outputHashes jsonOutput:jsonOutput];
|
||||
|
||||
#ifdef DEBUG
|
||||
if (isatty(STDOUT_FILENO) && !jsonOutput) {
|
||||
printf("Calculating time: %f\n", [[NSDate date] timeIntervalSinceDate:startTime]);
|
||||
}
|
||||
#endif
|
||||
// Clear the "Calculating ..." indicator if present
|
||||
if (PrettyOutput()) printf("\33[2K\r");
|
||||
|
||||
// Wait for all the writes to the outputHashes to finish
|
||||
dispatch_group_wait(outputHashesGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
if (outputHashes.count) [self printOutputHashes:outputHashes];
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -539,8 +563,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
*filePaths = paths.copy;
|
||||
}
|
||||
|
||||
+ (void)printOutputHashes:(NSArray *)outputHashes jsonOutput:(BOOL)jsonOutput {
|
||||
if (jsonOutput) {
|
||||
+ (void)printOutputHashes:(NSArray *)outputHashes {
|
||||
if (json) {
|
||||
id object = (outputHashes.count > 1) ? outputHashes : outputHashes.firstObject;
|
||||
if (!object) return;
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
|
||||
|
||||
@@ -121,6 +121,10 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
|
||||
if (path) {
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (!fi.path) {
|
||||
[self printErrorUsageAndExit:@"Provided path was not a plain file"];
|
||||
}
|
||||
|
||||
if (newRule.type == SNTRuleTypeBinary) {
|
||||
newRule.shasum = fi.SHA256;
|
||||
} else if (newRule.type == SNTRuleTypeCertificate) {
|
||||
|
||||
@@ -215,9 +215,9 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
- (void)eventUploadBundleBinaries {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p syncBundleEvents]) {
|
||||
LOGD(@"Event Upload bundle binaries complete");
|
||||
LOGD(@"Event upload for bundle binaries complete");
|
||||
} else {
|
||||
LOGW(@"Event Upload bundle binary search failed");
|
||||
LOGW(@"Event upload for bundle binary search failed");
|
||||
}
|
||||
return [self postflight];
|
||||
}
|
||||
|
||||
@@ -57,8 +57,22 @@
|
||||
|
||||
- (BOOL)syncBundleEvents {
|
||||
NSMutableArray *newEvents = [NSMutableArray array];
|
||||
for (NSString *bundlePath in self.syncState.bundleBinaryRequests) {
|
||||
[newEvents addObjectsFromArray:[self findRelatedBinaries:bundlePath]];
|
||||
for (NSString *bundlePath in [NSSet setWithArray:self.syncState.bundleBinaryRequests]) {
|
||||
__block NSArray *relatedBinaries;
|
||||
__block BOOL shouldCancel = NO;
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
relatedBinaries = [self findRelatedBinaries:bundlePath shouldCancel:&shouldCancel];
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Give the search up to 5m to run
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 300))) {
|
||||
LOGD(@"Timed out while searching for related binaries at path %@", bundlePath);
|
||||
shouldCancel = YES;
|
||||
} else {
|
||||
[newEvents addObjectsFromArray:relatedBinaries];
|
||||
}
|
||||
}
|
||||
return [self uploadEvents:newEvents];
|
||||
}
|
||||
@@ -66,10 +80,10 @@
|
||||
- (BOOL)uploadEvents:(NSArray *)events {
|
||||
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
|
||||
|
||||
NSMutableDictionary *eventIds = [NSMutableDictionary dictionaryWithCapacity:events.count];
|
||||
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:events.count];
|
||||
for (SNTStoredEvent *event in events) {
|
||||
[uploadEvents addObject:[self dictionaryForEvent:event]];
|
||||
eventIds[event.idx] = @YES;
|
||||
if (event.idx) [eventIds addObject:event.idx];
|
||||
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
|
||||
}
|
||||
|
||||
@@ -82,7 +96,7 @@
|
||||
LOGI(@"Uploaded %lu events", uploadEvents.count);
|
||||
|
||||
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
|
||||
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allKeys]];
|
||||
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
|
||||
|
||||
// See if there are any events remaining to upload
|
||||
if (uploadEvents.count < events.count) {
|
||||
@@ -158,37 +172,47 @@
|
||||
#undef ADDKEY
|
||||
}
|
||||
|
||||
// Find binaries within a bundle given the bundle's path
|
||||
// Searches for 10 minutes, creating new events.
|
||||
- (NSArray *)findRelatedBinaries:(NSString *)path {
|
||||
SNTFileInfo *requestedPath = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
// Prevent processing the same bundle twice.
|
||||
static NSMutableDictionary *previouslyProcessedBundles;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
previouslyProcessedBundles = [NSMutableDictionary dictionary];
|
||||
});
|
||||
if (previouslyProcessedBundles[requestedPath.bundleIdentifier]) return nil;
|
||||
previouslyProcessedBundles[requestedPath.bundleIdentifier] = @YES;
|
||||
/**
|
||||
Find binaries within a bundle given the bundle's path. Will run until completion, however long
|
||||
that might be. Search is done within the bundle concurrently, using up to 25 threads at once.
|
||||
|
||||
@param path, the path to begin searching underneath
|
||||
@param shouldCancel, if YES, the search is cancelled part way through.
|
||||
@return array of SNTStoredEvent's
|
||||
*/
|
||||
- (NSArray *)findRelatedBinaries:(NSString *)path shouldCancel:(BOOL *)shouldCancel {
|
||||
// For storing the generated events, with a simple lock for writing.
|
||||
NSMutableArray *relatedEvents = [NSMutableArray array];
|
||||
NSLock *relatedEventsLock = [[NSLock alloc] init];
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block BOOL shouldCancel = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
NSString *file;
|
||||
// Limit the number of threads that can process files at once to keep CPU usage down.
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(25);
|
||||
|
||||
while (file = [dirEnum nextObject]) {
|
||||
@autoreleasepool {
|
||||
if (shouldCancel) break;
|
||||
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
|
||||
// Group the processing into a single group so we can wait on the whole group at the end.
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
file = [path stringByAppendingPathComponent:file];
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
while (1) {
|
||||
@autoreleasepool {
|
||||
if (*shouldCancel) break;
|
||||
NSString *file = [dirEnum nextObject];
|
||||
if (!file) break;
|
||||
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
|
||||
|
||||
// Wait for a processing thread to become available
|
||||
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
|
||||
|
||||
dispatch_group_async(group,
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
|
||||
^{
|
||||
@autoreleasepool {
|
||||
NSString *newFile = [path stringByAppendingPathComponent:file];
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:newFile];
|
||||
if (!fi.isExecutable) {
|
||||
dispatch_semaphore_signal(sema);
|
||||
return;
|
||||
}
|
||||
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:file];
|
||||
if (fi.isExecutable) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.filePath = fi.path;
|
||||
se.fileSHA256 = fi.SHA256;
|
||||
@@ -202,20 +226,18 @@
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
|
||||
se.signingChain = cs.certificates;
|
||||
|
||||
[relatedEvents addObject:[self dictionaryForEvent:se]];
|
||||
[relatedEventsLock lock];
|
||||
[relatedEvents addObject:se];
|
||||
[relatedEventsLock unlock];
|
||||
|
||||
dispatch_semaphore_signal(sema);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Give the search up to 10m per bundle to run.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 600))) {
|
||||
shouldCancel = YES;
|
||||
LOGD(@"Timed out while searching for related events at path %@", path);
|
||||
}
|
||||
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
|
||||
return relatedEvents;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
// If user requested it or we've never had a successful sync, try from a clean slate.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] ||
|
||||
[[SNTConfigurator configurator] syncCleanRequired]) {
|
||||
LOGD(@"Clean sync requested by user");
|
||||
requestDict[kRequestCleanSync] = @YES;
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@
|
||||
}
|
||||
|
||||
if ([resp[kCleanSync] boolValue]) {
|
||||
LOGD(@"Clean sync requested by server");
|
||||
self.syncState.cleanSync = YES;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
@"'type' INTEGER NOT NULL, "
|
||||
@"'custommsg' TEXT"
|
||||
@")"];
|
||||
[db executeUpdate:@"CREATE VIEW binrules AS SELECT * FROM rules WHERE type=1"];
|
||||
[db executeUpdate:@"CREATE VIEW certrules AS SELECT * FROM rules WHERE type=2"];
|
||||
[db executeUpdate:@"CREATE UNIQUE INDEX rulesunique ON rules (shasum, type)"];
|
||||
|
||||
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
|
||||
@@ -49,6 +47,12 @@
|
||||
newVersion = 1;
|
||||
}
|
||||
|
||||
if (version < 2) {
|
||||
[db executeUpdate:@"DROP VIEW IF EXISTS binrules"];
|
||||
[db executeUpdate:@"DROP VIEW IF EXISTS certrules"];
|
||||
newVersion = 2;
|
||||
}
|
||||
|
||||
// Save hashes of the signing certs for launchd and santad.
|
||||
// Used to ensure rules for them are not removed.
|
||||
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
|
||||
@@ -57,8 +61,8 @@
|
||||
// Ensure the certificates used to sign the running launchd/santad are whitelisted.
|
||||
// If they weren't previously and the database is not new, log an error.
|
||||
int ruleCount = [db intForQuery:@"SELECT COUNT(*)"
|
||||
@"FROM certrules "
|
||||
@"WHERE (shasum=? OR shasum=?) AND state=?",
|
||||
@"FROM rules "
|
||||
@"WHERE (shasum=? OR shasum=?) AND state=? AND type=2",
|
||||
self.santadCertSHA, self.launchdCertSHA, @(SNTRuleStateWhitelist)];
|
||||
if (ruleCount != 2) {
|
||||
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
|
||||
@@ -84,7 +88,7 @@
|
||||
- (NSUInteger)binaryRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM binrules"];
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=1"];
|
||||
}];
|
||||
return count;
|
||||
}
|
||||
@@ -92,7 +96,7 @@
|
||||
- (NSUInteger)certificateRuleCount {
|
||||
__block NSUInteger count = 0;
|
||||
[self inDatabase:^(FMDatabase *db) {
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM certrules"];
|
||||
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=2"];
|
||||
}];
|
||||
return count;
|
||||
}
|
||||
@@ -139,12 +143,12 @@
|
||||
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
|
||||
// Protect rules for santad/launchd certificates.
|
||||
NSPredicate *p = [NSPredicate predicateWithFormat:
|
||||
@"(SELF.shasum = %@ OR SELF.shasum = %@) AND SELF.type = %d",
|
||||
self.santadCertSHA, self.launchdCertSHA, SNTRuleTypeCertificate];
|
||||
@"(SELF.shasum = %@ OR SELF.shasum = %@) AND SELF.type = %d",
|
||||
self.santadCertSHA, self.launchdCertSHA, SNTRuleTypeCertificate];
|
||||
NSArray *requiredHashes = [rules filteredArrayUsingPredicate:p];
|
||||
p = [NSPredicate predicateWithFormat:@"SELF.state == %d", SNTRuleStateWhitelist];
|
||||
NSArray *requiredHashesWhitelist = [requiredHashes filteredArrayUsingPredicate:p];
|
||||
if ((cleanSlate && requiredHashesWhitelist.count != 2) ||
|
||||
if ((cleanSlate && requiredHashesWhitelist.count < 2) ||
|
||||
(requiredHashes.count != requiredHashesWhitelist.count)) {
|
||||
LOGE(@"Received request to remove whitelist for launchd/santad certificates.");
|
||||
[self fillError:error code:SNTRuleTableErrorMissingRequiredRule message:nil];
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#import "SNTDaemonControlController.h"
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
#import "SNTDriverManager.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
#import "SNTPolicyProcessor.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
@@ -35,6 +37,7 @@ double watchdogRAMPeak = 0;
|
||||
@interface SNTDaemonControlController ()
|
||||
@property NSString *_syncXsrfToken;
|
||||
@property dispatch_source_t syncTimer;
|
||||
@property SNTPolicyProcessor *policyProcessor;
|
||||
@end
|
||||
|
||||
@implementation SNTDaemonControlController
|
||||
@@ -44,6 +47,8 @@ double watchdogRAMPeak = 0;
|
||||
if (self) {
|
||||
_syncTimer = [self createSyncTimer];
|
||||
[self rescheduleSyncSecondsFromNow:30];
|
||||
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:
|
||||
[SNTDatabaseController ruleTable]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -141,6 +146,17 @@ double watchdogRAMPeak = 0;
|
||||
certificateSHA256:certificateSHA256]);
|
||||
}
|
||||
|
||||
#pragma mark Decision Ops
|
||||
|
||||
- (void)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate
|
||||
reply:(void (^)(SNTEventState))reply {
|
||||
reply([self.policyProcessor decisionForFilePath:filePath
|
||||
fileSHA256:fileSHA256
|
||||
signingCertificate:signingCertificate].decision);
|
||||
}
|
||||
|
||||
#pragma mark Config Ops
|
||||
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply {
|
||||
|
||||
@@ -28,42 +28,46 @@ static NSString *const kRulesDatabaseName = @"rules.db";
|
||||
static NSString *const kEventsDatabaseName = @"events.db";
|
||||
|
||||
+ (SNTEventTable *)eventTable {
|
||||
static FMDatabaseQueue *eventDatabaseQueue = nil;
|
||||
static SNTEventTable *eventDatabase;
|
||||
static dispatch_once_t eventDatabaseToken;
|
||||
dispatch_once(&eventDatabaseToken, ^{
|
||||
[self createDatabasePath];
|
||||
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kEventsDatabaseName];
|
||||
eventDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
FMDatabaseQueue *dbq = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
chown([fullPath UTF8String], 0, 0);
|
||||
chmod([fullPath UTF8String], 0600);
|
||||
|
||||
#ifndef DEBUG
|
||||
[eventDatabaseQueue inDatabase:^(FMDatabase *db) {
|
||||
[dbq inDatabase:^(FMDatabase *db) {
|
||||
db.logsErrors = NO;
|
||||
}];
|
||||
#endif
|
||||
|
||||
eventDatabase = [[SNTEventTable alloc] initWithDatabaseQueue:dbq];
|
||||
});
|
||||
|
||||
return [[SNTEventTable alloc] initWithDatabaseQueue:eventDatabaseQueue];
|
||||
return eventDatabase;
|
||||
}
|
||||
|
||||
+ (SNTRuleTable *)ruleTable {
|
||||
static FMDatabaseQueue *ruleDatabaseQueue = nil;
|
||||
static SNTRuleTable *ruleDatabase;
|
||||
static dispatch_once_t ruleDatabaseToken;
|
||||
dispatch_once(&ruleDatabaseToken, ^{
|
||||
[self createDatabasePath];
|
||||
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kRulesDatabaseName];
|
||||
ruleDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
FMDatabaseQueue *dbq = [[FMDatabaseQueue alloc] initWithPath:fullPath];
|
||||
chown([fullPath UTF8String], 0, 0);
|
||||
chmod([fullPath UTF8String], 0600);
|
||||
|
||||
#ifndef DEBUG
|
||||
[ruleDatabaseQueue inDatabase:^(FMDatabase *db) {
|
||||
[dbq inDatabase:^(FMDatabase *db) {
|
||||
db.logsErrors = NO;
|
||||
}];
|
||||
#endif
|
||||
|
||||
ruleDatabase = [[SNTRuleTable alloc] initWithDatabaseQueue:dbq];
|
||||
});
|
||||
return [[SNTRuleTable alloc] initWithDatabaseQueue:ruleDatabaseQueue];
|
||||
return ruleDatabase;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
@@ -28,15 +28,12 @@
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@interface SNTEventLog ()
|
||||
@property NSMutableDictionary *detailStore;
|
||||
@property NSMutableDictionary<NSNumber *, SNTCachedDecision *> *detailStore;
|
||||
@property dispatch_queue_t detailStoreQueue;
|
||||
|
||||
// Caches for uid->username and gid->groupname lookups.
|
||||
// Both dictionaries must be accessed from the nameMapQueue
|
||||
// to enforce thread-safety.
|
||||
@property NSMutableDictionary *userNameMap;
|
||||
@property NSMutableDictionary *groupNameMap;
|
||||
@property dispatch_queue_t nameMapQueue;
|
||||
@property NSCache<NSNumber *, NSString *> *userNameMap;
|
||||
@property NSCache<NSNumber *, NSString *> *groupNameMap;
|
||||
@end
|
||||
|
||||
@implementation SNTEventLog
|
||||
@@ -48,10 +45,10 @@
|
||||
_detailStoreQueue = dispatch_queue_create("com.google.santad.detail_store",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_userNameMap = [NSMutableDictionary dictionary];
|
||||
_groupNameMap = [NSMutableDictionary dictionary];
|
||||
_nameMapQueue = dispatch_queue_create("com.google.santad.name_map_queue",
|
||||
DISPATCH_QUEUE_SERIAL);
|
||||
_userNameMap = [[NSCache alloc] init];
|
||||
_userNameMap.countLimit = 100;
|
||||
_groupNameMap = [[NSCache alloc] init];
|
||||
_groupNameMap.countLimit = 100;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -401,7 +398,7 @@
|
||||
if (sanitized) {
|
||||
[str appendFormat:@"|args=%@", sanitized];
|
||||
} else {
|
||||
[str appendFormat:@"|args=%s", &bytes[stringStart]];
|
||||
[str appendFormat:@"|args=%@", @(&bytes[stringStart])];
|
||||
}
|
||||
|
||||
if (shouldFree) {
|
||||
@@ -410,39 +407,29 @@
|
||||
}
|
||||
|
||||
- (NSString *)nameForUID:(uid_t)uid {
|
||||
__block NSString *name;
|
||||
|
||||
NSNumber *uidNumber = @(uid);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
name = self.userNameMap[uidNumber];
|
||||
});
|
||||
|
||||
NSString *name = [self.userNameMap objectForKey:uidNumber];
|
||||
if (name) return name;
|
||||
|
||||
struct passwd *pw = getpwuid(uid);
|
||||
if (pw) {
|
||||
name = @(pw->pw_name);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
self.userNameMap[uidNumber] = name;
|
||||
});
|
||||
[self.userNameMap setObject:name forKey:uidNumber];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
- (NSString *)nameForGID:(gid_t)gid {
|
||||
__block NSString *name;
|
||||
|
||||
NSNumber *gidNumber = @(gid);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
name = self.groupNameMap[gidNumber];
|
||||
});
|
||||
|
||||
NSString *name = [self.groupNameMap objectForKey:gidNumber];
|
||||
if (name) return name;
|
||||
|
||||
struct group *gr = getgrgid(gid);
|
||||
if (gr) {
|
||||
name = @(gr->gr_name);
|
||||
dispatch_sync(self.nameMapQueue, ^{
|
||||
self.groupNameMap[gidNumber] = name;
|
||||
});
|
||||
[self.groupNameMap setObject:name forKey:gidNumber];
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -23,10 +23,8 @@
|
||||
@class SNTRuleTable;
|
||||
|
||||
///
|
||||
/// SNTExecutionController is responsible for everything that happens when a request to execute
|
||||
/// a binary occurs:
|
||||
/// + Making a decision about whether to allow or deny this binary based on any existing rules
|
||||
/// for that specific binary, its signing certificate and the operating mode of santad.
|
||||
/// SNTExecutionController is responsible for handling binary execution requests:
|
||||
/// + Uses SNTPolicyProcessor to make a decision about whether to allow or deny the binary.
|
||||
/// + Sending the decision to the kernel as soon as possible
|
||||
/// + (If denied or unknown) Storing details about the execution event to the database
|
||||
/// for upload and spwaning santactl to quickly try and send that to the server.
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#import "SNTEventTable.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTNotificationQueue.h"
|
||||
#import "SNTPolicyProcessor.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
@@ -41,9 +42,10 @@
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTEventTable *eventTable;
|
||||
@property SNTNotificationQueue *notifierQueue;
|
||||
@property SNTPolicyProcessor *policyProcessor;
|
||||
@property SNTRuleTable *ruleTable;
|
||||
|
||||
@property NSMutableDictionary *uploadBackoff;
|
||||
@property NSCache<NSString *, NSDate *> *uploadBackoff;
|
||||
@property dispatch_queue_t eventQueue;
|
||||
@end
|
||||
|
||||
@@ -63,8 +65,10 @@
|
||||
_eventTable = eventTable;
|
||||
_notifierQueue = notifierQueue;
|
||||
_eventLog = eventLog;
|
||||
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable];
|
||||
|
||||
_uploadBackoff = [NSMutableDictionary dictionaryWithCapacity:128];
|
||||
_uploadBackoff = [[NSCache alloc] init];
|
||||
_uploadBackoff.countLimit = 128;
|
||||
_eventQueue = dispatch_queue_create("com.google.santad.event_upload", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// This establishes the XPC connection between libsecurity and syspolicyd.
|
||||
@@ -76,85 +80,48 @@
|
||||
|
||||
#pragma mark Binary Validation
|
||||
|
||||
- (SNTEventState)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
SNTRule *rule = [_ruleTable ruleForBinarySHA256:cd.sha256 certificateSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.type) {
|
||||
case SNTRuleTypeBinary:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowBinary;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockBinary;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case SNTRuleTypeCertificate:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowCertificate;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return SNTEventStateBlockCertificate;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *msg = [self fileIsScopeBlacklisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return SNTEventStateBlockScope;
|
||||
}
|
||||
|
||||
msg = [self fileIsScopeWhitelisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return SNTEventStateAllowScope;
|
||||
}
|
||||
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor: return SNTEventStateAllowUnknown;
|
||||
case SNTClientModeLockdown: return SNTEventStateBlockUnknown;
|
||||
default: return SNTEventStateBlockUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)validateBinaryWithMessage:(santa_message_t)message {
|
||||
// Get info about the file. If we can't get this info, allow execution and log an error.
|
||||
if (unlikely(message.path == NULL)) {
|
||||
LOGE(@"Path for vnode_id is NULL: %llu", message.vnode_id);
|
||||
[_driverManager postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
NSError *fileInfoError;
|
||||
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
|
||||
if (!binInfo) {
|
||||
LOGW(@"Failed to read file %@: %@", binInfo.path, fileInfoError.localizedDescription);
|
||||
if (unlikely(!binInfo)) {
|
||||
LOGE(@"Failed to read file %@: %@", @(message.path), fileInfoError.localizedDescription);
|
||||
[_driverManager postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
|
||||
// PrinterProxy workaround, see description above the method for more details.
|
||||
if ([self printerProxyWorkaround:binInfo]) return;
|
||||
if ([self printerProxyWorkaround:binInfo]) {
|
||||
[_driverManager postToKernelAction:ACTION_RESPOND_DENY forVnodeID:message.vnode_id];
|
||||
return;
|
||||
}
|
||||
|
||||
// Get codesigning info about the file.
|
||||
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path];
|
||||
NSError *csError;
|
||||
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path
|
||||
error:&csError];
|
||||
// We specifically ignore CSInfoPlistFailed (-67030) as it sometimes appears spuriously
|
||||
// when trying to validate a binary separately from its bundle.
|
||||
if (csError && csError.code != errSecCSInfoPlistFailed) {
|
||||
csInfo = nil;
|
||||
}
|
||||
|
||||
// Actually make the decision.
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.sha256 = binInfo.SHA256;
|
||||
cd.certCommonName = csInfo.leafCertificate.commonName;
|
||||
cd.certSHA256 = csInfo.leafCertificate.SHA256;
|
||||
SNTCachedDecision *cd = [self.policyProcessor decisionForFileInfo:binInfo
|
||||
fileSHA256:nil
|
||||
signingCertificate:csInfo.leafCertificate];
|
||||
cd.vnodeId = message.vnode_id;
|
||||
cd.quarantineURL = binInfo.quarantineDataURL;
|
||||
cd.decision = [self makeDecision:cd binaryInfo:binInfo];
|
||||
|
||||
// Formulate an action from the decision
|
||||
santa_action_t action =
|
||||
(SNTEventStateAllow & cd.decision) ? ACTION_RESPOND_ALLOW : ACTION_RESPOND_DENY;
|
||||
|
||||
// Save decision details for logging the execution later.
|
||||
santa_action_t action = [self actionForEventState:cd.decision];
|
||||
if (action == ACTION_RESPOND_ALLOW) [_eventLog saveDecisionDetails:cd];
|
||||
|
||||
// Send the decision to the kernel.
|
||||
@@ -226,7 +193,7 @@
|
||||
se.filePath, se.fileSHA256, se.parentName, se.ppid];
|
||||
NSURL *detailURL = [SNTBlockMessage eventDetailURLForEvent:se];
|
||||
if (detailURL) {
|
||||
[msg appendFormat:@"%@\n\n", detailURL.absoluteString];
|
||||
[msg appendFormat:@"More info:\n%@\n\n", detailURL.absoluteString];
|
||||
}
|
||||
[self printMessage:msg toTTYForPID:message.ppid];
|
||||
|
||||
@@ -236,45 +203,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the file at @c path is in-scope for checking with Santa.
|
||||
///
|
||||
/// Files that are out of scope:
|
||||
/// + Non Mach-O files that are not part of an installer package.
|
||||
/// + Files in whitelisted path.
|
||||
///
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Whitelist Regex";
|
||||
}
|
||||
|
||||
// If file is not a Mach-O file, we're not interested unless it's part of an install package.
|
||||
// TODO(rah): Consider adding an option to check all scripts.
|
||||
// TODO(rah): Consider adding an option to disable package script checks.
|
||||
if (!fi.isMachO && ![fi.path hasPrefix:@"/private/tmp/PKInstallSandbox."]) {
|
||||
return @"Not a Mach-O";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Blacklist Regex";
|
||||
}
|
||||
|
||||
if ([[SNTConfigurator configurator] enablePageZeroProtection] && fi.isMissingPageZero) {
|
||||
return @"Missing __PAGEZERO";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Workaround for issue with PrinterProxy.app.
|
||||
|
||||
@@ -320,8 +248,8 @@
|
||||
This runs `santactl sync` for the event that was just saved, so that the user
|
||||
has something to vote in straight away.
|
||||
|
||||
This method is always called on a serial queue to ensure the backoff works properly
|
||||
and to keep this low-priority method away from the high-priority decision making threads.
|
||||
This method is always called on a serial queue to keep this low-priority method
|
||||
away from the high-priority decision making threads.
|
||||
*/
|
||||
- (void)initiateEventUploadForEvent:(SNTStoredEvent *)event {
|
||||
// The event upload is skipped if the full path is equal to that of santactl so that
|
||||
@@ -333,12 +261,12 @@
|
||||
|
||||
// The event upload is skipped if an event upload has been initiated for it in the
|
||||
// last 10 minutes.
|
||||
NSDate *backoff = self.uploadBackoff[event.fileSHA256];
|
||||
NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256];
|
||||
|
||||
NSDate *now = [NSDate date];
|
||||
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
|
||||
|
||||
self.uploadBackoff[event.fileSHA256] = now;
|
||||
[self.uploadBackoff setObject:now forKey:event.fileSHA256];
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
@@ -351,24 +279,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (santa_action_t)actionForEventState:(SNTEventState)state {
|
||||
switch (state) {
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateAllowUnknown:
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
case SNTEventStateBlockBinary:
|
||||
case SNTEventStateBlockCertificate:
|
||||
case SNTEventStateBlockScope:
|
||||
case SNTEventStateBlockUnknown:
|
||||
return ACTION_RESPOND_DENY;
|
||||
default:
|
||||
LOGW(@"Invalid event state %ld", state);
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid {
|
||||
if (pid < 2) return; // don't bother even looking for launchd.
|
||||
|
||||
@@ -387,7 +297,7 @@
|
||||
}
|
||||
|
||||
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
|
||||
NSMutableDictionary *loggedInUsers = [NSMutableDictionary dictionary];
|
||||
NSMutableSet *loggedInUsers = [NSMutableSet set];
|
||||
NSMutableArray *loggedInHosts = [NSMutableArray array];
|
||||
|
||||
struct utmpx *nxt;
|
||||
@@ -402,12 +312,12 @@
|
||||
sessionName = [NSString stringWithFormat:@"%@@%s", userName, nxt->ut_line];
|
||||
}
|
||||
|
||||
if (userName.length) loggedInUsers[userName] = [NSNull null];
|
||||
if (userName.length) [loggedInUsers addObject:userName];
|
||||
if (sessionName.length) [loggedInHosts addObject:sessionName];
|
||||
}
|
||||
endutxent();
|
||||
|
||||
*users = [loggedInUsers allKeys];
|
||||
*users = [loggedInUsers allObjects];
|
||||
*sessions = [loggedInHosts copy];
|
||||
}
|
||||
|
||||
|
||||
58
Source/santad/SNTPolicyProcessor.h
Normal file
58
Source/santad/SNTPolicyProcessor.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// http://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 "SNTCommonEnums.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
|
||||
#import <MOLCertificate.h>
|
||||
|
||||
@class SNTCachedDecision;
|
||||
@class SNTFileInfo;
|
||||
@class SNTRuleTable;
|
||||
|
||||
///
|
||||
/// Creates SNTCachedDecision objects from a SNTFileInfo object or a file path. Decisions are based
|
||||
/// on any existing rules for that specific binary, its signing certificate and the operating mode
|
||||
/// of santad.
|
||||
///
|
||||
@interface SNTPolicyProcessor : NSObject
|
||||
|
||||
///
|
||||
/// @param ruleTable The rule table to be used for every decision
|
||||
///
|
||||
- (nullable instancetype)initWithRuleTable:(nonnull SNTRuleTable *)ruleTable;
|
||||
|
||||
///
|
||||
/// @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 signingCertificate A MOLCertificate object, can be nil.
|
||||
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
|
||||
/// returned. Binary rules take precedence over cert rules.
|
||||
///
|
||||
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
|
||||
fileSHA256:(nullable NSString *)fileSHA256
|
||||
signingCertificate:(nullable MOLCertificate *)signingCertificate;
|
||||
|
||||
///
|
||||
/// A wrapper for decisionForFileInfo:fileSHA256:signingCertificate:. This method is slower as it
|
||||
/// has to create the SNTFileInfo object. This is mainly used by the santactl binary because
|
||||
/// 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:(nonnull NSString *)filePath
|
||||
fileSHA256:(nullable NSString *)fileSHA256
|
||||
signingCertificate:(nullable MOLCertificate *)signingCertificate;
|
||||
|
||||
@end
|
||||
164
Source/santad/SNTPolicyProcessor.m
Normal file
164
Source/santad/SNTPolicyProcessor.m
Normal file
@@ -0,0 +1,164 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// http://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 "SNTPolicyProcessor.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTRuleTable.h"
|
||||
|
||||
@interface SNTPolicyProcessor()
|
||||
@property SNTRuleTable *ruleTable;
|
||||
@end
|
||||
|
||||
@implementation SNTPolicyProcessor
|
||||
|
||||
- (instancetype)initWithRuleTable:(SNTRuleTable *)ruleTable {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_ruleTable = ruleTable;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (SNTCachedDecision *)decisionForFileInfo:(SNTFileInfo *)fileInfo
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate {
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
|
||||
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
|
||||
cd.quarantineURL = fileInfo.quarantineDataURL;
|
||||
if (signingCertificate) {
|
||||
cd.certCommonName = signingCertificate.commonName;
|
||||
cd.certSHA256 = signingCertificate.SHA256;
|
||||
}
|
||||
|
||||
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
|
||||
certificateSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.type) {
|
||||
case SNTRuleTypeBinary:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
cd.decision = SNTEventStateAllowBinary;
|
||||
return cd;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
cd.decision = SNTEventStateBlockBinary;
|
||||
return cd;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case SNTRuleTypeCertificate:
|
||||
switch (rule.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
cd.decision = SNTEventStateAllowCertificate;
|
||||
return cd;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
cd.decision = SNTEventStateBlockCertificate;
|
||||
return cd;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *msg = [self fileIsScopeBlacklisted:fileInfo];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
cd.decision = SNTEventStateBlockScope;
|
||||
return cd;
|
||||
}
|
||||
|
||||
msg = [self fileIsScopeWhitelisted:fileInfo];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
cd.decision = SNTEventStateAllowScope;
|
||||
return cd;
|
||||
}
|
||||
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case SNTClientModeMonitor:
|
||||
cd.decision = SNTEventStateAllowUnknown;
|
||||
return cd;
|
||||
case SNTClientModeLockdown:
|
||||
cd.decision = SNTEventStateBlockUnknown;
|
||||
return cd;
|
||||
default:
|
||||
cd.decision = SNTEventStateBlockUnknown;
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTCachedDecision *)decisionForFilePath:(NSString *)filePath
|
||||
fileSHA256:(NSString *)fileSHA256
|
||||
signingCertificate:(MOLCertificate *)signingCertificate {
|
||||
NSError *error;
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
|
||||
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
|
||||
return [self decisionForFileInfo:fileInfo
|
||||
fileSHA256:fileSHA256
|
||||
signingCertificate:signingCertificate];
|
||||
}
|
||||
|
||||
///
|
||||
/// Checks whether the file at @c path is in-scope for checking with Santa.
|
||||
///
|
||||
/// Files that are out of scope:
|
||||
/// + Non Mach-O files that are not part of an installer package.
|
||||
/// + Files in whitelisted path.
|
||||
///
|
||||
/// @return @c YES if file is in scope, @c NO otherwise.
|
||||
///
|
||||
- (NSString *)fileIsScopeWhitelisted:(SNTFileInfo *)fi {
|
||||
// Determine if file is within a whitelisted path
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] whitelistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Whitelist Regex";
|
||||
}
|
||||
|
||||
// If file is not a Mach-O file, we're not interested unless it's part of an install package.
|
||||
// TODO(rah): Consider adding an option to check all scripts.
|
||||
// TODO(rah): Consider adding an option to disable package script checks.
|
||||
if (!fi.isMachO && ![fi.path hasPrefix:@"/private/tmp/PKInstallSandbox."]) {
|
||||
return @"Not a Mach-O";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)fileIsScopeBlacklisted:(SNTFileInfo *)fi {
|
||||
NSRegularExpression *re = [[SNTConfigurator configurator] blacklistPathRegex];
|
||||
if ([re numberOfMatchesInString:fi.path options:0 range:NSMakeRange(0, fi.path.length)]) {
|
||||
return @"Blacklist Regex";
|
||||
}
|
||||
|
||||
if ([[SNTConfigurator configurator] enablePageZeroProtection] && fi.isMissingPageZero) {
|
||||
return @"Missing __PAGEZERO";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -136,43 +136,7 @@
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIFOzCCBCOgAwIBAgIIKtpxuqe9F58wDQYJKoZIhvcNAQEFBQAw
|
||||
fzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAk
|
||||
BgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMw
|
||||
MQYDVQQDDCpBcHBsZSBDb2RlIFNpZ25pbmcgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkwHhcNMTMwNDEyMjIzNDM1WhcNMjEwNDEyMjIz
|
||||
NDM1WjBWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5j
|
||||
LjEXMBUGA1UECwwOQXBwbGUgU29mdHdhcmUxGTAXBgNVBAMMEFNv
|
||||
ZnR3YXJlIFNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/MLh0mE+uBguklG4xVG0J0TyjsDkQqdDmqmAiXdPk
|
||||
hKJAQZBkxmA9kWHaUqhFJ54sZMvkHqgkClI6s9PsFkF4wZ7RBuZ4
|
||||
JWMI89/KQeYd/jXpUVwTFYvp0Z1xe9HJqkuemdqPwCm4L5BvpLtl
|
||||
j4Bq1z1obeR4wqUSL/gy6X7JXVyMPhYgG9denRuGLQj3vBmkTQ5B
|
||||
pErbaxqARVAEqUyNFQfqaie9u4iePD+yUjmX47fI61RSmIovI1Zl
|
||||
5ekq2VG0I/oE3ffroN/VmvJeCPFfh/CxR2x1sbGM0RPjesHsYkF0
|
||||
poM08fladGQ5P1luzyzAYIMpPOfeT18N85M5XzCNAgMBAAGjggHi
|
||||
MIIB3jAdBgNVHQ4EFgQUxu0+Svsu6D8T1aAVs13Z57P3aDUwDAYD
|
||||
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBSOaabEd0JOBKVWQpxRH4ba
|
||||
0iCPCTCCARwGA1UdIASCARMwggEPMIIBCwYJKoZIhvdjZAUBMIH9
|
||||
MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0
|
||||
aWZpY2F0ZWF1dGhvcml0eTCBwwYIKwYBBQUHAgIwgbYMgbNSZWxp
|
||||
YW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
|
||||
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
|
||||
ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2Us
|
||||
IGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBw
|
||||
cmFjdGljZSBzdGF0ZW1lbnRzLjA1BgNVHR8ELjAsMCqgKKAmhiRo
|
||||
dHRwOi8vY3JsLmFwcGxlLmNvbS9jb2Rlc2lnbmluZy5jcmwwDgYD
|
||||
VR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA8G
|
||||
CSqGSIb3Y2QGFgQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFfUxSFX
|
||||
GxTaEjIsEQUMBA+VqtTi+vLEbWjeUlINInPIhXMd71FO8IpJsGiU
|
||||
ZVEi3/1AjzW0aEBSuyWOzPrOfBJW2MDQVQW1SrG1YfyVfJFectEo
|
||||
tB0rbdpLZ58F/ObnWUpDXh97hDe+/rqKKzMFlFCDuP6a2wO7jWLy
|
||||
GW13k+N1zzZZMV4IbV0BHGVTUpN2eJwXCxAelLw2kFZLRC6Y2aYx
|
||||
ofAcZpSZVHMTtVE4vCSioDA7emWHrMC8FfReMMedoyoE38TPR4Rt
|
||||
n/3/RcOgGaw8u62PlPm5x8hxNhHt6AG6tHVIgqQqUxoFBZudxkcb
|
||||
9eggcqAbS+W+ZPw4DZr/Q0E=
|
||||
</data>
|
||||
<data>MIIFOzCCBCOgAwIBAgIIKtpxuqe9F58wDQYJKoZIhvcNAQEFBQAwfzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMwMQYDVQQDDCpBcHBsZSBDb2RlIFNpZ25pbmcgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTMwNDEyMjIzNDM1WhcNMjEwNDEyMjIzNDM1WjBWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEXMBUGA1UECwwOQXBwbGUgU29mdHdhcmUxGTAXBgNVBAMMEFNvZnR3YXJlIFNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/MLh0mE+uBguklG4xVG0J0TyjsDkQqdDmqmAiXdPkhKJAQZBkxmA9kWHaUqhFJ54sZMvkHqgkClI6s9PsFkF4wZ7RBuZ4JWMI89/KQeYd/jXpUVwTFYvp0Z1xe9HJqkuemdqPwCm4L5BvpLtlj4Bq1z1obeR4wqUSL/gy6X7JXVyMPhYgG9denRuGLQj3vBmkTQ5BpErbaxqARVAEqUyNFQfqaie9u4iePD+yUjmX47fI61RSmIovI1Zl5ekq2VG0I/oE3ffroN/VmvJeCPFfh/CxR2x1sbGM0RPjesHsYkF0poM08fladGQ5P1luzyzAYIMpPOfeT18N85M5XzCNAgMBAAGjggHiMIIB3jAdBgNVHQ4EFgQUxu0+Svsu6D8T1aAVs13Z57P3aDUwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSOaabEd0JOBKVWQpxRH4ba0iCPCTCCARwGA1UdIASCARMwggEPMIIBCwYJKoZIhvdjZAUBMIH9MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0aWZpY2F0ZWF1dGhvcml0eTCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vY3JsLmFwcGxlLmNvbS9jb2Rlc2lnbmluZy5jcmwwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA8GCSqGSIb3Y2QGFgQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFfUxSFXGxTaEjIsEQUMBA+VqtTi+vLEbWjeUlINInPIhXMd71FO8IpJsGiUZVEi3/1AjzW0aEBSuyWOzPrOfBJW2MDQVQW1SrG1YfyVfJFectEotB0rbdpLZ58F/ObnWUpDXh97hDe+/rqKKzMFlFCDuP6a2wO7jWLyGW13k+N1zzZZMV4IbV0BHGVTUpN2eJwXCxAelLw2kFZLRC6Y2aYxofAcZpSZVHMTtVE4vCSioDA7emWHrMC8FfReMMedoyoE38TPR4Rtn/3/RcOgGaw8u62PlPm5x8hxNhHt6AG6tHVIgqQqUxoFBZudxkcb9eggcqAbS+W+ZPw4DZr/Q0E=</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
@@ -212,35 +176,7 @@
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEDjCCAvagAwIBAgIBITANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMTExMDI0MTczOTQxWhcNMjYxMDI0
|
||||
MTczOTQxWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxMzAxBgNVBAMMKkFwcGxlIENvZGUgU2lnbmluZyBDZXJ0
|
||||
aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAKKoEXH/DvkLa/glDZiBXWtZvVobibPn5e7O
|
||||
OZgjNTlInyGrJ9nunCDwZDgIawynz9xQth0GxFvxXRqbVGWGcy9i
|
||||
5Ti9ARBkcm18aUdhnBAFJuPrhcIsJNxqwj+I/MysKUyhSXkRmnV2
|
||||
5R640NIJtExTePvfGHahj6SpMsqRp7b6l705qs0bUBGIq2rt62bK
|
||||
IEusOy3vqufWyYgtacKkKmEv24cC86EhuUyfDvj52S3KcgR/Ha5u
|
||||
+j+Is8yjQO4XhxhRlrzP5C2twulZTl0cZTMnA6pno5Mkh8eHeQK5
|
||||
XZizDu7NaQg+jEiSJLJt1zC+z9jkyKeXgdAeI9w4mV9h/oUCAwEA
|
||||
AaOBsTCBrjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB
|
||||
BQUHAwMwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjmmmxHdC
|
||||
TgSlVkKcUR+G2tIgjwkwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40u
|
||||
QKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5h
|
||||
cHBsZS5jb20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUF
|
||||
AAOCAQEAcHOt9lIVarcVGN6pKtGddpsesmmWx8LD4SvQ7wddcPja
|
||||
PFpIR9s5bIDKc95iG7c6yqNaHuOH2iVKk5vvcxCTc13j9J1+3g+B
|
||||
9qmZwVhunPSJAL7PT/8C0w789fP0choysconDt6o05mPauaZ+2HJ
|
||||
T/IXsRhn8DDAxgruyESBpIm78XlBw+6uyGtnfMxsSYZMAtPTam4Y
|
||||
nPhcOMgwh5ow2mcouOKaedqfpTsfUWI7IvF+U3waC8PwTdxJRPKI
|
||||
iM46W7md6bK3W1KnxtVYiXK32MyzqBgdUJc/Hdpqrji/e3kxvmO5
|
||||
94WFF+ltisTiGJQv129SpZmx3USbB3CSiCZ32w==
|
||||
</data>
|
||||
<data>MIIEDjCCAvagAwIBAgIBITANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMTExMDI0MTczOTQxWhcNMjYxMDI0MTczOTQxWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMzAxBgNVBAMMKkFwcGxlIENvZGUgU2lnbmluZyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKoEXH/DvkLa/glDZiBXWtZvVobibPn5e7OOZgjNTlInyGrJ9nunCDwZDgIawynz9xQth0GxFvxXRqbVGWGcy9i5Ti9ARBkcm18aUdhnBAFJuPrhcIsJNxqwj+I/MysKUyhSXkRmnV25R640NIJtExTePvfGHahj6SpMsqRp7b6l705qs0bUBGIq2rt62bKIEusOy3vqufWyYgtacKkKmEv24cC86EhuUyfDvj52S3KcgR/Ha5u+j+Is8yjQO4XhxhRlrzP5C2twulZTl0cZTMnA6pno5Mkh8eHeQK5XZizDu7NaQg+jEiSJLJt1zC+z9jkyKeXgdAeI9w4mV9h/oUCAwEAAaOBsTCBrjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjmmmxHdCTgSlVkKcUR+G2tIgjwkwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5hcHBsZS5jb20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAcHOt9lIVarcVGN6pKtGddpsesmmWx8LD4SvQ7wddcPjaPFpIR9s5bIDKc95iG7c6yqNaHuOH2iVKk5vvcxCTc13j9J1+3g+B9qmZwVhunPSJAL7PT/8C0w789fP0choysconDt6o05mPauaZ+2HJT/IXsRhn8DDAxgruyESBpIm78XlBw+6uyGtnfMxsSYZMAtPTam4YnPhcOMgwh5ow2mcouOKaedqfpTsfUWI7IvF+U3waC8PwTdxJRPKIiM46W7md6bK3W1KnxtVYiXK32MyzqBgdUJc/Hdpqrji/e3kxvmO594WFF+ltisTiGJQv129SpZmx3USbB3CSiCZ32w==</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
@@ -261,40 +197,7 @@
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5
|
||||
MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmE
|
||||
Les2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRr
|
||||
EdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b
|
||||
8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6ws
|
||||
IG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
|
||||
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CW
|
||||
QYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMU
|
||||
ZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
|
||||
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4w
|
||||
ggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggr
|
||||
BgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2Ev
|
||||
MIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2Vy
|
||||
dGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
|
||||
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z
|
||||
IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9s
|
||||
aWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVu
|
||||
dHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J2
|
||||
0ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNii
|
||||
Pvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3
|
||||
iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
|
||||
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP
|
||||
3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5
|
||||
MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJ
|
||||
BzewdXUh
|
||||
</data>
|
||||
<data>MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
@@ -314,7 +217,7 @@
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485894498.53763503</real>
|
||||
<real>485894498.537635</real>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
@@ -325,7 +228,7 @@
|
||||
<key>$classname</key>
|
||||
<string>NSDate</string>
|
||||
</dict>
|
||||
<integer>6</integer>
|
||||
<integer>131072</integer>
|
||||
<integer>11196</integer>
|
||||
<integer>10760</integer>
|
||||
<string>bash</string>
|
||||
@@ -454,7 +357,7 @@
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485894568.92822498</real>
|
||||
<real>485894568.928225</real>
|
||||
</dict>
|
||||
<integer>1</integer>
|
||||
<integer>11427</integer>
|
||||
|
||||
260
Tests/LogicTests/SNTCommandFileInfoTest.m
Normal file
260
Tests/LogicTests/SNTCommandFileInfoTest.m
Normal file
@@ -0,0 +1,260 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// http://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 <XCTest/XCTest.h>
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject
|
||||
|
||||
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
|
||||
@property(nonatomic) NSMutableDictionary *propertyMap;
|
||||
+ (NSArray *)fileInfoKeys;
|
||||
+ (NSArray *)signingChainKeys;
|
||||
- (SNTAttributeBlock)codeSigned;
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath
|
||||
daemonConnection:(SNTXPCConnection *)daemonConn;
|
||||
+ (void)parseArguments:(NSArray *)args
|
||||
forKey:(NSString **)key
|
||||
certIndex:(NSNumber **)certIndex
|
||||
jsonOutput:(BOOL *)jsonOutput
|
||||
filePaths:(NSArray **)filePaths;
|
||||
|
||||
@end
|
||||
|
||||
@interface SNTCommandFileInfoTest : XCTestCase
|
||||
|
||||
@property SNTCommandFileInfo *cfi;
|
||||
@property id cscMock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTCommandFileInfoTest
|
||||
|
||||
- (void)setUp {
|
||||
[super setUp];
|
||||
|
||||
self.cfi = [[SNTCommandFileInfo alloc] initWithFilePath:nil daemonConnection:nil];
|
||||
self.cscMock = OCMClassMock([MOLCodesignChecker class]);
|
||||
OCMStub([self.cscMock alloc]).andReturn(self.cscMock);
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
self.cfi = nil;
|
||||
[self.cscMock stopMocking];
|
||||
self.cscMock = nil;
|
||||
|
||||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsKey {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
[SNTCommandFileInfo parseArguments:@[ @"--key", @"SHA-256", @"/usr/bin/yes" ]
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertEqualObjects(key, @"SHA-256");
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsCertIndex {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
[SNTCommandFileInfo parseArguments:@[ @"--cert-index", @"1", @"/usr/bin/yes" ]
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertEqualObjects(certIndex, @(1));
|
||||
}
|
||||
- (void)testParseArgumentsJSON {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
[SNTCommandFileInfo parseArguments:@[ @"--json", @"/usr/bin/yes" ]
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertTrue(jsonOutput);
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsFilePaths {
|
||||
NSString *key;
|
||||
NSNumber *certIndex;
|
||||
BOOL jsonOutput = NO;
|
||||
NSArray *filePaths;
|
||||
NSArray *args = @[ @"/usr/bin/yes", @"/bin/mv", @"--key", @"SHA-256", @"/bin/ls", @"--json",
|
||||
@"/bin/rm", @"--cert-index", @"1", @"/bin/cp"];
|
||||
[SNTCommandFileInfo parseArguments:args
|
||||
forKey:&key
|
||||
certIndex:&certIndex
|
||||
jsonOutput:&jsonOutput
|
||||
filePaths:&filePaths];
|
||||
XCTAssertEqual(filePaths.count, 5);
|
||||
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/mv"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/ls"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/rm"]);
|
||||
XCTAssertTrue([filePaths containsObject:@"/bin/cp"]);
|
||||
}
|
||||
|
||||
- (void)testKeysAlignWithPropertyMap {
|
||||
NSArray *mapKeys = self.cfi.propertyMap.allKeys;
|
||||
NSArray *keys = [SNTCommandFileInfo fileInfoKeys];
|
||||
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
XCTAssertTrue([mapKeys containsObject:obj]);
|
||||
}];
|
||||
[mapKeys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
XCTAssertTrue([keys containsObject:obj]);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testCodeSignedNo {
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSUnsigned userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), @"No");
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureFailed {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedStaticCodeChanged {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSStaticCodeChanged userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureNotVerifiable {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureNotVerifiable userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedSignatureUnsupported {
|
||||
NSString *expected = @"Yes, but code/signature changed/unverifiable";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSSignatureUnsupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceDirectoryFailed {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceDirectoryFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceNotSupported {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceNotSupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourceRulesInvalid {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourceRulesInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesInvalid {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesNotFound {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotFound userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedResourcesNotSealed {
|
||||
NSString *expected = @"Yes, but resources invalid";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSResourcesNotSealed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqFailed {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqInvalid {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqInvalid userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedReqUnsupported {
|
||||
NSString *expected = @"Yes, but failed requirement validation";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSReqUnsupported userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedInfoPlistFailed {
|
||||
NSString *expected = @"Yes, but can't validate as Info.plist is missing";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:errSecCSInfoPlistFailed userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
- (void)testCodeSignedDefault {
|
||||
NSString *expected = @"Yes, but failed to validate (999)";
|
||||
NSError *err = [NSError errorWithDomain:@"" code:999 userInfo:nil];
|
||||
OCMStub([self.cscMock initWithBinaryPath:OCMOCK_ANY
|
||||
error:[OCMArg setTo:err]]).andReturn(self.cscMock);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi), expected);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -39,10 +39,6 @@
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTCommandSyncEventUpload (Testing)
|
||||
- (NSArray *)findRelatedBinaries:(SNTStoredEvent *)event;
|
||||
@end
|
||||
|
||||
@interface SNTCommandSyncTest : XCTestCase
|
||||
@property SNTCommandSyncState *syncState;
|
||||
@property id<SNTDaemonControlXPC> daemonConnRop;
|
||||
@@ -253,41 +249,41 @@
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[@"events"];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertEqual(events.count, 2);
|
||||
|
||||
NSDictionary *event = events[0];
|
||||
XCTAssertEqualObjects(event[@"file_sha256"],
|
||||
XCTAssertEqualObjects(event[kFileSHA256],
|
||||
@"ff98fa0c0a1095fedcbe4d388a9760e71399a5c3c017a847ffa545663b57929a");
|
||||
XCTAssertEqualObjects(event[@"file_name"], @"yes");
|
||||
XCTAssertEqualObjects(event[@"file_path"], @"/usr/bin");
|
||||
XCTAssertEqualObjects(event[@"decision"], @"BLOCK_BINARY");
|
||||
XCTAssertEqualObjects(event[kFileName], @"yes");
|
||||
XCTAssertEqualObjects(event[kFilePath], @"/usr/bin");
|
||||
XCTAssertEqualObjects(event[kDecision], @"BLOCK_BINARY");
|
||||
NSArray *sessions = @[ @"foouser@console", @"foouser@ttys000"];
|
||||
XCTAssertEqualObjects(event[@"current_sessions"], sessions);
|
||||
XCTAssertEqualObjects(event[kCurrentSessions], sessions);
|
||||
NSArray *users = @[ @"foouser" ];
|
||||
XCTAssertEqualObjects(event[@"logged_in_users"], users);
|
||||
XCTAssertEqualObjects(event[@"executing_user"], @"root");
|
||||
XCTAssertEqualObjects(event[@"pid"], @(11196));
|
||||
XCTAssertEqualObjects(event[@"ppid"], @(10760));
|
||||
XCTAssertEqualObjects(event[@"execution_time"], @(1464201698.537635));
|
||||
XCTAssertEqualObjects(event[kLoggedInUsers], users);
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"root");
|
||||
XCTAssertEqualObjects(event[kPID], @(11196));
|
||||
XCTAssertEqualObjects(event[kPPID], @(10760));
|
||||
XCTAssertEqualObjects(event[kExecutionTime], @(1464201698.537635));
|
||||
|
||||
NSArray *certs = event[@"signing_chain"];
|
||||
NSArray *certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 3);
|
||||
|
||||
NSDictionary *cert = [certs firstObject];
|
||||
XCTAssertEqualObjects(cert[@"sha256"],
|
||||
XCTAssertEqualObjects(cert[kCertSHA256],
|
||||
@"2aa4b9973b7ba07add447ee4da8b5337c3ee2c3a991911e80e7282e8a751fc32");
|
||||
XCTAssertEqualObjects(cert[@"cn"], @"Software Signing");
|
||||
XCTAssertEqualObjects(cert[@"org"], @"Apple Inc.");
|
||||
XCTAssertEqualObjects(cert[@"ou"], @"Apple Software");
|
||||
XCTAssertEqualObjects(cert[@"valid_from"], @(1365806075));
|
||||
XCTAssertEqualObjects(cert[@"valid_until"], @(1618266875));
|
||||
XCTAssertEqualObjects(cert[kCertCN], @"Software Signing");
|
||||
XCTAssertEqualObjects(cert[kCertOrg], @"Apple Inc.");
|
||||
XCTAssertEqualObjects(cert[kCertOU], @"Apple Software");
|
||||
XCTAssertEqualObjects(cert[kCertValidFrom], @(1365806075));
|
||||
XCTAssertEqualObjects(cert[kCertValidUntil], @(1618266875));
|
||||
|
||||
event = events[1];
|
||||
XCTAssertEqualObjects(event[@"file_name"], @"hub");
|
||||
XCTAssertEqualObjects(event[@"executing_user"], @"foouser");
|
||||
certs = event[@"signing_chain"];
|
||||
XCTAssertEqualObjects(event[kFileName], @"hub");
|
||||
XCTAssertEqualObjects(event[kExecutingUser], @"foouser");
|
||||
certs = event[kSigningChain];
|
||||
XCTAssertEqual(certs.count, 0);
|
||||
|
||||
return YES;
|
||||
@@ -299,7 +295,6 @@
|
||||
- (void)testEventUploadBundleAndQuarantineData {
|
||||
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
sut = OCMPartialMock(sut);
|
||||
OCMStub([sut findRelatedBinaries:OCMOCK_ANY]);
|
||||
|
||||
NSData *eventData = [self dataFromFixture:@"sync_eventupload_input_quarantine.plist"];
|
||||
NSArray *events = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
|
||||
@@ -307,24 +302,24 @@
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[@"events"];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertEqual(events.count, 1);
|
||||
|
||||
NSDictionary *event = [events firstObject];
|
||||
XCTAssertEqualObjects(event[@"file_bundle_id"], @"com.luckymarmot.Paw");
|
||||
XCTAssertEqualObjects(event[@"file_bundle_path"], @"/Applications/Paw.app");
|
||||
XCTAssertEqualObjects(event[@"file_bundle_version"], @"2003004001");
|
||||
XCTAssertEqualObjects(event[@"file_bundle_version_string"], @"2.3.4");
|
||||
XCTAssertEqualObjects(event[@"quarantine_timestamp"], @(1464204868));
|
||||
XCTAssertEqualObjects(event[@"quarantine_agent_bundle_id"], @"com.google.Chrome");
|
||||
XCTAssertEqualObjects(event[@"quarantine_data_url"],
|
||||
XCTAssertEqualObjects(event[kFileBundleID], @"com.luckymarmot.Paw");
|
||||
XCTAssertEqualObjects(event[kFileBundlePath], @"/Applications/Paw.app");
|
||||
XCTAssertEqualObjects(event[kFileBundleVersion], @"2003004001");
|
||||
XCTAssertEqualObjects(event[kFileBundleShortVersionString], @"2.3.4");
|
||||
XCTAssertEqualObjects(event[kQuarantineTimestamp], @(1464204868));
|
||||
XCTAssertEqualObjects(event[kQuarantineAgentBundleID], @"com.google.Chrome");
|
||||
XCTAssertEqualObjects(event[kQuarantineDataURL],
|
||||
@"https://d3hevc2w7wq7nj.cloudfront.net/paw/Paw-2.3.4-2003004001.zip");
|
||||
XCTAssertEqualObjects(event[@"quarantine_referer_url"], @"https://luckymarmot.com/paw");
|
||||
XCTAssertEqualObjects(event[kQuarantineRefererURL], @"https://luckymarmot.com/paw");
|
||||
|
||||
return YES;
|
||||
}];
|
||||
|
||||
|
||||
[sut sync];
|
||||
}
|
||||
|
||||
@@ -332,7 +327,6 @@
|
||||
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
self.syncState.eventBatchSize = 1;
|
||||
sut = OCMPartialMock(sut);
|
||||
OCMStub([sut findRelatedBinaries:OCMOCK_ANY]);
|
||||
|
||||
NSData *eventData = [self dataFromFixture:@"sync_eventupload_input_basic.plist"];
|
||||
NSArray *events = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
|
||||
@@ -350,6 +344,28 @@
|
||||
XCTAssertEqual(requestCount, 2);
|
||||
}
|
||||
|
||||
- (void)testEventUploadBundleSearch {
|
||||
SNTCommandSyncEventUpload *sut = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
self.syncState.eventBatchSize = 128;
|
||||
self.syncState.bundleBinaryRequests = @[ @"/Applications/Safari.app"];
|
||||
sut = OCMPartialMock(sut);
|
||||
|
||||
[self stubRequestBody:nil response:nil error:nil validateBlock:^BOOL(NSURLRequest *req) {
|
||||
NSDictionary *requestDict = [self dictFromRequest:req];
|
||||
NSArray *events = requestDict[kEvents];
|
||||
|
||||
XCTAssertGreaterThanOrEqual(events.count, 3);
|
||||
|
||||
for (NSDictionary *event in events) {
|
||||
XCTAssertEqualObjects(event[kDecision], kDecisionBundleBinary);
|
||||
}
|
||||
|
||||
return YES;
|
||||
}];
|
||||
|
||||
[sut syncBundleEvents];
|
||||
}
|
||||
|
||||
#pragma mark - SNTCommandSyncRuleDownload Tests
|
||||
|
||||
- (void)testRuleDownload {
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
self.mockCodesignChecker = OCMClassMock([MOLCodesignChecker class]);
|
||||
OCMStub([self.mockCodesignChecker alloc]).andReturn(self.mockCodesignChecker);
|
||||
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY])
|
||||
OCMStub([self.mockCodesignChecker initWithBinaryPath:OCMOCK_ANY error:[OCMArg setTo:NULL]])
|
||||
.andReturn(self.mockCodesignChecker);
|
||||
|
||||
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
XCTAssertNotNil(sut);
|
||||
|
||||
OCMVerifyAll(mockConnection);
|
||||
[mockConnection stopMocking];
|
||||
}
|
||||
|
||||
- (void)testInitServer {
|
||||
@@ -52,6 +53,7 @@
|
||||
SNTXPCConnection *sut = [[SNTXPCConnection alloc] initServerWithName:@"TestServer"];
|
||||
XCTAssertNotNil(sut);
|
||||
OCMVerifyAll(mockListener);
|
||||
[mockListener stopMocking];
|
||||
}
|
||||
|
||||
- (void)testConnectionRejection {
|
||||
@@ -75,6 +77,8 @@
|
||||
[sutClient resume];
|
||||
|
||||
[self waitForExpectationsWithTimeout:3.0 handler:NULL];
|
||||
|
||||
[mockCodesignChecker stopMocking];
|
||||
}
|
||||
|
||||
- (void)testConnectionAcceptance {
|
||||
|
||||
Reference in New Issue
Block a user