Compare commits

...

37 Commits

Author SHA1 Message Date
Russell Hancox
91aefe25c4 santad: Fix printer-proxy workaround (#120) 2016-10-13 15:30:08 -04:00
Russell Hancox
a8c11097d9 Project: Use NSSet instead of NSDictionary for uniqueness in collections (#119) 2016-10-13 15:20:19 -04:00
Russell Hancox
92ba4a3ae9 santactl/sync: Debug log when clean sync requested (#118) 2016-10-13 15:20:12 -04:00
Russell Hancox
7c5d382010 santactl/sync: Fix bundle searching, make concurrent. (#115)
* santactl/sync: Fix bundle searching, make concurrent.
2016-10-13 15:14:35 -04:00
Russell Hancox
f8fbaefd86 Tests: Fix XPC connection tests (#116)
Also disable LTO in debug builds.
2016-10-13 12:43:26 -04:00
Russell Hancox
181b37296a santactl/sync Tests: Use constants (#117) 2016-10-13 12:43:14 -04:00
Tom Burgin
2ab61cfa12 SNTCommandFileInfo: Fixed retain cycle. Added locking for a NSMutableArray when accessed on multiple threads (#114) 2016-10-13 11:38:33 -04:00
Tom Burgin
1b0e9b14ef Global json bool shared between class and instance methods. https://github.com/google/santa/issues/112 (#113) 2016-10-12 14:35:27 -04:00
Russell Hancox
2aacc9266f Revert changes for building with Xcode 8 (#111)
* Partial Revert of "Project: Update project files for Xcode 8 (#105)"

Building with Xcode 8 (and specifically the 10.12 SDK) breaks logging on
10.12 and on top of that some tests don't pass while working perfectly
fine on 10.11. For now, we'll just continue building with 7.3.1.

* README: Add note about building with Xcode 7.3.1
2016-10-10 14:24:14 -04:00
Russell Hancox
d648d477bb santa-driver: Fix deadlocking on Sierra (#107)
1. Don't RemoveFromCache for advisory access by santad itself.
2. wakeup sleeping threads when removing from cache
3. Move the vnode type check earlier in the process for the vnode scope
2016-09-28 16:36:23 -04:00
Russell Hancox
6f91c1a1d3 Project: Update project files for Xcode 8 (#105) 2016-09-28 16:11:22 -04:00
Russell Hancox
aa1aca24b7 Common: Don't crash if ClientMode key is not an integer. (#106)
NSString has longLongValue but not longValue, so switch to that then cast down. Check that the receiver responds to longLongValue before calling it just in case someone tries to set it to an NSData or something.
2016-09-26 11:53:51 -04:00
Tom Burgin
6a0867172f Mocking for MOLCodesignChecker initWithBinaryPath:error: (#104) 2016-09-23 15:40:37 -04:00
Russell Hancox
f025a4b2fb santad: In required rule protection, handle case where there are multiple rules for the required certs (#101) 2016-09-22 16:17:59 -04:00
Russell Hancox
8871f36a92 santa-driver: FetchDecision - use a loop rather than recursing. (#100) 2016-09-22 15:58:53 -04:00
Russell Hancox
f17490edad santad: Handle UTF-8 in process args. (#99)
While appendFormat with %s is slightly faster (~1üs) it doesn't handle UTF-8 properly.
2016-09-22 15:38:00 -04:00
Russell Hancox
b360e782c6 santad: Start ignoring errSecCSInfoPlistFailed (-67030) (#98) 2016-09-22 15:36:35 -04:00
Russell Hancox
8d94324dd6 santad: Update SNTFileWatcher to fix broken dispatch source. (#97)
I'm not certain if this is a Sierra change or just that it was more rare before but changing a cancel handler on a dispatch source no longer seems to have any effect. This meant the file descriptor for the currently-active source was being closed instead of the one for the source that was just cancelled. It wasn't actually necessary to get the file handle from the source, we can just rely on capturing it in the block, which works just as well.
2016-09-22 15:36:26 -04:00
Russell Hancox
2818609412 santactl/sync: Fix bundle event upload (#96) 2016-09-20 12:37:12 -04:00
Russell Hancox
270a2e69d4 Project: Add bundler caching to travis build (#95) 2016-09-19 07:19:15 -04:00
Russell Hancox
d1d9762e29 santa-driver: Don't filter advisory vnode_write notifications (#94) 2016-09-15 10:17:18 -04:00
Russell Hancox
1666e8b127 Move some NSMutableDictionary uses to NSCache, log client connection (#93)
* santa-driver: Log when client connects (we already log disconnect)
* santad: Move a couple of NSMutableDictionary uses over to NSCache, add type info.
2016-09-14 17:09:04 -04:00
Tom Burgin
08dfad208b Move decision making to SNTPolicyProcessor (#91)
Move SNTEventState to a mixed bit field enum
SNTCommandFileInfo now handles all rule states
2016-09-14 12:34:42 -04:00
Russell Hancox
b5921f95f3 santa-driver: Remove the static wrappers in SantaDriverClient (#90)
SantaDriverClient was implemented to have static functions that call instance
methods passing appropriate arguments. While this works and is 'technically correct' (best kind),
it's a bit messy and hard to read.
2016-09-12 10:14:38 -04:00
Russell Hancox
2063bc3db3 Update pods, check length of EventDetailBundleURL, add text above URL in TTY (#89)
* SantaGUI: Check EventDetailBundleURL length rather than just existence

* santad: Add title above detail URL in TTY

* Project: Update pods
2016-09-09 16:11:40 -04:00
Tom Burgin
4380016d52 Compile SNTCommandController and SNTCommandFileInfo in the LogicTests target (#86) 2016-09-07 10:56:15 -04:00
Tom Burgin
5e3ceabe46 SNTCommandFileInfo Tests (#85) 2016-09-06 14:21:37 -04:00
Tom Burgin
8e7936275b Merge pull request #84 from russellhancox/fix-70
santactl/rule: Handle bad path properly (dir, non-file)
2016-09-06 13:31:57 -04:00
Russell Hancox
4b967239fa santactl/rule: Handle bad path properly (dir, non-file)
Fixes #70
2016-09-06 13:29:05 -04:00
Tom Burgin
92945c384c Merge pull request #83 from russellhancox/fix-82
Package: Ensure /usr/local/bin exists before making symlinks in it.
2016-09-06 13:10:44 -04:00
Russell Hancox
79d93c4ecf Package: Ensure /usr/local/bin exists before making symlinks in it.
Fixes #82
2016-09-06 13:03:49 -04:00
Allister Banks
76b6f25b0c uninstall.sh typo
typo
2016-09-01 11:20:44 -04:00
Allister Banks
aadce4890a Add uninstall script (#77)
Leaves configs, performs no checks about current state but should be
relatively idempotent (can't unload/rm stuff that's not there)
2016-08-30 11:41:20 -04:00
Tom Burgin
0e95a98fc2 santactl fileinfo sha1 & sha256 simultaneous hashing (#67) 2016-08-23 15:40:01 -04:00
Tom Burgin
9483437e8f Merge pull request #66 from russellhancox/master
santad: Database access optimizations
2016-08-23 14:29:40 -04:00
Russell Hancox
59542f8aef santad: Drop binrules/certrules views in rules database. 2016-08-23 12:48:41 -04:00
Russell Hancox
e29f7332f5 santad: Avoid creating multiple SNT*Table objects, as initializing them can be slow. 2016-08-23 12:48:41 -04:00
35 changed files with 1013 additions and 636 deletions

View File

@@ -1,6 +1,8 @@
---
language: objective-c
cache: cocoapods
cache:
- bundler
- cocoapods
sudo: false
osx_image: xcode7

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = "";

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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) {

View File

@@ -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];
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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];

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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];
}

View 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

View 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

View File

@@ -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>

View 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

View File

@@ -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 {

View File

@@ -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]);

View File

@@ -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 {