Compare commits

..

106 Commits

Author SHA1 Message Date
Tom Burgin
f631f219b0 santactl/sync: fixed exception when file_name is None / NSNull (#180)
* santactl/sync: fixed exception when file_name is None / NSNull

* review updates
2017-07-06 11:52:49 -04:00
Tom Burgin
aacae020b8 logs: add DAAppearanceTime to the DISKAPPEAR logs (#179)
* logs: add DAAppearanceTime to the DISKAPPEAR logs

* review updates

* discussion updates
2017-07-02 16:27:40 -04:00
Tom Burgin
7c426e0eec santactl/sync: upload file bundle executable relative path for bundle events (#178) 2017-06-28 11:55:21 -04:00
Tom Burgin
363826502f santabs: de-dupe generated events before upload (#177)
* santabs: de-dupe generated events before upload and remove locks

* review updates

* error updates
2017-06-22 17:46:04 -04:00
Russell Hancox
1cfadae068 SantaGUI: Don't show pop-up notifications for empty filenames (#176) 2017-06-12 11:28:32 -07:00
Tom Burgin
d3b3d722b4 santabs: use the ancestor bundle when searching for binaries (#175)
* santabs: use the ancestor bundle when searching for binaries

* review updates

* bundle tests
2017-06-09 17:04:23 -04:00
Tom Burgin
a82428958b santactl/rule: Add the ability to check the status of arbitrary sha256 hashes without on-disk artifacts. (#172) 2017-05-30 13:07:47 -04:00
Tom Burgin
b185632bda santad/SantaGUI: Add needsBundleHash property to SNTStoredEvent && (#170)
santactl/sync:sync server enables/disables client bundle support
2017-05-30 13:04:08 -04:00
Tom Burgin
e7a0c3d25b santactl/sync: Sync Server to set FCM interval and deadline (#168)
* santactl/sync: Sync Server to set FCM interval and deadline

* rename default constants

* review updates
2017-05-22 11:50:37 -04:00
Tom Burgin
ab33de2c15 project/config: Move /var/log/santa.log to /var/db/santa/santa.log (#173) 2017-05-22 11:10:10 -04:00
Tom Burgin
a1031cdc27 protect wakeup() from being called with 0 (#167)
* don't call wakeup on 0

* project: "share" the santabs target

* Project: Update CocoaPods to 1.2.1

* Project: pod deintegrate - pod install
2017-04-14 16:13:45 -04:00
Tom Burgin
e3ab3ca506 Update SNTCommandSyncEventUpload.m (#162) 2017-04-13 16:58:25 -04:00
Matthew Suozzo
b4cd1ccbee santa-driver: Fix a typo 2017-04-13 14:27:05 -04:00
Matthew Suozzo
14573a5714 santa-driver: Refactor cache expiration calculation 2017-04-12 22:35:08 -04:00
Tom Burgin
96150a9668 Bundle Events (#145)
* santabs: Create Santa Bundle Service

* common: SNTXPCConnection add initClientWithServiceName:

* santad: add logic for blocked bundles

* SantaGUI: add ui elements and xpc connections to / from santabs

* santactl/sync: add api features for syncing bundle events

* santactl/bundleinfo: add bundleinfo command for debug builds

* common: prefer bundle hash over file hash for event urls

* common: remove syncBackoff property - this is now handled in santactl sync

* common: add properties to support the bundle event api

* common: find a bundle from a nested binary

* review updates

* sane bundle hash time outs

* post rebase updates

* post review updates
2017-04-07 15:31:56 -04:00
Russell Hancox
c10c1303ed SantaGUI: Add preprocessor flag to import Cocoa for SNTBlockMessage 2017-04-05 14:19:50 -04:00
Tom Burgin
7852e69685 SantaCache fix 0 init (#158) 2017-03-22 09:38:27 -04:00
Russell Hancox
094880af50 Project: Add DevelopmentTeam configuration (#157)
This is a generated xcconfig in the Rakefile which gets included by the project
to set the DEVELOPMENT_TEAM key to keep Xcode 8 happy. The development team is
figured based on the available “Mac Developer” certificate.

Also update the way SantaCache declares a ‘zero’ value, update the
OCMock pod and add a few missing includes.
2017-03-20 16:34:59 -04:00
Tom Burgin
c3db518aca santactl/sync: use the new fcm-stream format (#156) 2017-03-20 14:42:29 -04:00
Tom Burgin
41ee0c5fdb Running without a config fixes (#154)
* common: capture fileSystemRepresentation in a local variable

* santactl/status: check for instant notification status only when there is a sync url

* s/FALSE/NO
2017-03-17 12:12:41 -04:00
Tom Burgin
ae178bc146 create default config if one does not exist (#153) 2017-03-10 17:17:52 -05:00
Tom Burgin
a2a660d483 config update and modules (#152)
* santactl/sync: https://github.com/google/santa/issues/150

* pch to modules
2017-03-09 13:02:02 -08:00
Tom Burgin
8684cc34f7 santactl/sync: use hostname for reachability (#149)
* Revert "SNTXPCConnection: make XPC debugging easier (#141)"

This reverts commit a2d6338400.

* santactl/sync: use hostname for reachability

* style update
2017-03-08 07:55:35 -08:00
Tom Burgin
0aba8b78ba disable bundle scans (#146)
* config: update to cocoapods-1.2.0 and molfcmclient 1.2

* santactl/sync: disable sync server bundle scan requests
2017-03-01 09:02:00 -08:00
Russell Hancox
5e735aa8d5 santad: Clear cache when regexes change. (#143)
When white/black-list regexes are changed clear the kernel cache so the regexes are able to take effect immediately. Fixes #142
2017-02-03 11:00:32 -05:00
Tom Burgin
a2d6338400 SNTXPCConnection: make XPC debugging easier (#141) 2017-01-31 15:36:09 -05:00
Russell Hancox
5e4b8350ab SNTXPCConnection: allow redefining invalidationHandler after connections are established (#140) 2017-01-23 11:10:13 -05:00
Tom Burgin
4a65b646df santactl status: add last successful rule sync date (#139)
* santactl status: add last successful rule sync date
2017-01-11 15:52:07 -05:00
Tom Burgin
24c715aae9 santactl sync: reachability and notification updates santad: syncd xpc updates (#138)
* santactl sync: post a notification for every matching rule and fcm message

* santactl sync: if full sync fails, retry when reachable

* santad: only allow one syncd connection at any given time
2017-01-10 16:14:15 -05:00
Tom Burgin
9ab85768bd Update Podfile.lock to use MOLFCMClient v1.1 (#136) 2017-01-03 11:10:15 -05:00
Tom Burgin
16458d96e7 Notification verbage update (#135) 2016-12-14 14:41:20 -05:00
Tom Burgin
b307dd17af Use machine ids as the targeted sync indicator (#134)
* Use machine ids as the targeted sync indicator

* remove unused constant
2016-12-12 16:53:24 -05:00
Tom Burgin
313552352c Display the binary name when a local rule is synced from a push notification (#133) 2016-12-07 17:40:11 -05:00
Tom Burgin
543ac7c649 push notifications with FCM (#132)
* push notifications with FCM

* Don't display rule count in notifications. Get FCM broadcast topic from sync server.
2016-12-06 16:04:34 -05:00
Tom Burgin
dacff76694 run santactl as a sync daemon (#129)
* run santactl as a sync daemon
2016-11-16 14:41:12 -05:00
Russell Hancox
c134169ea1 santad: Drop AUTOINCREMENT on event table (#130) 2016-11-01 11:14:51 -04:00
Russell Hancox
e252945047 santactl/fileinfo: Send resolved path to santad for processing (#128) 2016-10-26 16:04:27 -04:00
Russell Hancox
f8cfcaab20 Package/Conf: Fix typo in uninstall.sh (#126) 2016-10-25 15:05:36 -04:00
Tom Burgin
528237a239 santactl status: check non-boxed vars when building json output (#125) 2016-10-24 12:14:56 -04:00
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
Russell Hancox
f8640feafe Project: Include xcodebuild clean in rake clean 2016-08-22 14:49:18 -04:00
Russell Hancox
e94e9e2be4 Project: Clean up CocoaPods project cruft 2016-08-22 14:46:56 -04:00
Tom Burgin
4053aac365 Merge pull request #65 from russellhancox/master
santactl/fileinfo: Recognize bundle/plugin mach-o files.
2016-08-22 14:07:43 -04:00
Russell Hancox
a5fa6c7aef santactl/fileinfo: Recognize bundle/plugin mach-o files. 2016-08-22 14:05:22 -04:00
Russell Hancox
97263894d1 santactl/sync: Send existing client mode in preflight request 2016-08-19 15:10:50 -04:00
Russell Hancox
1885580958 Project: pod update 2016-08-19 15:10:50 -04:00
Tom Burgin
1167b470bb santactl/fileinfo: Fix arg parsing, better cert printing
* arg parse fixes

* More parse fixes
2016-08-19 14:53:33 -04:00
Russell Hancox
7600506d6d santad: Include client mode in execution logs. 2016-08-18 14:44:40 -04:00
Russell Hancox
86bad866a0 santad: Unify CERT vs CERTIFICATE in logs. 2016-08-18 14:13:36 -04:00
Russell Hancox
2f1a15cf7e SantaGUI: Fix bundle version URLs 2016-08-18 14:11:42 -04:00
Tom Burgin
52b0e1870f Squashed binary and cert rule fetching down to one call. (#62) 2016-08-17 17:06:51 -04:00
Tom Burgin
9b181c1e0d santactl fileinfo updates (#61)
* Added --json output option. Added --key output option.
* Added multi-file processing
* Added threading
* \r to cleanup during really quick runs
2016-08-17 15:55:03 -04:00
Tom Burgin
100f2dc45e Merge pull request #60 from russellhancox/master
Performance improvements, GUI bundle handling
2016-08-12 16:42:00 -04:00
Russell Hancox
b247c3d477 santa-driver: Try to prevent logspam when dropping log queue messages
Both PostTo*Queue methods use mutexes, so access to the failed_*_queue_requests_ variables don't need to be atomic.
2016-08-12 16:08:23 -04:00
Russell Hancox
76ee82b258 santad: Limit log queue to 15 threads
To counteract the increased likelihood of dropped messages, double the maximum
log queue size.
2016-08-12 15:04:21 -04:00
Russell Hancox
e8fcd29669 santa-driver: If a request for a given vnode is pending, don't repeat request. 2016-08-12 15:04:21 -04:00
Russell Hancox
8dd16ecea4 santa-driver: Remove references to vnode_id_str
These should have been culled when moving to SantaCache but were missed.
2016-08-12 15:04:21 -04:00
Russell Hancox
e9c0bcd877 SantaGUI: Handle bundles having version instead of short version string 2016-08-12 15:04:21 -04:00
Allister Banks
75ed4b52a6 revise readme (#57)
* overall readme revise

admin-specific vs. security/performance features split up, add details
about path-based functionality, PAGEZERO feature, failsafe cert
whitelisting, explicitly say default mode is MONITOR

* process feedback

sticking with talking about binary launches while kext is loaded,
integrated all other feedback
2016-08-10 15:53:55 -04:00
Tom Burgin
71635c00df Merge pull request #58 from russellhancox/master
Performance improvements
2016-08-10 15:53:00 -04:00
Russell Hancox
1810af5483 SantaGUI: Change Dismiss button to Ignore 2016-08-10 15:18:22 -04:00
Russell Hancox
b07835dfd5 santad: Cache user/group id->name lookups. 2016-08-10 15:18:22 -04:00
Russell Hancox
4c33aa2aae santad: Improve loggedInUsers:sessions: 2016-08-09 16:51:23 -04:00
Russell Hancox
3c255640cb santad: Speed up TTY message creation 2016-08-09 16:51:23 -04:00
Russell Hancox
3d08ba9ebc santa-driver: Use msleep/wakeup instead of IOSleep.
This brings the average cache-miss decision making time down by 66%. Previously the minimum decision time was 10ms, now it's <1ms.
2016-08-09 16:51:23 -04:00
Russell Hancox
f64482500e santa-driver: Add debug logging of decision times to GetFromDaemon 2016-08-09 16:51:20 -04:00
Russell Hancox
215902f192 SantaCache: Extract entry value before unlocking bucket. 2016-07-19 16:28:35 -04:00
Russell Hancox
3e9c3a069d Project: Pod update 2016-07-19 14:51:01 -04:00
Russell Hancox
841fb48479 santa-driver: Only send file mod notifications to queue if client is connected. 2016-07-14 13:45:13 -04:00
Russell Hancox
df8e41925f SNTFileInfo: Check NSURLQuarantinePropertiesKey is usable 2016-07-13 17:29:53 -04:00
127 changed files with 5033 additions and 1874 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ Santa.xcodeproj/xcuserdata
Santa.xcodeproj/project.xcworkspace
Santa.xcworkspace/xcuserdata
Santa.xcworkspace/xcshareddata
Source/DevelopmentTeam.xcconfig

View File

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

View File

@@ -82,3 +82,4 @@ myclean:
@rm -f com.google.santad.plist
@rm -f com.google.santagui.plist
@rm -f install.sh
@rm -f uninstall.sh

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

@@ -1,6 +1,6 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
> /var/db/santa/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
? [= Sender kernel] [S= Message santa-driver:] claim
? [= Sender kernel] [S= Message santa-driver:] file /var/log/santa.log
? [= Sender kernel] [S= Message santa-driver:] file /var/db/santa/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/log/santa.log
? [= Facility com.google.santa] file /var/db/santa/santa.log

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/bin/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

@@ -11,6 +11,11 @@ target :santad do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
target :santabs do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
end
end
target :santactl do
@@ -18,6 +23,7 @@ target :santactl do
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'MOLFCMClient', '~> 1.3'
end
target :LogicTests do

View File

@@ -2,27 +2,31 @@ PODS:
- FMDB (2.6.2):
- FMDB/standard (= 2.6.2)
- FMDB/standard (2.6.2)
- MOLAuthenticatingURLSession (1.6):
- MOLCertificate (~> 1.3)
- MOLAuthenticatingURLSession (2.2):
- MOLCertificate (~> 1.5)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- OCMock (3.3)
- MOLFCMClient (1.3):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.4)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient (~> 1.3)
- OCMock
SPEC CHECKSUMS:
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
PODFILE CHECKSUM: bc456d69693ca262c781dbbde40529a9474b84b5
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
COCOAPODS: 1.0.1
COCOAPODS: 1.2.1

View File

@@ -16,25 +16,53 @@ managing the system and synchronizing the database with a server.
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
and finishing up a security audit.
Santa is named because it keeps track of binaries that are naughty and nice.
It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
Features
Admin-Related Features
========
* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except
those marked as blacklisted will be allowed to run, whilst being logged and
recorded in the database. In LOCKDOWN mode, only whitelisted binaries are
* Multiple modes: In the default MONITOR mode, all binaries except
those marked as blacklisted will be allowed to run, whilst being logged and recorded in the events database. In LOCKDOWN mode, only whitelisted binaries are
allowed to run.
* Codesign listing: Binaries can be whitelisted/blacklisted by their signing
certificate, so you can trust/block all binaries by a given publisher. The
binary will only be whitelisted by certificate if its signature validates
correctly. However, a decision for a binary will override a decision for a
certificate; i.e. you can whitelist a certificate while blacklisting a binary
signed by that certificate or vice-versa.
* Event logging: When the kext is loaded, all binary launches are logged.
When in either mode, all unknown or denied binaries are stored in the database to enable later aggregation.
* Certificate-based rules, with override levels: Instead of relying on a binaries hash (or 'fingerprint'), executables can be whitelisted/blacklisted by their signing
certificate. You can therefore trust/block all binaries by a given publisher that were signed with that cert across version updates. A
binary can only be whitelisted by its certificate if its signature validates
correctly, but a rule for a binaries fingerprint will override a decision for a
certificate; i.e. you can whitelist a certificate while blacklisting a binary
signed with that certificate, or vice-versa.
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature as Managed Client for OS X's (the precursor to configuration profiles, which used the same implementation mechanism) Application Launch Restrictions via the mcxalr binary. This implementation carries the added benefit of being configurable via regex, and doesn't rely on LaunchServices. As detailed in the wiki, when evaluating rules this holds the lowest precendence.
* Failsafe cert rules: You cannot put in a deny rule that would block the certificate used to sign launchd, a.k.a. pid 1, and therefore all components used in macOS. The binaries in every OS update (and in some cases entire new versions) are therefore auto-whitelisted. This does not affect binaries from Apple's App Store, which use various certs that change regularly for common apps. Likewise, you cannot blacklist Santa itself, and Santa uses a distinct separate cert than other Google apps.
Intentions and Expectations
===========================
No single system or process will stop *all* attacks, or provide 100% security.
Santa is written with the intention of helping protect users from themselves.
People often download malware and trust it, giving the malware credentials, or
allowing unknown software to exfiltrate more data about your system. As a
centrally managed component, Santa can help stop the spread of malware among a
larger fleet of machines. Independently, Santa can aid in analyzing what is
running on your computer.
Santa is part of a defense-in-depth strategy, and you should continue to protect
hosts in whatever other ways you see fit.
Get Help
========
If you have questions or otherwise need help getting started, the
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place. Please consult the [wiki](https://github.com/google/santa/wiki) and [issues](https://github.com/google/santa/issues) as well.
Security and Performance-Related Features
============
* In-kernel caching: whitelisted binaries are cached in the kernel so the
processing required to make a request is only done if the binary
isn't already cached.
@@ -44,42 +72,17 @@ daemon, the GUI agent and the command-line utility) communicate with each other
using XPC and check that their signing certificates are identical before any
communication is accepted.
* Event logging: all executions processed by the userland agent are logged and
all unknown or denied binaries are also stored in the database for upload to a
server.
* Kext uses only KPIs: the kernel extension only uses provided kernel
programming interfaces to do its job. This means that the kext code should
continue to work across OS versions.
Intentions and Expectations
===========================
No single system or process will stop *all* attacks, or provide 100% security.
Santa is written with the intention of helping protect users from themselves.
People often download malware and trust it, giving the malware credentials, or
allowing unknown software to exfiltrate more data about your system. As a
centrally managed component, Santa can help stop the spread of malware among a
larger fleet of machines. Additionally, Santa can aid in analyzing what is
running in your fleet.
Santa is part of a defense-in-depth strategy, and you should continue to protect
hosts in whatever other ways you see fit.
Get Help
========
If you have questions or need help getting started, the
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is the
best place to start.
Known Issues
============
Santa is not yet a 1.0 and we have some known issues to be aware of:
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been replaced or
libraries loaded using `DYLD_INSERT_LIBRARIES`. We are working on also protecting
against these avenues of attack.
dynamic libraries loaded with dlopen, libraries on disk that have been replaced, or
libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version 0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were exploited in some versions of macOS. We are working on also protecting against similar avenues of attack.
* Kext communication security: the kext will only accept a connection from a
single client at a time and said client must be running as root. We haven't yet
@@ -89,9 +92,8 @@ found a good way to ensure the kext only accepts connections from a valid client
only the root user can read/write it. We're considering approaches to secure
this further.
* Sync client: the command-line client includes a command to synchronize with a
management server, including the uploading of events that have occurred on the
machine and to download new rules. We're still very heavily working on this
* Sync client: The `santactl` command-line client includes a flag to synchronize with a management server, which uploads events that have occurred on the
machine and downloads new rules. We're still very heavily working on this
server (which is AppEngine-based and will be open-sourced in the future), so the
sync client code is unfinished. It does show the 'API' that we're expecting to
use so if you'd like to write your own management server, feel free to look at
@@ -104,7 +106,7 @@ of temporary generated scripts, which we can't possibly whitelist and not doing
so would cause problems. We're happy to revisit this (or at least make it an
option) if it would be useful to others.
* Documentation: There currently isn't any.
* Documentation: This is currently limited.
* Tests: There aren't enough of them.
@@ -119,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

@@ -1,3 +1,5 @@
require 'openssl'
WORKSPACE = 'Santa.xcworkspace'
DEFAULT_SCHEME = 'All'
OUTPUT_PATH = 'Build'
@@ -5,6 +7,8 @@ BINARIES = ['Santa.app', 'santa-driver.kext']
DSYMS = ['Santa.app.dSYM', 'santa-driver.kext.dSYM', 'santad.dSYM', 'santactl.dSYM']
XCPRETTY_DEFAULTS = '-sc'
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
DEVTEAM_FILE = 'Source/DevelopmentTeam.xcconfig'
DEVTEAM_CERT_CN = 'Mac Developer'
$DISABLE_XCPRETTY = false
task :default do
@@ -44,6 +48,13 @@ task :init do
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
$DISABLE_XCPRETTY = true
end
cert_pem = `security find-certificate -p -c '#{DEVTEAM_CERT_CN}'`
cert = OpenSSL::X509::Certificate.new cert_pem
team_id = cert.subject.to_a.find {|f| f[0] == "OU"}[1]
File.open(DEVTEAM_FILE, 'w') { |f|
f.puts("// This file is auto-generated. Do not edit manually")
f.puts("DEVELOPMENT_TEAM = #{team_id}")
}
end
task :remove_existing do
@@ -55,6 +66,7 @@ desc "Clean"
task :clean => :init do
puts "Cleaning"
FileUtils.rm_rf(OUTPUT_PATH)
xcodebuild("-scheme All clean")
end
# Build
@@ -94,6 +106,7 @@ namespace :install do
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
system '/usr/bin/killall -HUP syslogd'
Rake::Task['build:build'].invoke(config)
puts "Installing with configuration: #{config}"
Rake::Task['remove_existing'].invoke()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C78227531E1C3C58006EB2D6"
BuildableName = "santabs.xpc"
BlueprintName = "santabs"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C78227531E1C3C58006EB2D6"
BuildableName = "santabs.xpc"
BlueprintName = "santabs"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C78227531E1C3C58006EB2D6"
BuildableName = "santabs.xpc"
BlueprintName = "santabs"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14E46" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="16D32" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
@@ -16,7 +16,7 @@
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="200"/>
<autoresizingMask key="autoresizingMask"/>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<development version="6300" identifier="xcode"/>
@@ -9,6 +9,10 @@
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
<connections>
<outlet property="applicationNameLabel" destination="qgf-Jf-cJr" id="1JX-X8-03v"/>
<outlet property="bundleHashLabel" destination="xP7-jE-NF8" id="i8B-Gs-2E3"/>
<outlet property="bundleHashTitle" destination="MhO-U0-MLR" id="KT0-bK-fpV"/>
<outlet property="foundFileCountLabel" destination="LHV-gV-vyf" id="Sr0-T2-xGx"/>
<outlet property="hashingIndicator" destination="VyY-Yg-JOe" id="Yq4-tZ-9ep"/>
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
</connections>
@@ -17,14 +21,14 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<windowStyleMask key="styleMask" utility="YES"/>
<rect key="contentRect" x="167" y="107" width="497" height="439"/>
<rect key="contentRect" x="167" y="107" width="540" height="479"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="497" height="439"/>
<rect key="frame" x="0.0" y="0.0" width="540" height="479"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="-6" y="411" width="37" height="32"/>
<rect key="frame" x="16" y="451" width="37" height="32"/>
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -37,7 +41,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="206" y="368" width="85" height="41"/>
<rect key="frame" x="228" y="408" width="85" height="41"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" metaFont="systemUltraLight" size="34"/>
<color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
@@ -51,7 +55,7 @@
</connections>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="22" y="329" width="454" height="17"/>
<rect key="frame" x="43" y="369" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
@@ -65,58 +69,10 @@
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="VC7-bE-uHc"/>
</connections>
</textField>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="132" width="1" height="167"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="282" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="282" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="257" width="120" height="17"/>
<rect key="frame" x="8" y="297" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
@@ -128,9 +84,9 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="232" width="120" height="17"/>
<rect key="frame" x="8" y="272" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="3wU-P0-gAC"/>
<constraint firstAttribute="width" constant="138" id="3wU-P0-gAC"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="adC-be-Beh">
<font key="font" metaFont="systemBold"/>
@@ -141,10 +97,10 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="257" width="294" height="17"/>
<textField toolTip="Binary Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="297" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
<constraint firstAttribute="width" constant="311" id="xVR-j3-dLw"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Name" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
@@ -156,7 +112,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="207" width="120" height="17"/>
<rect key="frame" x="8" y="247" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -167,9 +123,9 @@
</textFieldCell>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="207" width="294" height="17"/>
<rect key="frame" x="187" y="247" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
<constraint firstAttribute="width" constant="311" id="Dem-wH-KHm"/>
</constraints>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
<font key="font" metaFont="system"/>
@@ -185,7 +141,7 @@
</connections>
</textField>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="40" y="208" width="15" height="15"/>
<rect key="frame" x="62" y="248" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
@@ -207,7 +163,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<rect key="frame" x="8" y="182" width="120" height="17"/>
<rect key="frame" x="8" y="222" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -217,8 +173,8 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="182" width="219" height="17"/>
<textField toolTip="SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="187" y="222" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
@@ -234,8 +190,26 @@
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MhO-U0-MLR" userLabel="Label: Bundle Identifier">
<rect key="frame" x="8" y="197" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Bundle Identifier" id="LEe-u0-52o">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.needsBundleHash" id="2kb-3z-Kyn">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="120" height="17"/>
<rect key="frame" x="8" y="157" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -246,7 +220,7 @@
</textFieldCell>
</textField>
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="157" width="294" height="17"/>
<rect key="frame" x="187" y="157" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
@@ -269,7 +243,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="120" height="17"/>
<rect key="frame" x="8" y="132" width="142" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -279,26 +253,8 @@
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="132" y="33" width="112" height="25"/>
<rect key="frame" x="154" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
@@ -315,13 +271,40 @@ DQ
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="272" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="p1W-f9-KBX"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="113" y="80" width="315" height="29"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="256" y="33" width="110" height="25"/>
<rect key="frame" x="278" y="33" width="110" height="25"/>
<constraints>
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<buttonCell key="cell" type="roundTextured" title="Ignore" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
@@ -335,61 +318,138 @@ DQ
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
</connections>
</button>
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="232" width="294" height="17"/>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="p1W-f9-KBX"/>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
<rect key="frame" x="91" y="80" width="315" height="29"/>
<progressIndicator wantsLayer="YES" canDrawConcurrently="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="1" bezeled="NO" controlSize="small" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="VyY-Yg-JOe">
<rect key="frame" x="187" y="199" width="217" height="12"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
<constraint firstAttribute="width" constant="217" id="M22-Dv-KIP"/>
</constraints>
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
</progressIndicator>
<textField toolTip="Bundle SHA-256" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xP7-jE-NF8">
<rect key="frame" x="187" y="197" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="s7W-o9-2nN"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="Calculating..." id="yJa-yL-X9a">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
<binding destination="-2" name="value" keyPath="self.event.fileBundleHash" id="CnT-q6-bot"/>
</connections>
</button>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LHV-gV-vyf">
<rect key="frame" x="187" y="182" width="219" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="LUu-Vd-peN"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="1000 related binaries" id="AVM-vB-hB8">
<font key="font" metaFont="fixedUser" size="11"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="168" y="132" width="1" height="207"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="187" y="322" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="311" id="Pav-ZA-iAu"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="322" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="138" id="8mA-zi-Ev7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
</subviews>
<constraints>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="0AD-PS-5V1"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="5Mr-By-PAU"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="centerY" secondItem="qgf-Jf-cJr" secondAttribute="centerY" id="AKX-pe-hEX"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-67" id="GD2-Ka-deo"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="GT2-tO-2td"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pDa-fA-vnC" secondAttribute="top" id="Gd4-Nr-n5G"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="HUT-MI-jsR"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
<constraint firstItem="LHV-gV-vyf" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="IA0-dy-2be"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="IwX-ja-ZIs"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" priority="500" id="JY4-N1-j8e"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="centerX" secondItem="Iwq-Lx-rLv" secondAttribute="centerX" id="LkH-F4-Ncm"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="55" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
<constraint firstItem="LHV-gV-vyf" firstAttribute="top" secondItem="VyY-Yg-JOe" secondAttribute="bottom" id="Vjr-NX-j8V"/>
<constraint firstItem="MhO-U0-MLR" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="Vly-VE-BwU"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="top" secondItem="bDE-Tl-UHg" secondAttribute="bottom" constant="8" id="ZoS-xV-2WA"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aMJ-Wb-vRS"/>
@@ -401,22 +461,28 @@ DQ
<constraint firstItem="qgf-Jf-cJr" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="esg-lX-BAT"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="fGd-YS-phP"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="LHV-gV-vyf" secondAttribute="bottom" constant="8" id="h4h-K3-BTd"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" priority="700" constant="8" id="hXw-6Z-lb2"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="jdk-ak-soQ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="MhO-U0-MLR" secondAttribute="trailing" constant="20" id="ke9-wW-5fr"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="top" secondItem="h6f-PY-cc0" secondAttribute="bottom" constant="25" id="lYd-VZ-lBs"/>
<constraint firstItem="VyY-Yg-JOe" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="18" id="lei-uP-T8m"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="xP7-jE-NF8" secondAttribute="bottom" priority="701" constant="8" id="oY4-e7-lsz"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="pCX-eX-erN"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
<constraint firstItem="xP7-jE-NF8" firstAttribute="centerY" secondItem="MhO-U0-MLR" secondAttribute="centerY" id="pdC-x8-Nao"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="60" id="phL-j9-rPq"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="u1y-6V-moc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="VyY-Yg-JOe" firstAttribute="centerY" secondItem="MhO-U0-MLR" secondAttribute="centerY" id="vB8-c5-pfO"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="YNz-ka-cBi" secondAttribute="trailing" constant="20" id="vfq-83-tKI"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="z6s-ga-iAk"/>
</constraints>
@@ -424,8 +490,9 @@ DQ
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="302.5" y="304.5"/>
<point key="canvasLocation" x="274" y="326.5"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="iXx-cu-WYe"/>
</objects>
<resources>
<image name="NSInfo" width="32" height="32"/>

View File

@@ -1,3 +0,0 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
@interface SNTAboutWindowController : NSWindowController
@property IBOutlet NSButton *moreInfoButton;

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
/**
An NSTextField subclass that provides an accessiblity label equal to:
(self.toolTip + self.stringValue) where available. It also sets the

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
///
/// Initiates and manages the connection to santad
///

View File

@@ -26,7 +26,8 @@
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *listener;
@property SNTXPCConnection *daemonListener;
@property SNTXPCConnection *bundleListener;
@end
@implementation SNTAppDelegate
@@ -49,18 +50,19 @@
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
self.listener.invalidationHandler = nil;
[self.listener invalidate];
self.listener = nil;
self.daemonListener.invalidationHandler = nil;
[self.daemonListener invalidate];
self.daemonListener = nil;
}];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
[self attemptReconnection];
[self attemptDaemonReconnection];
}];
[self createConnection];
[self createDaemonConnection];
[self createBundleConnection];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
@@ -71,24 +73,24 @@
#pragma mark Connection handling
- (void)createConnection {
- (void)createDaemonConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
WEAKIFY(self);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.acceptedHandler = ^{
self.daemonListener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.daemonListener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.daemonListener.exportedObject = self.notificationManager;
self.daemonListener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
self.daemonListener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptReconnection];
[self attemptDaemonReconnection];
};
[self.listener resume];
[self.daemonListener resume];
// Tell daemon to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
@@ -97,12 +99,46 @@
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptReconnection];
[self attemptDaemonReconnection];
}
}
- (void)attemptReconnection {
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
- (void)attemptDaemonReconnection {
[self performSelectorInBackground:@selector(createDaemonConnection) withObject:nil];
}
- (void)createBundleConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
WEAKIFY(self);
// Create listener for return connection from the bundle service.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.bundleListener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.bundleListener.exportedInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
self.bundleListener.exportedObject = self.notificationManager;
self.bundleListener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.bundleListener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptBundleReconnection];
};
[self.bundleListener resume];
// Tell santabs to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setBundleNotificationListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptBundleReconnection];
}
}
- (void)attemptBundleReconnection {
[self performSelectorInBackground:@selector(createBundleConnection) withObject:nil];
}
#pragma mark Menu Management

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
///
/// An NSPanel that can become key/main and can fade in/out.
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
@class SNTStoredEvent;
@protocol SNTMessageWindowControllerDelegate
@@ -29,6 +31,40 @@
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
/// Reference to the "Bundle Hash" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashLabel;
/// Reference to the "Bundle Identifier" label in the XIB. Used to remove if application
/// doesn't have a bundle hash.
@property(weak) IBOutlet NSTextField *bundleHashTitle;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSProgressIndicator *hashingIndicator;
///
/// Is displayed if calculating the bundle hash is taking a bit.
///
@property(weak) IBOutlet NSTextField *foundFileCountLabel;
///
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property(weak) IBOutlet NSButton *openEventButton;
///
/// The execution event that this window is for
///
@property(readonly) SNTStoredEvent *event;
///
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///

View File

@@ -14,7 +14,7 @@
#import "SNTMessageWindowController.h"
#import <SecurityInterface/SFCertificatePanel.h>
@import SecurityInterface.SFCertificatePanel;
#import "MOLCertificate.h"
#import "SNTBlockMessage.h"
@@ -24,9 +24,6 @@
#import "SNTStoredEvent.h"
@interface SNTMessageWindowController ()
/// The execution event that this window is for
@property SNTStoredEvent *event;
/// The custom message to display for this event
@property(copy) NSString *customMessage;
@@ -36,13 +33,9 @@
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
@property IBOutlet NSButton *openEventButton;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property IBOutlet NSTextField *applicationNameLabel;
@property(weak) IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@@ -55,10 +48,34 @@
if (self) {
_event = event;
_customMessage = message;
_progress = [NSProgress discreteProgressWithTotalUnitCount:1];
[_progress addObserver:self
forKeyPath:@"fractionCompleted"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
- (void)dealloc {
[_progress removeObserver:self forKeyPath:@"fractionCompleted"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:@"fractionCompleted"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSProgress *progress = object;
if (progress.fractionCompleted != 0.0) {
self.hashingIndicator.indeterminate = NO;
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
@@ -73,6 +90,18 @@
}
}
if (!self.event.needsBundleHash) {
[self.bundleHashLabel removeFromSuperview];
[self.hashingIndicator removeFromSuperview];
[self.foundFileCountLabel removeFromSuperview];
} else {
self.openEventButton.enabled = NO;
self.hashingIndicator.indeterminate = YES;
[self.hashingIndicator startAnimation:self];
self.bundleHashLabel.hidden = YES;
self.foundFileCountLabel.stringValue = @"";
}
if (!self.event.fileBundleName) {
[self.applicationNameLabel removeFromSuperview];
}
@@ -83,6 +112,7 @@
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}

View File

@@ -12,12 +12,15 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
#import "SNTMessageWindowController.h"
#import "SNTXPCNotifierInterface.h"
///
/// Keeps track of pending notifications and ensures only one is presented to the user at a time.
///
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate,
SNTNotifierXPC, SNTBundleNotifierXPC>
@end

View File

@@ -18,13 +18,27 @@
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTNotificationManager ()
/// The currently displayed notification
@property SNTMessageWindowController *currentWindowController;
/// The queue of pending notifications
@property(readonly) NSMutableArray *pendingNotifications;
/// The connection to the bundle service
@property SNTXPCConnection *bundleServiceConnection;
/// A semaphore to block bundle hashing until a connection is established
@property dispatch_semaphore_t bundleServiceSema;
// A serial queue for holding hashBundleBinaries requests
@property dispatch_queue_t hashBundleBinariesQueue;
@end
@implementation SNTNotificationManager
@@ -35,6 +49,9 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
self = [super init];
if (self) {
_pendingNotifications = [[NSMutableArray alloc] init];
_bundleServiceSema = dispatch_semaphore_create(0);
_hashBundleBinariesQueue = dispatch_queue_create("com.google.santagui.hashbundlebinaries",
DISPATCH_QUEUE_SERIAL);
}
return self;
}
@@ -48,7 +65,16 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
if ([self.pendingNotifications count]) {
self.currentWindowController = [self.pendingNotifications firstObject];
[self.currentWindowController showWindow:self];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
} else {
// Tear down the bundle service
self.bundleServiceSema = dispatch_semaphore_create(0);
[self.bundleServiceConnection invalidate];
self.bundleServiceConnection = nil;
[NSApp hide:self];
}
}
@@ -65,7 +91,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
[ud setObject:d forKey:silencedNotificationsKey];
}
#pragma mark SNTNotifierXPC protocol method
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
NSUserNotification *un = [[NSUserNotification alloc] init];
@@ -131,6 +157,115 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
}
});
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
un.informativeText = message ?: @"Requested application can now be run";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
#pragma mark SNTBundleNotifierXPC protocol methods
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount {
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
self.currentWindowController.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu %@",
binaryCount, hashedCount ?: fileCount, hashedCount ? @"hashed" : @"files"];
});
}
}
- (void)setBundleServiceListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[c resume];
self.bundleServiceConnection = c;
WEAKIFY(self);
self.bundleServiceConnection.invalidationHandler = ^{
STRONGIFY(self);
if (self.currentWindowController) {
[self updateBlockNotification:self.currentWindowController.event withBundleHash:nil];
}
};
dispatch_semaphore_signal(self.bundleServiceSema);
}
#pragma mark SNTBundleNotifierXPC helper methods
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
self.currentWindowController.foundFileCountLabel.stringValue = @"Searching for files...";
// Wait a max of 6 secs for the bundle service. Should the bundle service fall over, it will
// reconnect within 5 secs. Otherwise abandon bundle hashing and display the blockable event.
if (dispatch_semaphore_wait(self.bundleServiceSema,
dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC))) {
[self updateBlockNotification:event withBundleHash:nil];
return;
}
// Let all future requests flow, until the connection is terminated and we go back to waiting.
dispatch_semaphore_signal(self.bundleServiceSema);
// NSProgress becomes current for this thread. XPC messages vend a child node to the receiver.
[self.currentWindowController.progress becomeCurrentWithPendingUnitCount:100];
// Start hashing. Progress is reported to the root NSProgress (currentWindowController.progress).
[[self.bundleServiceConnection remoteObjectProxy]
hashBundleBinariesForEvent:event
reply:^(NSString *bh, NSArray<SNTStoredEvent *> *events, NSNumber *ms) {
// Revert to displaying the blockable event if we fail to calculate the bundle hash
if (!bh) return [self updateBlockNotification:event withBundleHash:nil];
event.fileBundleHash = bh;
event.fileBundleBinaryCount = @(events.count);
event.fileBundleHashMilliseconds = ms;
event.fileBundleExecutableRelPath = [events.firstObject fileBundleExecutableRelPath];
for (SNTStoredEvent *se in events) {
se.fileBundleHash = bh;
se.fileBundleBinaryCount = @(events.count);
se.fileBundleHashMilliseconds = ms;
}
// Send the results to santad. It will decide if they need to be synced.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] syncBundleEvent:event relatedEvents:events];
// Update the UI with the bundle hash. Also make the openEventButton available.
[self updateBlockNotification:event withBundleHash:bh];
}];
[self.currentWindowController.progress resignCurrent];
}
- (void)updateBlockNotification:(SNTStoredEvent *)event withBundleHash:(NSString *)bundleHash {
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
if (bundleHash) {
[self.currentWindowController.bundleHashLabel setHidden:NO];
} else {
[self.currentWindowController.bundleHashLabel removeFromSuperview];
[self.currentWindowController.bundleHashTitle removeFromSuperview];
}
self.currentWindowController.event.fileBundleHash = bundleHash;
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
[self.currentWindowController.hashingIndicator setHidden:YES];
[self.currentWindowController.openEventButton setEnabled:YES];
}
});
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
#import "SNTAppDelegate.h"
int main(int argc, const char *argv[]) {

View File

@@ -12,6 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifdef SANTAGUI
@import Cocoa;
#else
@import Foundation;
#endif
@class SNTStoredEvent;
@interface SNTBlockMessage : NSObject

View File

@@ -92,9 +92,16 @@
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *formatStr;
if (config.eventDetailBundleURL && event.fileBundleID) {
NSString *formatStr, *versionStr;
if (config.eventDetailBundleURL.length && event.fileBundleID) {
formatStr = config.eventDetailBundleURL;
versionStr = event.fileBundleVersion;
if (!versionStr) versionStr = event.fileBundleVersionString;
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
withString:event.fileBundleID];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
withString:versionStr];
} else {
formatStr = config.eventDetailURL;
}
@@ -102,8 +109,9 @@
if (!formatStr.length) return nil;
if (event.fileSHA256) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileSHA256];
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
@@ -113,14 +121,6 @@
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
withString:config.machineID];
}
if (event.fileBundleID) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
withString:event.fileBundleID];
}
if (event.fileBundleVersionString) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
withString:event.fileBundleVersionString];
}
return [NSURL URLWithString:formatStr];
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// These enums are used in various places throughout the Santa client code.
/// The integer values are also stored in the database and so shouldn't be changed.
@@ -41,19 +43,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

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///
@@ -131,21 +133,20 @@ extern NSString *const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// If YES, mid-execution event uploads are skipped.
/// This property is never stored on disk.
///
@property BOOL syncBackOff;
///
/// The machine owner.
///
@property(readonly, nonatomic) NSString *machineOwner;
///
/// The last date of successful sync.
/// The last date of a successful full sync.
///
@property(nonatomic) NSDate *syncLastSuccess;
@property(nonatomic) NSDate *fullSyncLastSuccess;
///
/// The last date of a successful rule sync.
///
@property(nonatomic) NSDate *ruleSyncLastSuccess;
///
/// If YES a clean sync is required.
@@ -157,6 +158,12 @@ extern NSString *const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSString *machineID;
///
/// If YES, enables bundle detection for blocked events. This property is not stored on disk.
/// Its value is set by a sync server that supports bundles. Defaults to NO.
///
@property BOOL bundlesEnabled;
#pragma mark Server Auth Settings
///

View File

@@ -52,7 +52,8 @@ static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
@@ -100,12 +101,18 @@ 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 {
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
self.clientMode = SNTClientModeMonitor;
return SNTClientModeMonitor;
}
}
@@ -253,12 +260,22 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kServerAuthRootsFileKey];
}
- (NSDate *)syncLastSuccess {
return self.configData[kSyncLastSuccess];
- (NSDate *)fullSyncLastSuccess {
return self.configData[kFullSyncLastSuccess];
}
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
self.configData[kSyncLastSuccess] = syncLastSuccess;
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self saveConfigToDisk];
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
- (NSDate *)ruleSyncLastSuccess {
return self.configData[kRuleSyncLastSuccess];
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveConfigToDisk];
}
@@ -311,7 +328,14 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
- (void)reloadConfigData {
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:self.configFilePath]) return;
if (![fm fileExistsAtPath:self.configFilePath]) {
// As soon as saveConfigToDisk is called, reloadConfigData will be called again because
// of the SNTFileWatchers on the config path. No need to use dictionaryWithCapacity: here.
self.configData = [NSMutableDictionary dictionary];
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
[self saveConfigToDisk];
return;
};
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple function to check and drop root privileges.
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Represents a binary on disk, providing access to details about that binary
/// such as the SHA-1, SHA-256, Info.plist and the Mach-O data.
@@ -36,6 +38,18 @@
///
- (instancetype)initWithPath:(NSString *)path;
///
/// Initializer for already resolved paths.
///
/// @param path The path of the file this instance is to represent. The path will
/// not be converted and will be used as is. If the path is not a regular file this method will
/// return nil and fill in an error.
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
/// describing the problem.
///
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error;
///
/// @return Path of this file.
///
@@ -84,6 +98,11 @@
///
- (BOOL)isDylib;
///
/// @return YES if this file is a bundle executable (QuickLook/Spotlight plugin, etc.)
///
- (BOOL)isBundle;
///
/// @return YES if this file is a kernel extension.
///
@@ -109,6 +128,21 @@
///
- (BOOL)isMissingPageZero;
///
/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle
/// found in the tree. Defaults to NO, which uses the first found bundle, if any.
///
/// @example:
/// An SNTFileInfo object that represents
/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc
/// useAncestorBundle is set to YES
/// /Applications/Photos.app will be used to get data backing all the bundle methods
///
/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found.
/// Setting the useAncestorBundle propery will clear this cache and force a re-search.
///
@property(nonatomic) BOOL useAncestorBundle;
///
/// @return An NSBundle if this file is part of a bundle.
///

View File

@@ -59,23 +59,43 @@
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error {
self = [super init];
if (self) {
NSBundle *bndl;
_path = [self resolvePath:path bundle:&bndl];
_bundleRef = bndl;
if (_path.length == 0) {
_path = path;
if (!_path.length) {
if (error) {
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
NSString *errStr = @"Unable to use empty path";
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:260
code:270
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
struct stat fileStat;
lstat(_path.UTF8String, &fileStat);
if (!((S_IFMT & fileStat.st_mode) == S_IFREG)) {
if (error) {
NSString *errStr = [NSString stringWithFormat:@"Non regular file: %s", strerror(errno)];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:290
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
_fileSize = fileStat.st_size;
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
}
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (error) {
@@ -87,24 +107,29 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return nil;
}
_fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
struct stat fileStat;
fstat(_fileHandle.fileDescriptor, &fileStat);
_fileSize = fileStat.st_size;
if (_fileSize == 0) return nil;
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
_fileOwnerHomeDir = @(pwd->pw_dir);
}
}
}
return self;
}
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
NSBundle *bndl;
NSString *resolvedPath = [self resolvePath:path bundle:&bndl];
if (!resolvedPath.length) {
if (error) {
NSString *errStr = @"Unable to resolve empty path";
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:260
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
return nil;
}
self = [self initWithResolvedPath:resolvedPath error:error];
if (self && bndl) _bundleRef = bndl;
return self;
}
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path error:NULL];
}
@@ -196,22 +221,26 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return [self.machHeaders allKeys];
}
- (BOOL)isExecutable {
- (uint32_t)machFileType {
struct mach_header *mach_header = [self firstMachHeader];
if (mach_header && mach_header->filetype == MH_EXECUTE) return YES;
return NO;
if (mach_header) return mach_header->filetype;
return -1;
}
- (BOOL)isExecutable {
return [self machFileType] == MH_EXECUTE;
}
- (BOOL)isDylib {
struct mach_header *mach_header = [self firstMachHeader];
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
return NO;
return [self machFileType] == MH_DYLIB;
}
- (BOOL)isBundle {
return [self machFileType] == MH_BUNDLE;
}
- (BOOL)isKext {
struct mach_header *mach_header = [self firstMachHeader];
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
return NO;
return [self machFileType] == MH_KEXT_BUNDLE;
}
- (BOOL)isMachO {
@@ -276,34 +305,34 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
///
/// Rationale: An NSBundle has a method executablePath for discovering the main binary within a
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
/// Also a bundle can contain multiple binaries within the MacOS folder and we want any of these
/// Also a bundle can contain multiple binaries within its subdirectories and we want any of these
/// to count as being part of the bundle.
///
/// This method relies on executable bundles being laid out as follows:
/// This method walks up the path until a bundle is found, if any.
///
/// @code
/// Bundle.app/
/// Contents/
/// MacOS/
/// executable
/// @endcode
///
/// If @c self.path is the full path to @c executable above, this method would return an
/// NSBundle reference for Bundle.app.
/// @param ancestor YES this will return the highest NSBundle found in the tree. No will return the
/// the lowest.
///
-(NSBundle *)findBundleWithAncestor:(BOOL)ancestor {
NSBundle *bundle;
NSMutableArray *pathComponents = [[self.path pathComponents] mutableCopy];
// Ignore the root path "/", for some reason this is considered a bundle.
while (pathComponents.count > 1) {
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) {
bundle = bndl;
if (!ancestor) break;
}
[pathComponents removeLastObject];
}
return bundle;
}
- (NSBundle *)bundle {
if (!self.bundleRef) {
self.bundleRef = (NSBundle *)[NSNull null];
// Check that the full path is at least 4-levels deep:
// e.g: /Calendar.app/Contents/MacOS/Calendar
NSArray *pathComponents = [self.path pathComponents];
NSUInteger pathComponentsCount = pathComponents.count;
if (pathComponentsCount < 4) return nil;
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponentsCount - 3)];
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
self.bundleRef =
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
}
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
}
@@ -312,6 +341,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return [self.bundle bundlePath];
}
- (void)setUseAncestorBundle:(BOOL)useAncestorBundle {
if (self.useAncestorBundle != useAncestorBundle) {
self.bundleRef = nil;
self.infoDict = nil;
}
_useAncestorBundle = useAncestorBundle;
}
- (NSDictionary *)infoPlist {
if (!self.infoDict) {
NSDictionary *d = [self embeddedPlist];
@@ -397,7 +434,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
NSData *fatHeader = [self safeSubdataWithRange:range];
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
if (fatHeader && (fh->magic == FAT_CIGAM || fh->magic == FAT_MAGIC)) {
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
@@ -530,7 +567,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
/// is not the one who downloaded the file.
///
- (NSDictionary *)quarantineData {
if (!self.quarantineDict && self.fileOwnerHomeDir) {
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
self.quarantineDict = (NSDictionary *)[NSNull null];
NSURL *url = [NSURL fileURLWithPath:self.path];
@@ -634,7 +671,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
BOOL directory;
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
return nil;
} else if (directory) {
} else if (directory && ![path isEqualToString:@"/"]) {
NSBundle *bndl = [NSBundle bundleWithPath:path];
if (bundle) *bundle = bndl;
return [bndl executablePath];

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple file watching class using dispatch sources. Will automatically
/// reload the watch if the file is deleted and continue watching for

View File

@@ -18,7 +18,7 @@
@interface SNTFileWatcher ()
@property NSString *filePath;
@property(strong) void (^handler)(unsigned long);
@property(copy) void (^handler)(unsigned long);
@property dispatch_source_t source;
@end
@@ -52,7 +52,8 @@
dispatch_async(queue, ^{
int fd = -1;
while ((fd = open([self.filePath fileSystemRepresentation], O_EVTONLY | O_CLOEXEC)) < 0) {
const char *filePath = [self.filePath fileSystemRepresentation];
while ((fd = open(filePath, O_EVTONLY | O_CLOEXEC)) < 0) {
usleep(200000); // wait 200ms
}
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
@@ -76,9 +77,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 +86,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

@@ -34,6 +34,8 @@
#else // KERNEL
@import Foundation;
typedef enum : NSUInteger {
LOG_LEVEL_ERROR,
LOG_LEVEL_WARN,

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///
@@ -20,7 +22,7 @@
@interface SNTStoredEvent : NSObject<NSSecureCoding>
///
/// An index for this event, empty unless the event came from the database.
/// An index for this event, randomly generated during initialization.
///
@property NSNumber *idx;
@@ -34,6 +36,28 @@
///
@property NSString *filePath;
///
/// Set to YES if the event is a part of a bundle. When an event is passed to SantaGUI this propery
/// will be used as an indicator to to kick off bundle hashing as necessary. Default value is NO.
///
@property BOOL needsBundleHash;
///
/// If the executed file was part of a bundle, this is the calculated hash of all the nested
/// executables within the bundle.
///
@property NSString *fileBundleHash;
///
/// If the executed file was part of a bundle, this is the time in ms it took to hash the bundle.
///
@property NSNumber *fileBundleHashMilliseconds;
///
/// If the executed file was part of a bundle, this is the total count of related mach-o binaries.
///
@property NSNumber *fileBundleBinaryCount;
///
/// If the executed file was part of the bundle, this is the CFBundleDisplayName, if it exists
/// or the CFBundleName if not.
@@ -45,6 +69,11 @@
///
@property NSString *fileBundlePath;
///
/// The relative path to the bundle's main executable.
///
@property NSString *fileBundleExecutableRelPath;
///
/// If the executed file was part of the bundle, this is the CFBundleID.
///

View File

@@ -33,8 +33,13 @@
ENCODE(self.fileSHA256, @"fileSHA256");
ENCODE(self.filePath, @"filePath");
ENCODE(@(self.needsBundleHash), @"needsBundleHash");
ENCODE(self.fileBundleHash, @"fileBundleHash");
ENCODE(self.fileBundleHashMilliseconds, @"fileBundleHashMilliseconds");
ENCODE(self.fileBundleBinaryCount, @"fileBundleBinaryCount");
ENCODE(self.fileBundleName, @"fileBundleName");
ENCODE(self.fileBundlePath, @"fileBundlePath");
ENCODE(self.fileBundleExecutableRelPath, @"fileBundleExecutableRelPath");
ENCODE(self.fileBundleID, @"fileBundleID");
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
@@ -57,6 +62,14 @@
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
}
- (instancetype)init {
self = [super init];
if (self) {
_idx = @(arc4random());
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
@@ -64,8 +77,13 @@
_fileSHA256 = DECODE(NSString, @"fileSHA256");
_filePath = DECODE(NSString, @"filePath");
_needsBundleHash = [DECODE(NSNumber, @"needsBundleHash") boolValue];
_fileBundleHash = DECODE(NSString, @"fileBundleHash");
_fileBundleHashMilliseconds = DECODE(NSNumber, @"fileBundleHashMilliseconds");
_fileBundleBinaryCount = DECODE(NSNumber, @"fileBundleBinaryCount");
_fileBundleName = DECODE(NSString, @"fileBundleName");
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
_fileBundleExecutableRelPath = DECODE(NSString, @"fileBundleExecutableRelPath");
_fileBundleID = DECODE(NSString, @"fileBundleID");
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple class for fetching system information
///

View File

@@ -0,0 +1,57 @@
/// Copyright 2017 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 Foundation;
@class SNTStoredEvent;
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
@protocol SNTBundleServiceXPC
///
/// @param listener The listener to connect back to the SantaGUI.
///
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
/// failure or cancellation occurs.
///
/// @param event The event that includes the fileBundlePath to be hashed. This method will
/// attempt to to find and use the ancestor bundle as a starting point.
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
///
/// @note If there is a current NSProgress when called this method will report back its progress.
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
@end
@interface SNTXPCBundleServiceInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTBundleServiceXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning.
///
+ (NSXPCInterface *)bundleServiceInterface;
///
/// Returns the MachService ID for this service.
///
+ (NSString *)serviceId;
@end

View File

@@ -0,0 +1,36 @@
/// Copyright 2017 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 "SNTXPCBundleServiceInterface.h"
#import "SNTStoredEvent.h"
@implementation SNTXPCBundleServiceInterface
+ (NSXPCInterface *)bundleServiceInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
return r;
}
+ (NSString *)serviceId {
return @"com.google.santabs";
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
/**
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
validation of connecting clients and forced connection establishment.
@@ -63,13 +65,20 @@
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
/**
Initializer a new client to a service exported by a LaunchDaemon.
Initialize a new client to a service exported by a LaunchDaemon.
@param name MachService name
@param privileged Use YES if the server is running as root.
*/
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
/**
Initialize a new client to a service within a bundle.
@param name service name
*/
- (nullable instancetype)initClientWithServiceName:(nonnull NSString *)name;
/**
Initialize a new client with a listener endpoint sent from another process.

View File

@@ -95,6 +95,17 @@
return self;
}
- (instancetype)initClientWithServiceName:(NSString *)name {
self = [super init];
if (self) {
_currentConnection = [[NSXPCConnection alloc] initWithServiceName:name];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
@@ -113,8 +124,10 @@
// send a message to the listener to finish establishing the connection
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
self.currentConnection.remoteObjectInterface = self.validationInterface;
self.currentConnection.interruptionHandler = self.invalidationHandler;
self.currentConnection.invalidationHandler = self.invalidationHandler;
self.currentConnection.interruptionHandler = self.currentConnection.invalidationHandler = ^{
STRONGIFY(self);
if (self.invalidationHandler) self.invalidationHandler();
};
[self.currentConnection resume];
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
STRONGIFY(self);

View File

@@ -12,8 +12,14 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTRule;
@class SNTStoredEvent;
@@ -38,13 +44,28 @@
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
- (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 certificateSHA256 A SHA256 hash of the signing certificate, 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
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
@@ -54,16 +75,32 @@
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;
- (void)bundlesEnabled:(void (^)(BOOL))reply;
- (void)setBundlesEnabled:(BOOL)bundlesEnabled reply:(void (^)())reply;
///
/// GUI Ops
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)pushNotifications:(void (^)(BOOL))reply;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply;
///
/// Bundle Ops
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event reply:(SNTBundleHashBlock)reply;
- (void)syncBundleEvent:(SNTStoredEvent *)event relatedEvents:(NSArray<SNTStoredEvent *> *)events;
@end

View File

@@ -37,6 +37,16 @@
argumentIndex:0
ofReply:NO];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(hashBundleBinariesForEvent:reply:)
argumentIndex:1
ofReply:YES];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(syncBundleEvent:relatedEvents:)
argumentIndex:1
ofReply:NO];
return r;
}

View File

@@ -12,7 +12,10 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTStoredEvent;
@@ -20,6 +23,17 @@
@protocol SNTNotifierXPC
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
- (void)postClientModeNotification:(SNTClientMode)clientmode;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
@end
/// Protocol implemented by SantaGUI and utilized by santabs
@protocol SNTBundleNotifierXPC
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount
hashedCount:(uint64_t)hashedCount;
- (void)setBundleServiceListener:(NSXPCListenerEndpoint *)listener;
@end
@interface SNTXPCNotifierInterface : NSObject
@@ -30,4 +44,10 @@
///
+ (NSXPCInterface *)notifierInterface;
///
/// @return an initialized NSXPCInterface for the SNTBundleNotifierXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)bundleNotifierInterface;
@end

View File

@@ -20,4 +20,8 @@
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
}
+ (NSXPCInterface *)bundleNotifierInterface {
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTBundleNotifierXPC)];
}
@end

View File

@@ -0,0 +1,37 @@
/// 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 Foundation;
#import "SNTCommonEnums.h"
@class SNTStoredEvent;
/// Protocol implemented by santactl and utilized by santad
@protocol SNTSyncdXPC
- (void)postEventToSyncServer:(SNTStoredEvent *)event;
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event reply:(void (^)(BOOL))reply;
- (void)postBundleEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end
@interface SNTXPCSyncdInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)syncdInterface;
@end

View File

@@ -0,0 +1,32 @@
/// 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 "SNTXPCSyncdInterface.h"
#import "SNTStoredEvent.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTStoredEvent class], nil]
forSelector:@selector(postBundleEventsToSyncServer:)
argumentIndex:0
ofReply:NO];
return r;
}
@end

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
@@ -91,8 +88,9 @@ template<class T> class SantaCache {
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
if (entry->key == key) {
T val = entry->value;
unlock(bucket);
return entry->value;
return val;
}
entry = entry->next;
}
@@ -249,7 +247,7 @@ template<class T> class SantaCache {
/**
Holder for a 'zero' entry for the current type
*/
T zero_ = {};
const T zero_ = T();
/**
Special bucket used when automatically clearing due to size

View File

@@ -42,6 +42,9 @@ bool SantaDecisionManager::init() {
client_pid_ = 0;
ts_ = { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
return true;
}
@@ -202,10 +205,15 @@ void SantaDecisionManager::AddToCache(
if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
decision_cache_->remove(identifier);
}
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
decision_cache_->remove(identifier);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
uint64_t SantaDecisionManager::CacheCount() const {
@@ -231,13 +239,8 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
if (RESPONSE_VALID(result)) {
if (result == ACTION_RESPOND_DENY) {
auto diff_time = GetCurrentUptime();
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
}
if (decision_time < diff_time) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache_->remove(identifier);
return ACTION_UNSET;
}
@@ -250,6 +253,13 @@ santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
auto return_action = ACTION_UNSET;
#ifdef DEBUG
clock_sec_t secs = 0;
clock_usec_t microsecs = 0;
clock_get_system_microtime(&secs, &microsecs);
uint64_t uptime = (secs * 1000000) + microsecs;
#endif
// Wait for the daemon to respond or die.
do {
// Add pending request to cache, to be replaced by daemon with actual response
@@ -257,20 +267,13 @@ santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uin
// Send request to daemon...
if (!PostToDecisionQueue(message)) {
OSIncrementAtomic(&failed_decision_queue_requests_);
if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
LOGE("Failed to queue more than %d requests, killing daemon",
kMaxDecisionQueueFailures);
proc_signal(client_pid_, SIGKILL);
client_pid_ = 0;
}
LOGE("Failed to queue request for %s.", message->path);
RemoveFromCache(identifier);
return ACTION_ERROR;
}
do {
IOSleep(kRequestLoopSleepMilliseconds);
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
} while (!RESPONSE_VALID(return_action) && ClientConnected());
@@ -283,21 +286,36 @@ santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uin
return ACTION_ERROR;
}
#ifdef DEBUG
clock_get_system_microtime(&secs, &microsecs);
LOGD("Decision time: %4lldms (%s)",
(((secs * 1000000) + microsecs) - uptime) / 1000, message->path);
#endif
return return_action;
}
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id,
const char *vnode_id_str) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
const uint64_t vnode_id) {
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;
// 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
char path[MAXPATHLEN];
@@ -311,7 +329,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;
}
@@ -321,6 +339,14 @@ santa_action_t SantaDecisionManager::FetchDecision(
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
lck_mtx_lock(decision_dataqueue_lock_);
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (++failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
LOGE("Failed to queue more than %d decision requests, killing daemon",
kMaxDecisionQueueFailures);
proc_signal(client_pid_, SIGKILL);
client_pid_ = 0;
}
}
lck_mtx_unlock(decision_dataqueue_lock_);
return kr;
}
@@ -329,7 +355,7 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
lck_mtx_lock(log_dataqueue_lock_);
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
if (failed_log_queue_requests_++ == 0) {
LOGW("Dropping log queue messages");
}
// If enqueue failed, pop an item off the queue and try again.
@@ -337,7 +363,9 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
log_dataqueue_->dequeue(0, &dataSize);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
} else {
OSCompareAndSwap(1, 0, &failed_log_queue_requests_);
if (failed_log_queue_requests_ > 0) {
failed_log_queue_requests_--;
}
}
lck_mtx_unlock(log_dataqueue_lock_);
return kr;
@@ -359,16 +387,11 @@ 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 and convert it to a string.
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
char vnode_str[MAX_VNODE_ID_STR];
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Fetch decision
auto returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
auto returnedAction = FetchDecision(cred, vp, vnode_id);
// If file has dirty blocks, remove from cache and deny. This would usually
// be the case if a file has been written to and flushed but not yet
@@ -404,14 +427,14 @@ 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);
vfs_context_rele(context);
if (action == KAUTH_FILEOP_CLOSE) {
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
RemoveFromCache(vnode_id);
} else if (action == KAUTH_FILEOP_EXEC) {
auto message = NewMessage(nullptr);
@@ -432,9 +455,7 @@ void SantaDecisionManager::FileOpCallback(
// Filter out modifications to locations that are definitely
// not useful or made by santad.
if (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));
@@ -474,6 +495,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;
@@ -505,24 +531,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

@@ -52,8 +52,8 @@ class SantaDecisionManager : public OSObject {
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the logging queue.
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the logging queue.
*/
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
@@ -131,10 +131,10 @@ class SantaDecisionManager : public OSObject {
protected:
/**
While waiting for a response from the daemon, this is the number of
While waiting for a response from the daemon, this is the maximum number of
milliseconds to sleep for before checking the cache for a response.
*/
static const uint32_t kRequestLoopSleepMilliseconds = 10;
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
/// The maximum number of milliseconds a cached deny message should be considered valid.
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
@@ -149,7 +149,7 @@ class SantaDecisionManager : public OSObject {
static const uint32_t kMaxDecisionQueueEvents = 512;
/// The maximum number of messages can be kept in the logging data queue at any time.
static const uint32_t kMaxLogQueueEvents = 1024;
static const uint32_t kMaxLogQueueEvents = 2048;
/**
Fetches a response from the daemon. Handles both daemon death
@@ -170,10 +170,10 @@ class SantaDecisionManager : public OSObject {
@param cred The credential for this request.
@param vp The Vnode for this request.
@param vnode_id The ID for this vnode.
@param vnode_id_str A string representation of the above ID.
@return santa_action_t The response for this request
*/
santa_action_t FetchDecision(
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id, const char *vnode_id_str);
const kauth_cred_t cred, const vnode_t vp, const uint64_t vnode_id);
/**
Posts the requested message to the decision data queue.
@@ -256,8 +256,8 @@ class SantaDecisionManager : public OSObject {
IOSharedDataQueue *decision_dataqueue_;
IOSharedDataQueue *log_dataqueue_;
int32_t failed_decision_queue_requests_;
int32_t failed_log_queue_requests_;
uint32_t failed_decision_queue_requests_;
uint32_t failed_log_queue_requests_;
int32_t listener_invocations_;
@@ -265,6 +265,8 @@ class SantaDecisionManager : public OSObject {
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;
struct timespec ts_;
};
/**

View File

@@ -110,97 +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);
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);
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(
@@ -212,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

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>santabs</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>XPCService</key>
<dict>
<key>ServiceType</key>
<string>Application</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
/// Copyright 2017 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 Foundation;
#import "SNTXPCBundleServiceInterface.h"
@interface SNTBundleService : NSObject<SNTBundleServiceXPC>
@end

View File

@@ -0,0 +1,303 @@
/// Copyright 2017 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 "SNTBundleService.h"
#import <CommonCrypto/CommonDigest.h>
#import <pthread/pthread.h>
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import "SNTFileInfo.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTBundleService ()
@property SNTXPCConnection *notifierConnection;
@property SNTXPCConnection *listener;
@property(nonatomic) dispatch_queue_t queue;
@end
@implementation SNTBundleService
- (instancetype)init {
self = [super init];
if (self) {
_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
}
return self;
}
#pragma mark Connection handling
// Create a listener for SantaGUI to connect
- (void)createConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Create listener for return connection from SantaGUI.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
self.listener.exportedObject = self;
self.listener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
// Exit when SantaGUI is done with us.
self.listener.invalidationHandler = ^{
exit(0);
};
[self.listener resume];
// Tell SantaGUI to connect back to the above listener.
[[self.notifierConnection remoteObjectProxy] setBundleServiceListener:listener.endpoint];
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self attemptReconnection];
}
}
- (void)attemptReconnection {
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
}
#pragma mark SNTBundleServiceXPC Methods
// Connect to the SantaGUI
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCNotifierInterface bundleNotifierInterface];
[c resume];
self.notifierConnection = c;
dispatch_async(self.queue, ^{
[self createConnection];
});
}
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
reply:(SNTBundleHashBlock)reply {
NSProgress *progress =
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:100] : nil;
NSDate *startTime = [NSDate date];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(self.queue, ^{
// Use the highest bundle we can find. Save and reuse the bundle infomation when creating
// the related binary events.
SNTFileInfo *b = [[SNTFileInfo alloc] initWithPath:event.fileBundlePath];
b.useAncestorBundle = YES;
event.fileBundlePath = b.bundlePath;
event.fileBundleID = b.bundleIdentifier;
event.fileBundleName = b.bundleName;
event.fileBundleVersion = b.bundleVersion;
event.fileBundleVersionString = b.bundleShortVersionString;
// For most apps this should be "Contents/MacOS/AppName"
if (b.bundle.executablePath.length > b.bundlePath.length) {
event.fileBundleExecutableRelPath =
[b.bundle.executablePath substringFromIndex:b.bundlePath.length + 1];
}
NSDictionary *relatedEvents = [self findRelatedBinaries:event progress:progress];
NSString *bundleHash = [self calculateBundleHashFromSHA256Hashes:relatedEvents.allKeys
progress:progress];
NSNumber *ms = [NSNumber numberWithDouble:[startTime timeIntervalSinceNow] * -1000.0];
reply(bundleHash, relatedEvents.allValues, ms);
dispatch_semaphore_signal(sema);
});
// Master timeout of 10 min. Don't block the calling thread. NSProgress updates will be coming
// in over this thread.
dispatch_async(self.queue, ^{
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 600 * NSEC_PER_SEC))) {
[progress cancel];
}
});
}
#pragma mark Internal Methods
/**
Find binaries within a bundle given the bundle's event. It will run until a timeout occurs,
or until the NSProgress is cancelled. Search is done within the bundle concurrently.
@param event The SNTStoredEvent to begin searching.
@return An NSDictionary object with keys of fileSHA256 and values of SNTStoredEvent objects.
*/
- (NSDictionary *)findRelatedBinaries:(SNTStoredEvent *)event progress:(NSProgress *)progress {
// Find all files and folders within the fileBundlePath
NSFileManager *fm = [NSFileManager defaultManager];
NSArray *subpaths = [fm subpathsOfDirectoryAtPath:event.fileBundlePath error:NULL];
// This array is used to store pointers to executable SNTFileInfo objects. There will be one block
// dispatched per file in dirEnum. These blocks will write pointers to this array concurrently.
// No locks are used since every file has a slot.
//
// Xcode.app has roughly 500k files, 8bytes per pointer is ~4MB for this array. This size to space
// ratio seems appropriate as Xcode.app is in the upper bounds of bundle size.
__block void **fis = calloc(subpaths.count, sizeof(void *));
// Counts used as additional progress information in SantaGUI
__block volatile int64_t binaryCount = 0;
__block volatile int64_t sentBinaryCount = 0;
// Account for 80% of the work
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:80];
p = [NSProgress progressWithTotalUnitCount:subpaths.count * 100];
}
// Dispatch a block for every file in dirEnum.
dispatch_apply(subpaths.count, self.queue, ^(size_t i) {
@autoreleasepool {
if (progress.isCancelled) return;
dispatch_sync(dispatch_get_main_queue(), ^{
p.completedUnitCount++;
if (progress && ((i % 500) == 0 || binaryCount > sentBinaryCount)) {
sentBinaryCount = binaryCount;
[[self.notifierConnection remoteObjectProxy] updateCountsForEvent:event
binaryCount:binaryCount
fileCount:i
hashedCount:0];
}
});
NSString *subpath = subpaths[i];
NSString *file =
[event.fileBundlePath stringByAppendingPathComponent:subpath].stringByStandardizingPath;
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithResolvedPath:file error:NULL];
if (!fi.isExecutable) return;
fis[i] = (__bridge_retained void *)fi;
OSAtomicIncrement64Barrier(&binaryCount);
}
});
[progress resignCurrent];
NSMutableArray *fileInfos = [NSMutableArray arrayWithCapacity:binaryCount];
for (NSUInteger i = 0; i < subpaths.count; i++) {
if (fis[i]) [fileInfos addObject:(__bridge_transfer SNTFileInfo *)fis[i]];
}
free(fis);
return [self generateEventsFromBinaries:fileInfos blockingEvent:event progress:progress];
}
- (NSDictionary *)generateEventsFromBinaries:(NSArray *)fis
blockingEvent:(SNTStoredEvent *)event
progress:(NSProgress *)progress {
if (progress.isCancelled) return nil;
NSMutableDictionary *relatedEvents = [NSMutableDictionary dictionaryWithCapacity:fis.count];
// Account for 15% of the work
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:15];
p = [NSProgress progressWithTotalUnitCount:fis.count * 100];
}
dispatch_apply(fis.count, self.queue, ^(size_t i) {
@autoreleasepool {
if (progress.isCancelled) return;
SNTFileInfo *fi = fis[i];
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.filePath = fi.path;
se.fileSHA256 = fi.SHA256;
se.occurrenceDate = [NSDate distantFuture];
se.decision = SNTEventStateBundleBinary;
se.fileBundlePath = event.fileBundlePath;
se.fileBundleExecutableRelPath = event.fileBundleExecutableRelPath;
se.fileBundleID = event.fileBundleID;
se.fileBundleName = event.fileBundleName;
se.fileBundleVersion = event.fileBundleVersion;
se.fileBundleVersionString = event.fileBundleVersionString;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
se.signingChain = cs.certificates;
dispatch_sync(dispatch_get_main_queue(), ^{
relatedEvents[se.fileSHA256] = se;
p.completedUnitCount++;
if (progress) {
[[self.notifierConnection remoteObjectProxy] updateCountsForEvent:event
binaryCount:fis.count
fileCount:0
hashedCount:i];
}
});
}
});
[progress resignCurrent];
return relatedEvents;
}
- (NSString *)calculateBundleHashFromSHA256Hashes:(NSArray *)hashes
progress:(NSProgress *)progress {
if (!hashes.count) return nil;
// Account for 5% of the work
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:5];
p = [NSProgress progressWithTotalUnitCount:5 * 100];
}
NSMutableArray *sortedHashes = [hashes mutableCopy];
[sortedHashes sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
NSString *sha256Hashes = [sortedHashes componentsJoinedByString:@""];
CC_SHA256_CTX c256;
CC_SHA256_Init(&c256);
CC_SHA256_Update(&c256, (const void *)sha256Hashes.UTF8String, (CC_LONG)sha256Hashes.length);
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
NSString *sha256 = [[NSString alloc] initWithFormat:SHA256FormatString,
digest[0], digest[1], digest[2], digest[3],
digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11],
digest[12], digest[13], digest[14], digest[15],
digest[16], digest[17], digest[18], digest[19],
digest[20], digest[21], digest[22], digest[23],
digest[24], digest[25], digest[26], digest[27],
digest[28], digest[29], digest[30], digest[31]];
p.completedUnitCount++;
[progress resignCurrent];
return sha256;
}
@end

27
Source/santabs/main.m Normal file
View File

@@ -0,0 +1,27 @@
/// Copyright 2017 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 Foundation;
#import "SNTBundleService.h"
#import "SNTXPCBundleServiceInterface.h"
#import "SNTXPCConnection.h"
int main(int argc, const char *argv[]) {
SNTXPCConnection *c =
[[SNTXPCConnection alloc] initServerWithListener:[NSXPCListener serviceListener]];
c.exportedInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
c.exportedObject = [[SNTBundleService alloc] init];
[c resume];
}

View File

@@ -0,0 +1,79 @@
/// Copyright 2017 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 "SNTCommandController.h"
#import "SNTFileInfo.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandBundleInfo : NSObject<SNTCommand>
@end
@implementation SNTCommandBundleInfo
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"bundleinfo")
#endif
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Searches a bundle for binaries";
}
+ (NSString *)longHelpText {
return @"Searches a bundle for binaries";
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
NSError *error;
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:arguments.firstObject error:&error];
if (!fi) {
printf("%s\n", error.description.UTF8String);
exit(1);
} else if (!fi.bundle) {
printf("Not a bundle\n");
exit(2);
}
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.fileBundlePath = fi.bundlePath;
[[daemonConn remoteObjectProxy] hashBundleBinariesForEvent:se
reply:^(NSString *hash,
NSArray<SNTStoredEvent *> *events,
NSNumber *time) {
printf("Hashing time: %llu ms\n", time.unsignedLongLongValue);
printf("%lu events found\n", events.count);
printf("BundleHash: %s\n", hash.UTF8String);
for (SNTStoredEvent *event in events) {
printf("BundleID: %s \n\tSHA-256: %s \n\tPath: %s\n",
event.fileBundleID.UTF8String, event.fileSHA256.UTF8String, event.filePath.UTF8String);
}
exit(0);
}];
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTLogging.h"

View File

@@ -12,24 +12,362 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#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"
// file info keys
static NSString *const kPath = @"Path";
static NSString *const kBundleName = @"Bundle Name";
static NSString *const kBundleVersion = @"Bundle Version";
static NSString *const kBundleVersionStr = @"Bundle Version Str";
static NSString *const kDownloadReferrerURL = @"Download Referrer URL";
static NSString *const kDownloadURL = @"Download URL";
static NSString *const kDownloadTimestamp = @"Download Timestamp";
static NSString *const kDownloadAgent = @"Download Agent";
static NSString *const kType = @"Type";
static NSString *const kPageZero = @"Page Zero";
static NSString *const kCodeSigned = @"Code-signed";
static NSString *const kRule = @"Rule";
static NSString *const kSigningChain = @"Signing Chain";
// signing chain keys
static NSString *const kCommonName = @"Common Name";
static NSString *const kOrganization = @"Organization";
static NSString *const kOrganizationalUnit = @"Organizational Unit";
static NSString *const kValidFrom = @"Valid From";
static NSString *const kValidUntil = @"Valid Until";
// shared file info & signing chain keys
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>
@property(nonatomic) SNTXPCConnection *daemonConn;
@property(nonatomic) SNTFileInfo *fileInfo;
@property(nonatomic) MOLCodesignChecker *csc;
// file path used for object initialization
@property(readonly, nonatomic) NSString *filePath;
// Block type to be used with propertyMap values
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *);
// on read generated properties
@property(readonly, copy, nonatomic) SNTAttributeBlock path;
@property(readonly, copy, nonatomic) SNTAttributeBlock sha256;
@property(readonly, copy, nonatomic) SNTAttributeBlock sha1;
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleName;
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleVersion;
@property(readonly, copy, nonatomic) SNTAttributeBlock bundleShortVersionString;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadReferrerURL;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadURL;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
@property(readonly, copy, nonatomic) SNTAttributeBlock rule;
@property(readonly, copy, nonatomic) SNTAttributeBlock signingChain;
// Mapping between property string keys and SNTAttributeBlocks
@property(nonatomic) NSMutableDictionary<NSString *, SNTAttributeBlock> *propertyMap;
// Common Date Formatter
@property(nonatomic) NSDateFormatter *dateFormatter;
// Block Helpers
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi;
@end
@implementation SNTCommandFileInfo
REGISTER_COMMAND_NAME(@"fileinfo")
- (instancetype)initWithFilePath:(NSString *)filePath
daemonConnection:(SNTXPCConnection *)daemonConn {
self = [super init];
if (self) {
_filePath = filePath;
_daemonConn = daemonConn;
_dateFormatter = [[NSDateFormatter alloc] init];
_dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
_propertyMap = @{ kPath : self.path,
kSHA256 : self.sha256,
kSHA1 : self.sha1,
kBundleName : self.bundleName,
kBundleVersion : self.bundleVersion,
kBundleVersionStr : self.bundleVersionStr,
kDownloadReferrerURL : self.downloadReferrerURL,
kDownloadURL : self.downloadURL,
kDownloadTimestamp : self.downloadTimestamp,
kDownloadAgent : self.downloadAgent,
kType : self.type,
kPageZero : self.pageZero,
kCodeSigned : self.codeSigned,
kRule : self.rule,
kSigningChain : self.signingChain }.mutableCopy;
}
return self;
}
#pragma mark property getters
- (SNTFileInfo *)fileInfo {
if (!_fileInfo) {
_fileInfo = [[SNTFileInfo alloc] initWithPath:self.filePath];
if (!_fileInfo) {
fprintf(stderr, "\rInvalid or empty file: %s\n", self.filePath.UTF8String);
}
}
return _fileInfo;
}
- (SNTAttributeBlock)path {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.path;
};
}
- (SNTAttributeBlock)sha256 {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.SHA256;
};
}
- (SNTAttributeBlock)sha1 {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.SHA1;
};
}
- (SNTAttributeBlock)bundleName {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.bundleName;
};
}
- (SNTAttributeBlock)bundleVersion {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.bundleVersion;
};
}
- (SNTAttributeBlock)bundleVersionStr {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.bundleShortVersionString;
};
}
- (SNTAttributeBlock)downloadReferrerURL {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.quarantineRefererURL;
};
}
- (SNTAttributeBlock)downloadURL {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.quarantineDataURL;
};
}
- (SNTAttributeBlock)downloadTimestamp {
return ^id (SNTCommandFileInfo *fi) {
return [fi.dateFormatter stringFromDate:fi.fileInfo.quarantineTimestamp];
};
}
- (SNTAttributeBlock)downloadAgent {
return ^id (SNTCommandFileInfo *fi) {
return fi.fileInfo.quarantineAgentBundleID;
};
}
- (SNTAttributeBlock)type {
return ^id (SNTCommandFileInfo *fi) {
NSArray *archs = [fi.fileInfo architectures];
if (archs.count == 0) {
return [fi humanReadableFileType:fi.fileInfo];
}
return [NSString stringWithFormat:@"%@ (%@)",
[fi humanReadableFileType:fi.fileInfo], [archs componentsJoinedByString:@", "]];
};
}
- (SNTAttributeBlock)pageZero {
return ^id (SNTCommandFileInfo *fi) {
if ([fi.fileInfo isMissingPageZero]) {
return @"__PAGEZERO segment missing/bad!";
}
return nil;
};
}
- (SNTAttributeBlock)codeSigned {
return ^id (SNTCommandFileInfo *fi) {
NSError *error;
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
if (error) {
switch (error.code) {
case errSecCSUnsigned:
return @"No";
case errSecCSSignatureFailed:
case errSecCSStaticCodeChanged:
case errSecCSSignatureNotVerifiable:
case errSecCSSignatureUnsupported:
return @"Yes, but code/signature changed/unverifiable";
case errSecCSResourceDirectoryFailed:
case errSecCSResourceNotSupported:
case errSecCSResourceRulesInvalid:
case errSecCSResourcesInvalid:
case errSecCSResourcesNotFound:
case errSecCSResourcesNotSealed:
return @"Yes, but resources invalid";
case errSecCSReqFailed:
case errSecCSReqInvalid:
case errSecCSReqUnsupported:
return @"Yes, but failed requirement validation";
case errSecCSInfoPlistFailed:
return @"Yes, but can't validate as Info.plist is missing";
default: {
return [NSString stringWithFormat:@"Yes, but failed to validate (%ld)", error.code];
}
}
} else if (fi.csc.signatureFlags & kSecCodeSignatureAdhoc) {
return @"Yes, but ad-hoc";
} else {
return @"Yes";
}
};
}
- (SNTAttributeBlock)rule {
return ^id (SNTCommandFileInfo *fi) {
__block SNTEventState s;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[fi.daemonConn resume];
});
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if (!fi.csc) {
NSError *error;
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path(fi) error:&error];
}
[[fi.daemonConn remoteObjectProxy] decisionForFilePath:fi.path(fi)
fileSHA256:fi.propertyMap[kSHA256](fi)
certificateSHA256:fi.csc.leafCertificate.SHA256
reply:^(SNTEventState state) {
if (state) s = state;
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
return @"Cannot communicate with daemon";
} else {
NSMutableString *output =
(SNTEventStateAllow & s) ? @"Whitelisted".mutableCopy : @"Blacklisted".mutableCopy;
switch (s) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown:
[output appendString:@" (Unknown)"];
break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary:
[output appendString:@" (Binary)"];
break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
default:
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;
}
};
}
- (SNTAttributeBlock)signingChain {
return ^id (SNTCommandFileInfo *fi) {
if (!fi.csc) {
NSError *error;
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
}
if (fi.csc.certificates.count) {
NSMutableArray *certs = [[NSMutableArray alloc] initWithCapacity:fi.csc.certificates.count];
[fi.csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c, unsigned long idx,
BOOL *stop) {
[certs addObject:@{ kSHA256 : c.SHA256 ?: @"null",
kSHA1 : c.SHA1 ?: @"null",
kCommonName : c.commonName ?: @"null",
kOrganization : c.orgName ?: @"null",
kOrganizationalUnit : c.orgUnit ?: @"null",
kValidFrom : [fi.dateFormatter stringFromDate:c.validFrom] ?: @"null",
kValidUntil : [fi.dateFormatter stringFromDate:c.validUntil]
?: @"null"
}];
}];
return certs;
}
return nil;
};
}
- (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
if ([fi isScript]) return @"Script";
if ([fi isExecutable]) return @"Executable";
if ([fi isDylib]) return @"Dynamic Library";
if ([fi isBundle]) return @"Bundle/Plugin";
if ([fi isKext]) return @"Kernel Extension";
if ([fi isXARArchive]) return @"XAR Archive";
if ([fi isDMG]) return @"Disk Image";
return @"Unknown";
}
#pragma mark SNTCommand protocol methods
+ (BOOL)requiresRoot {
return NO;
}
@@ -43,190 +381,251 @@ REGISTER_COMMAND_NAME(@"fileinfo")
}
+ (NSString *)longHelpText {
return (@"The details provided will be the same ones Santa uses to make a decision\n"
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
@"the type of file.");
return [NSString stringWithFormat:
@"The details provided will be the same ones Santa uses to make a decision\n"
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
@"the type of file."
@"\n"
@"Usage: santactl fileinfo [options] [file-paths]\n"
@" --json: output in json format\n"
@" --key: search and return this one piece of information\n"
@" valid Keys:\n"
@"%@\n"
@" valid keys when using --cert-index:\n"
@"%@\n"
@" --cert-index: an integer corresponding to a certificate of the signing chain\n"
@" 1 for the leaf certificate\n"
@" -1 for the root certificate\n"
@" 2 and up for the intermediates / root\n"
@"\n"
@"Examples: santactl fileinfo --cert-index 1 --key SHA-256 --json /usr/bin/yes\n"
@" santactl fileinfo --key SHA-256 --json /usr/bin/yes\n"
@" santactl fileinfo /usr/bin/yes /bin/*\n",
[self printKeyArray:[self fileInfoKeys]],
[self printKeyArray:[self signingChainKeys]]];
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
NSString *filePath = [arguments firstObject];
if (!arguments.count) [self printErrorUsageAndExit:@"No arguments"];
if (!filePath) {
printf("Missing file path\n");
exit(1);
}
NSString *key;
NSNumber *certIndex;
NSArray *filePaths;
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
if (!fileInfo) {
printf("Invalid or empty file\n");
exit(1);
}
[self parseArguments:arguments
forKey:&key
certIndex:&certIndex
jsonOutput:&json
filePaths:&filePaths];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
// 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);
if (isatty(STDOUT_FILENO)) printf("Hashing...");
NSString *sha1, *sha256;
[fileInfo hashSHA1:&sha1 SHA256:&sha256];
if (isatty(STDOUT_FILENO)) printf("\r");
__block NSOperationQueue *hashQueue = [[NSOperationQueue alloc] init];
hashQueue.maxConcurrentOperationCount = 15;
[self printKey:@"Path" value:fileInfo.path];
[self printKey:@"SHA-256" value:sha256];
[self printKey:@"SHA-1" value:sha1];
__block NSUInteger hashed = 0;
if (fileInfo.bundlePath) {
[self printKey:@"Bundle Name" value:fileInfo.bundleName];
[self printKey:@"Bundle Version" value:fileInfo.bundleVersion];
[self printKey:@"Bundle Version Str" value:fileInfo.bundleShortVersionString];
}
[filePaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSBlockOperation *hashOperation = [NSBlockOperation blockOperationWithBlock:^{
if (PrettyOutput()) printf("\rCalculating %lu/%lu", ++hashed, filePaths.count);
if (fileInfo.quarantineDataURL) {
[self printKey:@"Download Referer URL" value:fileInfo.quarantineRefererURL];
[self printKey:@"Download URL" value:fileInfo.quarantineDataURL];
[self printKey:@"Download Timestamp"
value:[dateFormatter stringFromDate:fileInfo.quarantineTimestamp]];
[self printKey:@"Download Agent" value:fileInfo.quarantineAgentBundleID];
}
SNTCommandFileInfo *fi = [[self alloc] initWithFilePath:obj daemonConnection:daemonConn];
if (!fi.fileInfo) return;
NSArray *archs = [fileInfo architectures];
if (archs.count == 0) {
[self printKey:@"Type" value:[self humanReadableFileType:fileInfo]];
exit(0);
}
__block NSMutableDictionary *outputHash = [[NSMutableDictionary alloc] init];
NSString *s = [NSString stringWithFormat:@"%@ (%@)",
[self humanReadableFileType:fileInfo],
[archs componentsJoinedByString:@", "]];
[self printKey:@"Type" value:s];
if ([fileInfo isMissingPageZero]) {
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
}
// Code signature state
NSError *error;
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
if (error) {
switch (error.code) {
case errSecCSUnsigned:
[self printKey:@"Code-signed" value:@"No"];
break;
case errSecCSSignatureFailed:
case errSecCSStaticCodeChanged:
case errSecCSSignatureNotVerifiable:
case errSecCSSignatureUnsupported:
[self printKey:@"Code-signed" value:@"Yes, but code/signature changed/unverifiable"];
break;
case errSecCSResourceDirectoryFailed:
case errSecCSResourceNotSupported:
case errSecCSResourceRulesInvalid:
case errSecCSResourcesInvalid:
case errSecCSResourcesNotFound:
case errSecCSResourcesNotSealed:
[self printKey:@"Code-signed" value:@"Yes, but resources invalid"];
break;
case errSecCSReqFailed:
case errSecCSReqInvalid:
case errSecCSReqUnsupported:
[self printKey:@"Code-signed" value:@"Yes, but failed requirement validation"];
break;
case errSecCSInfoPlistFailed:
[self printKey:@"Code-signed" value:@"Yes, but can't validate as Info.plist is missing"];
break;
default: {
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
error.code];
[self printKey:@"Code-signed" value:val];
break;
if (key && !certIndex) {
SNTAttributeBlock block = fi.propertyMap[key];
outputHash[key] = block(fi);
} else if (certIndex) {
NSArray *signingChain = fi.signingChain(fi);
if (key) {
if ([certIndex isEqual:@(-1)]) {
outputHash[key] = signingChain.lastObject[key];
} else {
if (certIndex.unsignedIntegerValue - 1 < signingChain.count) {
outputHash[key] = signingChain[certIndex.unsignedIntegerValue - 1][key];
}
}
} else {
if ([certIndex isEqual:@(-1)]) {
outputHash[kSigningChain] = @[ signingChain.lastObject ?: @{} ];
} else {
NSMutableArray *indexedCert = [NSMutableArray arrayWithCapacity:signingChain.count];
[signingChain enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (certIndex.unsignedIntegerValue - 1 == idx) {
[indexedCert addObject:obj];
} else {
[indexedCert addObject:[NSNull null]];
}
}];
if (indexedCert.count) outputHash[kSigningChain] = indexedCert;
}
}
} 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 = obj;
outputHash[key] = block(fi);
}];
}
if (outputHash.count) {
dispatch_group_async(outputHashesGroup, outputHashesQueue, ^{
[outputHashes addObject:outputHash];
});
}
}
} else if (csc.signatureFlags & kSecCodeSignatureAdhoc) {
[self printKey:@"Code-signed" value:@"Yes, but ad-hoc"];
} else {
[self printKey:@"Code-signed" value:@"Yes"];
}
// Binary rule state
__block SNTRule *r;
dispatch_group_t group = dispatch_group_create();
[daemonConn resume];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseBinaryRuleForSHA256:sha256 reply:^(SNTRule *rule) {
if (rule) r = rule;
dispatch_group_leave(group);
}];
NSString *leafCertSHA = [[csc.certificates firstObject] SHA256];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseCertificateRuleForSHA256:leafCertSHA
reply:^(SNTRule *rule) {
if (!r && rule) r = rule;
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self printKey:@"Rule" value:@"Cannot communicate with daemon"];
} else {
NSString *output;
switch (r.state) {
case SNTRuleStateWhitelist:
output = @"Whitelisted";
if (isatty(STDOUT_FILENO)) {
output = @"\033[32mWhitelisted\033[0m";
}
[self printKey:@"Rule" value:output];
break;
case SNTRuleStateBlacklist:
case SNTRuleStateSilentBlacklist:
output = @"Blacklisted";
if (isatty(STDOUT_FILENO)) {
output = @"\033[31mBlacklisted\033[0m";
}
[self printKey:@"Rule" value:output];
break;
default:
output = @"None";
if (isatty(STDOUT_FILENO)) {
output = @"\033[33mNone\033[0m";
}
[self printKey:@"Rule" value:output];
}
}
// Signing chain
if (csc.certificates.count) {
printf("Signing chain:\n");
[csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c,
unsigned long idx,
BOOL *stop) {
printf(" %2lu. %-20s: %s\n", idx + 1, "SHA-256", [c.SHA256 UTF8String]);
printf(" %-20s: %s\n", "SHA-1", [c.SHA1 UTF8String]);
printf(" %-20s: %s\n", "Common Name", [c.commonName UTF8String]);
printf(" %-20s: %s\n", "Organization", [c.orgName UTF8String]);
printf(" %-20s: %s\n", "Organizational Unit", [c.orgUnit UTF8String]);
printf(" %-20s: %s\n", "Valid From",
[[dateFormatter stringFromDate:c.validFrom] UTF8String]);
printf(" %-20s: %s\n", "Valid Until",
[[dateFormatter stringFromDate:c.validUntil] UTF8String]);
printf("\n");
}];
}
hashOperation.qualityOfService = NSQualityOfServiceUserInitiated;
[hashQueue addOperation:hashOperation];
}];
// Wait for all the calculating threads to finish
[hashQueue waitUntilAllOperationsAreFinished];
// 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);
}
+ (void)printKey:(NSString *)key value:(NSString *)value {
if (!key || !value) return;
#pragma mark FileInfo helper methods
+ (NSArray *)fileInfoKeys {
return @[ kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr,
kDownloadReferrerURL, kDownloadURL, kDownloadTimestamp, kDownloadAgent,
kType, kPageZero, kCodeSigned, kRule, kSigningChain ];
}
+ (NSArray *)signingChainKeys {
return @[ kSHA256, kSHA1, kCommonName, kOrganization, kOrganizationalUnit, kValidFrom,
kValidUntil ];
}
+ (NSString *)printKeyArray:(NSArray *)array {
__block NSMutableString *string = [[NSMutableString alloc] init];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[string appendString:[NSString stringWithFormat:@" \"%@\"\n", obj]];
}];
return string;
}
+ (void)printErrorUsageAndExit:(NSString *)error {
printf("%s\n\n", [error UTF8String]);
printf("%s\n", [[self longHelpText] UTF8String]);
exit(1);
}
+ (void)parseArguments:(NSArray *)args
forKey:(NSString **)key
certIndex:(NSNumber **)certIndex
jsonOutput:(BOOL *)jsonOutput
filePaths:(NSArray **)filePaths {
__block NSMutableArray *paths = [[NSMutableArray alloc] init];
[args enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
if ([obj caseInsensitiveCompare:@"--json"] == NSOrderedSame) {
*jsonOutput = YES;
} else if ([obj caseInsensitiveCompare:@"--cert-index"] == NSOrderedSame) {
if (++idx > args.count - 1 || [args[idx] hasPrefix:@"--"]) {
[self printErrorUsageAndExit:@"\n--cert-index requires an argument"];
}
*certIndex = @([args[idx] integerValue]);
} else if ([obj caseInsensitiveCompare:@"--key"] == NSOrderedSame) {
if (++idx > args.count - 1 || [args[idx] hasPrefix:@"--"]) {
[self printErrorUsageAndExit:@"\n--key requires an argument"];
}
*key = args[idx];
} else if ([@([obj integerValue]) isEqual:*certIndex] || [obj isEqual:*key]) {
return;
} else {
[paths addObject:args[idx]];
}
}];
if (*key && !*certIndex && ![self.fileInfoKeys containsObject:*key]) {
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid key", *key]];
} else if (*key && *certIndex && ![self.signingChainKeys containsObject:*key]) {
[self printErrorUsageAndExit:
[NSString stringWithFormat:@"\n\"%@\" is an invalid key when using --cert-index", *key]];
} else if ([@(0) isEqual:*certIndex]) {
[self printErrorUsageAndExit:@"\n0 is an invalid --cert-index\n --cert-index is 1 indexed"];
}
if (!paths.count) [self printErrorUsageAndExit:@"\nat least one file-path is needed"];
*filePaths = paths.copy;
}
+ (void)printOutputHashes:(NSArray *)outputHashes {
if (json) {
id object = (outputHashes.count > 1) ? outputHashes : outputHashes.firstObject;
if (!object) return;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
options:NSJSONWritingPrettyPrinted
error:NULL];
printf("%s\n", [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding].UTF8String);
return;
}
[outputHashes enumerateObjectsUsingBlock:^(id outputHash, NSUInteger idx, BOOL *stop) {
if ([outputHash count] == 1) {
return [self printValueFromOutputHash:outputHash];
}
[self.fileInfoKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) {
[self printValueForKey:key fromOutputHash:outputHash];
}];
printf("\n");
}];
}
+ (void)printValueForKey:(NSString *)key fromOutputHash:(NSDictionary *)outputHash {
id value = outputHash[key];
if (!value) return;
if ([key isEqualToString:kSigningChain]) {
return [self printSigningChain:value];
}
printf("%-21s: %s\n", [key UTF8String], [value UTF8String]);
}
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
if ([fi isScript]) return @"Script";
if ([fi isExecutable]) return @"Executable";
if ([fi isDylib]) return @"Dynamic Library";
if ([fi isKext]) return @"Kernel Extension";
if ([fi isXARArchive]) return @"XAR Archive";
if ([fi isDMG]) return @"Disk Image";
return @"Unknown";
+ (void)printValueFromOutputHash:(NSDictionary *)outputHash {
if ([[[outputHash allKeys] firstObject] isEqualToString:kSigningChain]) {
return [self printSigningChain:[[outputHash allValues] firstObject]];
}
printf("%s\n", [[[outputHash allValues] firstObject] UTF8String]);
}
+ (void)printSigningChain:(NSArray *)signingChain {
if (!signingChain) return;
printf("%s:\n", kSigningChain.UTF8String);
__block int i = 0;
[signingChain enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isEqual:[NSNull null]]) return;
if (i++) printf("\n");
printf(" %2lu. %-20s: %s\n", idx + 1, kSHA256.UTF8String,
((NSString *)obj[kSHA256]).UTF8String);
printf(" %-20s: %s\n", kSHA1.UTF8String,
((NSString *)obj[kSHA1]).UTF8String);
printf(" %-20s: %s\n", kCommonName.UTF8String,
((NSString *)obj[kCommonName]).UTF8String);
printf(" %-20s: %s\n", kOrganization.UTF8String,
((NSString *)obj[kOrganization]).UTF8String);
printf(" %-20s: %s\n", kOrganizationalUnit.UTF8String,
((NSString *)obj[kOrganizationalUnit]).UTF8String);
printf(" %-20s: %s\n", kValidFrom.UTF8String,
((NSString *)obj[kValidFrom]).UTF8String);
printf(" %-20s: %s\n", kValidUntil.UTF8String,
((NSString *)obj[kValidUntil]).UTF8String);
}];
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTLogging.h"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#include "SNTLogging.h"
@@ -42,7 +44,7 @@ REGISTER_COMMAND_NAME(@"rule")
}
+ (NSString *)shortHelpText {
return @"Manually add/remove rules.";
return @"Manually add/remove/check rules.";
}
+ (NSString *)longHelpText {
@@ -52,14 +54,17 @@ REGISTER_COMMAND_NAME(@"rule")
@" --blacklist: add to blacklist\n"
@" --silent-blacklist: add to silent blacklist\n"
@" --remove: remove existing rule\n"
@" --check: check for an existing rule\n"
@"\n"
@" One of:\n"
@" --path {path}: path of binary/bundle to add/remove.\n"
@" Will add the hash of the file currently at that path.\n"
@" --sha256 {sha256}: hash to add/remove\n"
@" Does not work with --check. Use the fileinfo verb to check.\n"
@" the rule state of a file.\n"
@" --sha256 {sha256}: hash to add/remove/check\n"
@"\n"
@" Optionally:\n"
@" --certificate: add certificate rule instead of binary\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
@" --message {message}: custom message\n");
}
@@ -71,7 +76,7 @@ REGISTER_COMMAND_NAME(@"rule")
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
SNTConfigurator *config = [SNTConfigurator configurator];
if ([config syncBaseURL] != nil) {
if ([config syncBaseURL] && ![arguments containsObject:@"--check"]) {
printf("SyncBaseURL is set, rules are managed centrally.\n");
exit(1);
}
@@ -81,6 +86,7 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.type = SNTRuleTypeBinary;
NSString *path;
BOOL check = NO;
// Parse arguments
for (NSUInteger i = 0; i < arguments.count; ++i) {
@@ -94,6 +100,8 @@ REGISTER_COMMAND_NAME(@"rule")
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = SNTRuleStateRemove;
} else if ([arg caseInsensitiveCompare:@"--check"] == NSOrderedSame) {
check = YES;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
@@ -119,8 +127,17 @@ REGISTER_COMMAND_NAME(@"rule")
}
}
if (check) {
if (!newRule.shasum) return [self printErrorUsageAndExit:@"--check requires --sha256"];
return [self printStateOfRule:newRule daemonConnection:daemonConn];
}
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) {
@@ -153,4 +170,58 @@ REGISTER_COMMAND_NAME(@"rule")
}];
}
+ (void)printStateOfRule:(SNTRule *)rule daemonConnection:(SNTXPCConnection *)daemonConn {
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.shasum : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.shasum : nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block NSMutableString *output;
[[daemonConn remoteObjectProxy] decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
reply:^(SNTEventState s) {
output = (SNTEventStateAllow & s) ? @"Whitelisted".mutableCopy : @"Blacklisted".mutableCopy;
switch (s) {
case SNTEventStateAllowUnknown:
case SNTEventStateBlockUnknown:
[output appendString:@" (Unknown)"];
break;
case SNTEventStateAllowBinary:
case SNTEventStateBlockBinary:
[output appendString:@" (Binary)"];
break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate:
[output appendString:@" (Certificate)"];
break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope:
[output appendString:@" (Scope)"];
break;
default:
output = @"None".mutableCopy;
break;
}
if (isatty(STDOUT_FILENO)) {
if ((SNTEventStateAllow & s)) {
[output insertString:@"\033[32m" atIndex:0];
[output appendString:@"\033[0m"];
} else if ((SNTEventStateBlock & s)) {
[output insertString:@"\033[31m" atIndex:0];
[output appendString:@"\033[0m"];
} else {
[output insertString:@"\033[33m" atIndex:0];
[output appendString:@"\033[0m"];
}
}
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
printf("Cannot communicate with daemon");
exit(1);
}
printf("%s\n", output.UTF8String);
exit(0);
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import "SNTConfigurator.h"
@@ -102,10 +104,31 @@ REGISTER_COMMAND_NAME(@"status")
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] syncLastSuccess];
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] fullSyncLastSuccess];
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
NSDate *lastRuleSyncSuccess = [[SNTConfigurator configurator] ruleSyncLastSuccess];
NSString *lastRuleSyncSuccessStr =
[dateFormatter stringFromDate:lastRuleSyncSuccess] ?: lastSyncSuccessStr;
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
__block BOOL pushNotifications = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] pushNotifications:^(BOOL response) {
pushNotifications = response;
dispatch_group_leave(group);
}];
}
__block BOOL bundlesEnabled = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] bundlesEnabled:^(BOOL response) {
bundlesEnabled = response;
dispatch_group_leave(group);
}];
}
// Wait a maximum of 5s for stats collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
fprintf(stderr, "Failed to retrieve some stats from daemon\n\n");
@@ -114,7 +137,7 @@ REGISTER_COMMAND_NAME(@"status")
if ([arguments containsObject:@"--json"]) {
NSDictionary *stats = @{
@"daemon" : @{
@"mode" : clientMode,
@"mode" : clientMode ?: @"null",
@"file_logging" : @(fileLogging),
@"watchdog_cpu_events" : @(cpuEvents),
@"watchdog_ram_events" : @(ramEvents),
@@ -130,9 +153,12 @@ REGISTER_COMMAND_NAME(@"status")
@"events_pending_upload" : @(eventCount),
},
@"sync" : @{
@"server" : syncURLStr,
@"server" : syncURLStr ?: @"null",
@"clean_required" : @(syncCleanReqd),
@"last_successful" : lastSyncSuccessStr
@"last_successful_full" : lastSyncSuccessStr ?: @"null",
@"last_successful_rule" : lastRuleSyncSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"Connected" : @"Disconnected",
@"bundle_scanning" : @(bundlesEnabled)
},
};
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
@@ -142,22 +168,26 @@ REGISTER_COMMAND_NAME(@"status")
printf("%s\n", [statsStr UTF8String]);
} else {
printf(">>> Daemon Info\n");
printf(" %-22s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-22s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-22s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-22s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
printf(" %-25s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
printf(" %-25s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
printf(" %-25s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
printf(">>> Kernel Info\n");
printf(" %-22s | %lld\n", "Kernel cache count", cacheCount);
printf(" %-25s | %lld\n", "Kernel cache count", cacheCount);
printf(">>> Database Info\n");
printf(" %-22s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-22s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-22s | %lld\n", "Events Pending Upload", eventCount);
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
if (syncURLStr) {
printf(">>> Sync Info\n");
printf(" %-22s | %s\n", "Sync Server", [syncURLStr UTF8String]);
printf(" %-22s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
printf(" %-22s | %s\n", "Last Successful Sync", [lastSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Sync Server", [syncURLStr UTF8String]);
printf(" %-25s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
printf(" %-25s | %s\n", "Last Successful Full Sync", [lastSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Last Successful Rule Sync", [lastRuleSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Push Notifications",
(pushNotifications ? "Connected" : "Disconnected"));
printf(" %-25s | %s\n", "Bundle Scanning", (bundlesEnabled ? "Yes" : "No"));
}
}

View File

@@ -12,9 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#include <IOKit/kext/KextManager.h>
@import IOKit.kext;
#import "SNTCommonEnums.h"
#import "SNTFileInfo.h"

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
/// Category on NSData providing the option of getting zlib or gzip compressed data.
@interface NSData (Zlib)

View File

@@ -12,16 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import <MOLAuthenticatingURLSession.h>
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncState.h"
#import "SNTCommandSyncManager.h"
#import "SNTConfigurator.h"
#import "SNTDropRootPrivs.h"
#import "SNTLogging.h"
@@ -29,19 +24,22 @@
#import "SNTXPCControlInterface.h"
@interface SNTCommandSync : NSObject<SNTCommand>
@property SNTCommandSyncState *syncState;
@property SNTXPCConnection *listener;
@property SNTCommandSyncManager *syncManager;
@end
@implementation SNTCommandSync
REGISTER_COMMAND_NAME(@"sync")
#pragma mark SNTCommand protocol methods
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
return NO;
}
+ (NSString *)shortHelpText {
@@ -63,36 +61,16 @@ REGISTER_COMMAND_NAME(@"sync")
exit(1);
}
SNTConfigurator *config = [SNTConfigurator configurator];
if (![[SNTConfigurator configurator] syncBaseURL]) {
LOGE(@"Missing SyncBaseURL. Exiting.");
exit(1);
}
SNTCommandSync *s = [[self alloc] init];
// Gather some data needed during some sync stages
s.syncState = [[SNTCommandSyncState alloc] init];
s.syncState.syncBaseURL = config.syncBaseURL;
if (s.syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
exit(1);
} else if (![s.syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
s.syncState.machineID = config.machineID;
if (s.syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
exit(1);
}
s.syncState.machineOwner = config.machineOwner;
if (s.syncState.machineOwner.length == 0) {
s.syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
[[daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
s.syncState.xsrfToken = token;
}];
[daemonConn resume];
BOOL daemon = [arguments containsObject:@"--daemon"];
s.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:daemonConn
isDaemon:daemon];
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
@@ -100,138 +78,40 @@ REGISTER_COMMAND_NAME(@"sync")
diskCapacity:0
diskPath:nil]];
if (!s.syncManager.daemon) return [s.syncManager fullSync];
[s syncdWithDaemonConnection:daemonConn];
}
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
#pragma mark daemon methods
- (void)syncdWithDaemonConnection:(SNTXPCConnection *)daemonConn {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCSyncdInterface syncdInterface];
self.listener.exportedObject = self.syncManager;
self.listener.acceptedHandler = ^{
LOGD(@"santad <--> santactl connections established");
dispatch_semaphore_signal(sema);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateFile]) {
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
} else if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
s.syncState.session = [authURLSession session];
s.syncState.daemonConn = daemonConn;
if ([arguments containsObject:@"singleevent"]) {
NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1;
if (idx >= arguments.count) {
LOGI(@"singleevent takes an argument");
exit(1);
}
NSString *obj = arguments[idx];
if (obj.length != 64) {
LOGI(@"singleevent passed without SHA-256 as next argument");
exit(1);
}
return [s eventUploadSingleEvent:obj];
} else {
return [s preflight];
}
}
- (void)preflight {
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
if (self.syncState.uploadLogURL) {
return [self logUpload];
} else {
return [self eventUpload];
}
} else {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
}
- (void)logUpload {
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
return [self eventUpload];
}
- (void)eventUpload {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownload];
} else {
LOGE(@"Event upload failed, aborting run");
exit(1);
}
}
- (void)eventUploadSingleEvent:(NSString *)sha256 {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p syncSingleEventWithSHA256:sha256]) {
LOGD(@"Event upload complete");
self.listener.invalidationHandler = ^{
// If santad is unloaded kill santactl
LOGD(@"exiting");
exit(0);
} else {
LOGE(@"Event upload failed");
exit(1);
}
}
};
[self.listener resume];
- (void)ruleDownload {
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
if (self.syncState.bundleBinaryRequests.count) {
return [self eventUploadBundleBinaries];
}
return [self postflight];
} else {
LOGE(@"Rule download failed, aborting run");
exit(1);
}
}
// Tell daemon to connect back to the above listener.
[[daemonConn remoteObjectProxy] setSyncdListener:listener.endpoint];
- (void)eventUploadBundleBinaries {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p syncBundleEvents]) {
LOGD(@"Event Upload bundle binaries complete");
} else {
LOGW(@"Event Upload bundle binary search failed");
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self performSelectorInBackground:@selector(syncdWithDaemonConnection:) withObject:daemonConn];
}
return [self postflight];
}
- (void)postflight {
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
exit(0);
} else {
LOGE(@"Postflight failed");
exit(1);
}
[self.syncManager fullSyncSecondsFromNow:15];
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
extern NSString *const kXSRFToken;
extern NSString *const kSerialNumber;
@@ -31,6 +33,10 @@ extern NSString *const kWhitelistRegex;
extern NSString *const kBlacklistRegex;
extern NSString *const kBinaryRuleCount;
extern NSString *const kCertificateRuleCount;
extern NSString *const kFCMToken;
extern NSString *const kFCMFullSyncInterval;
extern NSString *const kFCMGlobalRuleSyncDeadline;
extern NSString *const kBundlesEnabled;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -53,9 +59,13 @@ extern NSString *const kLoggedInUsers;
extern NSString *const kCurrentSessions;
extern NSString *const kFileBundleID;
extern NSString *const kFileBundlePath;
extern NSString *const kFileBundleExecutableRelPath;
extern NSString *const kFileBundleName;
extern NSString *const kFileBundleVersion;
extern NSString *const kFileBundleShortVersionString;
extern NSString *const kFileBundleHash;
extern NSString *const kFileBundleHashMilliseconds;
extern NSString *const kFileBundleBinaryCount;
extern NSString *const kPID;
extern NSString *const kPPID;
extern NSString *const kParentName;
@@ -88,3 +98,22 @@ extern NSString *const kRuleCustomMsg;
extern NSString *const kCursor;
extern NSString *const kBackoffInterval;
extern NSString *const kFullSync;
extern NSString *const kRuleSync;
extern NSString *const kConfigSync;
extern NSString *const kLogSync;
extern const NSUInteger kDefaultEventBatchSize;
///
/// kDefaultFullSyncInterval
/// kDefaultFCMFullSyncInterval
/// kDefaultFCMGlobalRuleSyncDeadline
///
/// Are represented in seconds
///
extern const NSUInteger kDefaultFullSyncInterval;
extern const NSUInteger kDefaultFCMFullSyncInterval;
extern const NSUInteger kDefaultFCMGlobalRuleSyncDeadline;

View File

@@ -33,6 +33,10 @@ NSString *const kWhitelistRegex = @"whitelist_regex";
NSString *const kBlacklistRegex = @"blacklist_regex";
NSString *const kBinaryRuleCount = @"binary_rule_count";
NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kFCMToken = @"fcm_token";
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";
NSString *const kFCMGlobalRuleSyncDeadline = @"fcm_global_rule_sync_deadline";
NSString *const kBundlesEnabled = @"bundles_enabled";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -55,9 +59,13 @@ NSString *const kLoggedInUsers = @"logged_in_users";
NSString *const kCurrentSessions = @"current_sessions";
NSString *const kFileBundleID = @"file_bundle_id";
NSString *const kFileBundlePath = @"file_bundle_path";
NSString *const kFileBundleExecutableRelPath = @"file_bundle_executable_rel_path";
NSString *const kFileBundleName = @"file_bundle_name";
NSString *const kFileBundleVersion = @"file_bundle_version";
NSString *const kFileBundleShortVersionString = @"file_bundle_version_string";
NSString *const kFileBundleHash = @"file_bundle_hash";
NSString *const kFileBundleHashMilliseconds = @"file_bundle_hash_millis";
NSString *const kFileBundleBinaryCount = @"file_bundle_binary_count";
NSString *const kPID = @"pid";
NSString *const kPPID = @"ppid";
NSString *const kParentName = @"parent_name";
@@ -90,3 +98,13 @@ NSString *const kRuleCustomMsg = @"custom_msg";
NSString *const kCursor = @"cursor";
NSString *const kBackoffInterval = @"backoff";
NSString *const kFullSync = @"full_sync";
NSString *const kRuleSync = @"rule_sync";
NSString *const kConfigSync = @"config_sync";
NSString *const kLogSync = @"log_sync";
const NSUInteger kDefaultEventBatchSize = 50;
const NSUInteger kDefaultFullSyncInterval = 600;
const NSUInteger kDefaultFCMFullSyncInterval = 14400;
const NSUInteger kDefaultFCMGlobalRuleSyncDeadline = 600;

View File

@@ -12,12 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256;
- (BOOL)syncBundleEvents;
- (BOOL)uploadEvents:(NSArray *)events;
@end

View File

@@ -44,45 +44,26 @@
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256 {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] databaseEventForSHA256:sha256 reply:^(SNTStoredEvent *e) {
if (e) {
[self uploadEvents:@[ e ]];
}
dispatch_semaphore_signal(sema);
}];
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)syncBundleEvents {
NSMutableArray *newEvents = [NSMutableArray array];
for (NSString *bundlePath in self.syncState.bundleBinaryRequests) {
[newEvents addObjectsFromArray:[self findRelatedBinaries:bundlePath]];
}
return [self uploadEvents:newEvents];
}
- (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;
}
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
if (!r) return NO;
// Keep track of bundle search requests
// A list of bundle hashes that require their related binary events to be uploaded.
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
LOGI(@"Uploaded %lu events", uploadEvents.count);
// 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) {
@@ -119,15 +100,22 @@
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateBundleBinary: ADDKEY(newEvent, kDecision, kDecisionBundleBinary); break;
case SNTEventStateBundleBinary:
ADDKEY(newEvent, kDecision, kDecisionBundleBinary);
[newEvent removeObjectForKey:kExecutionTime];
break;
default: ADDKEY(newEvent, kDecision, kDecisionUnknown);
}
ADDKEY(newEvent, kFileBundleID, event.fileBundleID);
ADDKEY(newEvent, kFileBundlePath, event.fileBundlePath);
ADDKEY(newEvent, kFileBundleExecutableRelPath, event.fileBundleExecutableRelPath);
ADDKEY(newEvent, kFileBundleName, event.fileBundleName);
ADDKEY(newEvent, kFileBundleVersion, event.fileBundleVersion);
ADDKEY(newEvent, kFileBundleShortVersionString, event.fileBundleVersionString);
ADDKEY(newEvent, kFileBundleHash, event.fileBundleHash);
ADDKEY(newEvent, kFileBundleHashMilliseconds, event.fileBundleHashMilliseconds);
ADDKEY(newEvent, kFileBundleBinaryCount, event.fileBundleBinaryCount);
ADDKEY(newEvent, kPID, event.pid);
ADDKEY(newEvent, kPPID, event.ppid);
@@ -158,65 +146,4 @@
#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;
NSMutableArray *relatedEvents = [NSMutableArray array];
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;
while (file = [dirEnum nextObject]) {
@autoreleasepool {
if (shouldCancel) break;
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
file = [path stringByAppendingPathComponent:file];
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:file];
if (fi.isExecutable) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.filePath = fi.path;
se.fileSHA256 = fi.SHA256;
se.decision = SNTEventStateBundleBinary;
se.fileBundleID = fi.bundleIdentifier;
se.fileBundleName = fi.bundleName;
se.fileBundlePath = fi.bundlePath;
se.fileBundleVersion = fi.bundleVersion;
se.fileBundleVersionString = fi.bundleShortVersionString;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
se.signingChain = cs.certificates;
[relatedEvents addObject:[self dictionaryForEvent:se]];
}
}
}
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);
}
return relatedEvents;
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncLogUpload : SNTCommandSyncStage

View File

@@ -0,0 +1,55 @@
/// 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 Foundation;
#import "SNTXPCSyncdInterface.h"
@class SNTXPCConnection;
///
/// Handles push notifications and periodic syncing with a sync server.
///
@interface SNTCommandSyncManager : NSObject<SNTSyncdXPC>
@property(readonly, nonatomic) BOOL daemon;
///
/// Use the designated initializer initWithDaemonConnection:isDaemon:
///
- (instancetype)init NS_UNAVAILABLE;
///
/// Designated initializer.
///
/// @param daemonConn A connection to santad.
/// @param daemon Set to YES if periodic syncing should occur.
/// Set to NO if a single sync should be performed. NO is default.
///
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn
isDaemon:(BOOL)daemon NS_DESIGNATED_INITIALIZER;
///
/// Perform a full sync immediately. Non-blocking.
/// If a full sync is already running new requests will be dropped.
///
- (void)fullSync;
///
/// Perform a full sync seconds from now. Non-blocking.
/// If a full sync is already running new requests will be dropped.
///
- (void)fullSyncSecondsFromNow:(uint64_t)seconds;
@end

View File

@@ -0,0 +1,523 @@
/// 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 "SNTCommandSyncManager.h"
@import SystemConfiguration;
#import <MOLAuthenticatingURLSession.h>
#import <MOLFCMClient/MOLFCMClient.h>
#import "SNTConfigurator.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncState.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCSyncdInterface.h"
static NSString *const kFCMActionKey = @"action";
static NSString *const kFCMFileHashKey = @"file_hash";
static NSString *const kFCMFileNameKey = @"file_name";
static NSString *const kFCMTargetHostIDKey = @"target_host_id";
@interface SNTCommandSyncManager () {
SCNetworkReachabilityRef _reachability;
}
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
@property(nonatomic) NSCache *ruleSyncCache;
@property NSUInteger FCMFullSyncInterval;
@property NSUInteger FCMGlobalRuleSyncDeadline;
@property NSUInteger eventBatchSize;
@property MOLFCMClient *FCMClient;
@property(nonatomic) SNTXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@property(nonatomic) BOOL reachable;
@end
// Called when the network state changes
static void reachabilityHandler(
SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
// Put this check and set on the main thread to ensure serial access.
dispatch_async(dispatch_get_main_queue(), ^{
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
// Only call the setter when there is a change. This will filter out the redundant calls to this
// callback whenever the network interface states change.
if (commandSyncManager.reachable != (flags & kSCNetworkReachabilityFlagsReachable)) {
commandSyncManager.reachable = (flags & kSCNetworkReachabilityFlagsReachable);
}
});
}
@implementation SNTCommandSyncManager
#pragma mark init
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
self = [super init];
if (self) {
_daemonConn = daemonConn;
_daemon = daemon;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:self.FCMFullSyncInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
[self unlockAction:kFullSync];
}];
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
dispatch_source_set_timer(self.ruleSyncTimer,
DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTCommandSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.ruleSyncCache = self.ruleSyncCache;
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
LOGE(@"Rule download failed");
}
self.targetedRuleSync = NO;
[self unlockAction:kRuleSync];
}];
_dispatchLock = [[NSCache alloc] init];
_ruleSyncCache = [[NSCache alloc] init];
_eventBatchSize = kDefaultEventBatchSize;
_FCMFullSyncInterval = kDefaultFCMFullSyncInterval;
_FCMGlobalRuleSyncDeadline = kDefaultFCMGlobalRuleSyncDeadline;
}
return self;
}
#pragma mark SNTSyncdXPC protocol methods
- (void)postBundleEventToSyncServer:(SNTStoredEvent *)event reply:(void (^)(BOOL))reply {
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if (event && [p uploadEvents:@[event]]) {
BOOL needsRelatedEvents = [syncState.bundleBinaryRequests containsObject:event.fileBundleHash];
reply(needsRelatedEvents);
if (needsRelatedEvents) {
LOGD(@"Needs related events");
} else {
LOGD(@"Bundle event upload complete");
}
} else {
reply(NO);
LOGE(@"Bundle event upload failed");
}
}
- (void)postBundleEventsToSyncServer:(NSArray<SNTStoredEvent *> *)events {
SNTCommandSyncState *syncState = [self createSyncState];
syncState.eventBatchSize = self.eventBatchSize;
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if (events && [p uploadEvents:events]) {
LOGD(@"Bundle events upload complete");
} else {
LOGE(@"Bundle events upload failed");
}
}
- (void)postEventToSyncServer:(SNTStoredEvent *)event {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc]
initWithState:[self createSyncState]];
if (event && [p uploadEvents:@[event]]) {
LOGD(@"Event upload complete");
} else {
LOGE(@"Event upload failed");
}
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply((self.FCMClient.FCMToken != nil));
}
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
if ([self.FCMClient.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Continue with the current FCMToken");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
self.FCMClient = [[MOLFCMClient alloc] initWithFCMToken:syncState.FCMToken
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || [message isEqual:@{}]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.connectionErrorHandler = ^(NSError *error) {
STRONGIFY(self);
LOGE(@"FCM connection error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"%@", log);
};
[self.FCMClient connect];
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSDictionary *message = [self messageFromMessageData:[self messageDataFromFCMmessage:FCMmessage]];
if (!message) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSString *action = message[kFCMActionKey];
if (!action) {
LOGD(@"Push notification message contains no action");
return;
}
// Store the file name and hash in a cache. When the rule is actually added, use the cache
// to build a user notification.
NSString *fileHash = message[kFCMFileHashKey];
NSString *fileName = message[kFCMFileNameKey];
if (fileName && fileHash) {
[self.ruleSyncCache setObject:fileName forKey:fileHash];
}
LOGD(@"Push notification action: %@ received", action);
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetHostID = message[kFCMTargetHostIDKey];
if (targetHostID && [targetHostID caseInsensitiveCompare:machineID] == NSOrderedSame) {
LOGD(@"Targeted rule_sync for host_id: %@", targetHostID);
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds = arc4random_uniform((uint32_t)self.FCMGlobalRuleSyncDeadline);
LOGD(@"Global rule_sync, staggering: %u second delay", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
[self fullSync];
} else if ([action isEqualToString:kLogSync]) {
[self fullSync];
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)messageDataFromFCMmessage:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSDictionary *)messageFromMessageData:(NSData *)messageData {
NSError *error;
NSDictionary *rawMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!rawMessage) {
LOGD(@"Unable to parse push notification message data: %@", error);
return nil;
}
// Create a new message dropping unexpected values
NSArray *allowedKeys = @[ kFCMActionKey, kFCMFileHashKey, kFCMFileNameKey, kFCMTargetHostIDKey ];
NSMutableDictionary *message = [NSMutableDictionary dictionaryWithCapacity:allowedKeys.count];
for (NSString *key in allowedKeys) {
if ([rawMessage[key] isKindOfClass:[NSString class]] && [rawMessage[key] length]) {
message[key] = rawMessage[key];
}
}
return message.count ? [message copy] : nil;
}
#pragma mark sync timer control
- (void)fullSync {
[self fullSyncSecondsFromNow:0];
}
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kFullSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
return;
}
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)ruleSync {
[self ruleSyncSecondsFromNow:0];
}
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kRuleSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
return;
}
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
}
- (void)rescheduleTimerQueue:(dispatch_source_t)timerQueue secondsFromNow:(uint64_t)seconds {
uint64_t interval = seconds * NSEC_PER_SEC;
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
}
#pragma mark syncing chain
- (void)preflight {
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
// Clean up reachability if it was started for a non-network error
[self stopReachability];
self.eventBatchSize = syncState.eventBatchSize;
// Start listening for push notifications with a full sync every FCMFullSyncInterval
if (syncState.daemon && syncState.FCMToken) {
self.FCMFullSyncInterval = syncState.FCMFullSyncInterval;
self.FCMGlobalRuleSyncDeadline = syncState.FCMGlobalRuleSyncDeadline;
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCMToken not provided. Sync every %lu min.", kDefaultFullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kDefaultFullSyncInterval];
}
if (syncState.uploadLogURL) {
return [self logUploadWithSyncState:syncState];
} else {
return [self eventUploadWithSyncState:syncState];
}
} else {
if (!syncState.daemon) {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
}
- (void)logUploadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
return [self eventUploadWithSyncState:syncState];
}
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
} else {
LOGE(@"Event upload failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
if (!syncState.daemon) exit(0);
} else {
LOGE(@"Postflight failed");
if (!syncState.daemon) exit(1);
}
}
#pragma mark internal helpers
- (dispatch_source_t)createSyncTimerWithBlock:(void (^)())block {
dispatch_source_t timerQueue = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_source_set_event_handler(timerQueue, block);
dispatch_resume(timerQueue);
return timerQueue;
}
- (SNTCommandSyncState *)createSyncState {
// Gather some data needed during some sync stages
SNTCommandSyncState *syncState = [[SNTCommandSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
if (syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (!syncState.daemon) exit(1);
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
syncState.machineID = config.machineID;
if (syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
if (!syncState.daemon) exit(1);
}
syncState.machineOwner = config.machineOwner;
if (syncState.machineOwner.length == 0) {
syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
syncState.xsrfToken = token;
dispatch_group_leave(group);
}];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateFile]) {
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
} else if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return syncState;
}
- (void)lockAction:(NSString *)action {
[self.dispatchLock setObject:@YES forKey:action];
}
- (void)unlockAction:(NSString *)action {
[self.dispatchLock removeObjectForKey:action];
}
- (BOOL)checkLockAction:(NSString *)action {
return ([self.dispatchLock objectForKey:action] == nil);
}
#pragma mark reachability methods
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
[self stopReachability];
[self fullSync];
}
}
// Start listening for network state changes on a background thread
- (void)startReachability {
if (_reachability) return;
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].host.UTF8String;
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
SCNetworkReachabilityContext context = {
.info = (__bridge void *)self
};
if (SCNetworkReachabilitySetCallback(_reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
} else {
[self stopReachability];
}
}
// Stop listening for network state changes
- (void)stopReachability {
if (_reachability) {
SCNetworkReachabilitySetDispatchQueue(_reachability, NULL);
if (_reachability) CFRelease(_reachability);
_reachability = NULL;
}
}
@end

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncPostflight : SNTCommandSyncStage

View File

@@ -29,7 +29,7 @@
}
- (BOOL)sync {
NSDictionary *r = [self performRequest:[self requestWithDictionary:nil]];
[self performRequest:[self requestWithDictionary:nil]];
dispatch_group_t group = dispatch_group_create();
void (^replyBlock)() = ^{
@@ -43,14 +43,6 @@
reply:replyBlock];
}
// Update backoff interval
NSString *backoffInterval = r[kBackoffInterval];
if (backoffInterval) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue]
reply:replyBlock];
}
// Remove clean sync flag if we did a clean sync
if (self.syncState.cleanSync) {
dispatch_group_enter(group);

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncPreflight : SNTCommandSyncStage

View File

@@ -40,17 +40,32 @@
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kPrimaryUser] = self.syncState.machineOwner;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
requestDict[kBinaryRuleCount] = @(binary);
requestDict[kCertificateRuleCount] = @(certificate);
dispatch_semaphore_signal(sema);
dispatch_group_leave(group);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor:
requestDict[kClientMode] = kClientModeMonitor; break;
case SNTClientModeLockdown:
requestDict[kClientMode] = kClientModeLockdown; break;
default: break;
}
dispatch_group_leave(group);
}];
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
// 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;
}
@@ -59,10 +74,21 @@
if (!resp) return NO;
self.syncState.eventBatchSize = [resp[kBatchSize] intValue];
if (self.syncState.eventBatchSize == 0) {
self.syncState.eventBatchSize = 50;
}
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setBundlesEnabled:[resp[kBundlesEnabled] boolValue] reply:^{
dispatch_group_leave(group);
}];
self.syncState.eventBatchSize = [resp[kBatchSize] unsignedIntegerValue] ?: kDefaultEventBatchSize;
self.syncState.FCMToken = resp[kFCMToken];
// Don't let these go too low
NSUInteger value = [resp[kFCMFullSyncInterval] unsignedIntegerValue];
self.syncState.FCMFullSyncInterval =
(value < kDefaultFullSyncInterval) ? kDefaultFCMFullSyncInterval : value;
value = [resp[kFCMGlobalRuleSyncDeadline] unsignedIntegerValue];
self.syncState.FCMGlobalRuleSyncDeadline =
(value < 60) ? kDefaultFCMGlobalRuleSyncDeadline : value;
self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]];
@@ -81,9 +107,11 @@
}
if ([resp[kCleanSync] boolValue]) {
LOGD(@"Clean sync requested by server");
self.syncState.cleanSync = YES;
}
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
return YES;
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncRuleDownload : SNTCommandSyncStage

View File

@@ -67,7 +67,26 @@
return NO;
}
sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] setRuleSyncLastSuccess:[NSDate date] reply:^{
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
if (self.syncState.targetedRuleSync) {
for (SNTRule *r in self.syncState.downloadedRules) {
NSString *fileName = [[self.syncState.ruleSyncCache objectForKey:r.shasum] copy];
[self.syncState.ruleSyncCache removeObjectForKey:r.shasum];
if (fileName.length) {
NSString *message = [NSString stringWithFormat:@"%@ can now be run", fileName];
[[self.daemonConn remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
}
}
}
return YES;
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
@class SNTCommandSyncState;
@class SNTXPCConnection;

View File

@@ -26,6 +26,7 @@
@property(readwrite) NSURLSession *urlSession;
@property(readwrite) SNTCommandSyncState *syncState;
@property(readwrite) SNTXPCConnection *daemonConn;
@property BOOL xsrfFetched;
@end
@@ -170,9 +171,9 @@
}
- (BOOL)fetchXSRFToken {
__block BOOL success = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // only fetch token once per session
BOOL success = NO;
if (!self.xsrfFetched) { // only fetch token once per session
self.xsrfFetched = YES;
NSString *stageName = [@"xsrf" stringByAppendingFormat:@"/%@", self.syncState.machineID];
NSURL *u = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:u];
@@ -188,7 +189,7 @@
} else {
LOGD(@"Failed to retrieve XSRF token");
}
});
};
return success;
}

View File

@@ -12,8 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
@class SNTCommandSyncManager;
@class SNTXPCConnection;
/// An instance of this class is passed to each stage of the sync process for storing data
@@ -32,6 +35,15 @@
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// An FCM token to subscribe to push notifications.
@property(copy) NSString *FCMToken;
/// Full sync interval in seconds while listening for FCM messages.
@property NSUInteger FCMFullSyncInterval;
/// Leeway time in seconds when receiving a global rule sync message.
@property NSUInteger FCMGlobalRuleSyncDeadline;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@property(copy) NSString *machineOwner;
@@ -50,10 +62,19 @@
/// Log upload URL sent from server. If set, LogUpload phase needs to happen.
@property NSURL *uploadLogURL;
/// Array of bundle paths to find binaries for.
/// Array of bundle IDs to find binaries for.
@property NSArray *bundleBinaryRequests;
/// Rules downloaded from server.
@property NSMutableArray *downloadedRules;
/// Returns YES if the santactl session is running as a daemon, returns NO otherwise.
@property BOOL daemon;
/// Returns YES if the session is targeted for this machine, returns NO otherwise.
@property BOOL targetedRuleSync;
/// Reference to the sync manager's ruleSyncCache. Used to lookup binary names for notifications.
@property(weak) NSCache *ruleSyncCache;
@end

View File

@@ -1,3 +0,0 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
@class SNTXPCConnection;
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
///

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
// This is imported in the header rather than implementation to save
// classes that use this one from also having to import FMDB stuff.
#import <FMDB/FMDB.h>

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTDatabaseTable.h"
@class SNTNotificationMessage;
@@ -30,6 +32,14 @@
///
- (BOOL)addStoredEvent:(SNTStoredEvent *)event;
///
/// Add events to the database.
///
/// @param events the events to store.
/// @return YES if events were successfully stored.
///
- (BOOL)addStoredEvents:(NSArray<SNTStoredEvent *> *)events;
///
/// Retrieves all events in the database
///
@@ -44,15 +54,6 @@
///
- (NSUInteger)pendingEventsCount;
///
/// Retrieve an event from the database with a given SHA-256. If multiple events
/// exist for the same SHA-256, just the first is returned.
///
/// @param sha256 a SHA-256 of the binary to return an event for.
/// @return a single SNTStoredEvent.
///
- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256;
///
/// Delete a single event from the database using its index.
///

View File

@@ -15,7 +15,6 @@
#import "SNTEventTable.h"
#import "MOLCertificate.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@implementation SNTEventTable
@@ -66,28 +65,55 @@
newVersion = 2;
}
if (version < 3) {
// Clean-up: Disable AUTOINCREMENT on idx column
[db executeUpdate:@"CREATE TABLE 'events_tmp' ("
@"'idx' INTEGER PRIMARY KEY,"
@"'filesha256' TEXT NOT NULL,"
@"'eventdata' BLOB);"];
[db executeUpdate:@"INSERT INTO events_tmp SELECT * FROM events"];
[db executeUpdate:@"DROP TABLE events"];
[db executeUpdate:@"ALTER TABLE events_tmp RENAME TO events"];
newVersion = 3;
}
return newVersion;
}
#pragma mark Loading / Storing
- (BOOL)addStoredEvent:(SNTStoredEvent *)event {
if (!event.fileSHA256 ||
!event.filePath ||
!event.occurrenceDate ||
!event.decision) return NO;
return [self addStoredEvents:@[event]];
}
NSData *eventData;
@try {
eventData = [NSKeyedArchiver archivedDataWithRootObject:event];
} @catch (NSException *exception) {
return NO;
- (BOOL)addStoredEvents:(NSArray<SNTStoredEvent *> *)events {
NSMutableDictionary *eventsData = [NSMutableDictionary dictionaryWithCapacity:events.count];
for (SNTStoredEvent *event in events) {
if (!event.idx ||
!event.fileSHA256 ||
!event.filePath ||
!event.occurrenceDate ||
!event.decision) continue;
NSData *eventData;
@try {
eventData = [NSKeyedArchiver archivedDataWithRootObject:event];
} @catch (NSException *exception) {
continue;
}
eventsData[eventData] = event;
}
__block BOOL success = NO;
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
success = [db executeUpdate:@"INSERT INTO 'events' (filesha256, eventdata) VALUES (?, ?)",
event.fileSHA256, eventData];
[eventsData enumerateKeysAndObjectsUsingBlock:^(NSData * eventData,
SNTStoredEvent * event,
BOOL *stop) {
success = [db executeUpdate:@"INSERT INTO 'events' (idx, filesha256, eventdata)"
@"VALUES (?, ?, ?)",
event.idx, event.fileSHA256, eventData];
if (!success) *stop = YES;
}];
}];
return success;
@@ -103,26 +129,6 @@
return eventsPending;
}
- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256 {
__block SNTStoredEvent *storedEvent;
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:@"SELECT * FROM events WHERE filesha256=? LIMIT 1;", sha256];
if ([rs next]) {
storedEvent = [self eventFromResultSet:rs];
if (!storedEvent) {
[db executeUpdate:@"DELETE FROM events WHERE idx=?", [rs objectForColumnName:@"idx"]];
}
}
[rs close];
}];
return storedEvent;
}
- (NSArray *)pendingEvents {
NSMutableArray *pendingEvents = [[NSMutableArray alloc] init];
@@ -152,7 +158,7 @@
@try {
event = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
event.idx = @([rs intForColumn:@"idx"]);
event.idx = event.idx ?: @((uint32_t)[rs intForColumn:@"idx"]);
} @catch (NSException *exception) {
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
#import "SNTDatabaseTable.h"
@@ -39,14 +41,11 @@
- (NSUInteger)certificateRuleCount;
///
/// @return Rule for binary with given SHA-256
/// @return Rule for binary or certificate with given SHA-256. The binary rule will be returned
/// if it exists. If not, the certificate rule will be returned if it exists.
///
- (SNTRule *)binaryRuleForSHA256:(NSString *)SHA256;
///
/// @return Rule for certificate with given SHA-256
///
- (SNTRule *)certificateRuleForSHA256:(NSString *)SHA256;
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256;
///
/// Add an array of rules to the database. The rules will be added within a transaction and the

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;
}
@@ -108,25 +112,15 @@
return rule;
}
- (SNTRule *)certificateRuleForSHA256:(NSString *)SHA256 {
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256 {
__block SNTRule *rule;
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:@"SELECT * FROM certrules WHERE shasum=? LIMIT 1", SHA256];
if ([rs next]) {
rule = [self ruleFromResultSet:rs];
}
[rs close];
}];
return rule;
}
- (SNTRule *)binaryRuleForSHA256:(NSString *)SHA256 {
__block SNTRule *rule;
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs = [db executeQuery:@"SELECT * FROM binrules WHERE shasum=? LIMIT 1", SHA256];
FMResultSet *rs =
[db executeQuery:
@"SELECT * FROM rules WHERE (shasum=? and type=1) OR (shasum=? AND type=2) LIMIT 1",
binarySHA256, certificateSHA256];
if ([rs next]) {
rule = [self ruleFromResultSet:rs];
}
@@ -149,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

@@ -1,3 +0,0 @@
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// The main controller class for santad
///

View File

@@ -14,7 +14,8 @@
#import "SNTApplication.h"
#import <DiskArbitration/DiskArbitration.h>
@import DiskArbitration;
#include <sys/stat.h>
#include <sys/types.h>
@@ -23,6 +24,7 @@
#import "SNTDaemonControlController.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTDropRootPrivs.h"
#import "SNTEventLog.h"
#import "SNTEventTable.h"
#import "SNTExecutionController.h"
@@ -30,6 +32,7 @@
#import "SNTLogging.h"
#import "SNTNotificationQueue.h"
#import "SNTRuleTable.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -68,11 +71,18 @@
}
SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Establish XPC listener for santactl connections
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
// Establish XPC listener for Santa and santactl connections
SNTDaemonControlController *dc = [[SNTDaemonControlController alloc] init];
dc.driverManager = _driverManager;
dc.notQueue = notQueue;
dc.syncdQueue = syncdQueue;
_controlConnection =
[[SNTXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
@@ -81,6 +91,7 @@
[_controlConnection resume];
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
__block NSURL *origSyncURL = [[SNTConfigurator configurator] syncBaseURL];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^(unsigned long data) {
if (data & DISPATCH_VNODE_ATTRIB) {
@@ -107,6 +118,14 @@
[self.driverManager flushCache];
}
}
// Start santactl if the syncBaseURL changed from nil --> somthing
NSURL *syncURL = [[SNTConfigurator configurator] syncBaseURL];
if (!origSyncURL && syncURL) {
origSyncURL = syncURL;
LOGI(@"SyncBaseURL added, starting santactl.");
[self startSyncd];
}
}
}];
@@ -117,13 +136,29 @@
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
if (!_execController) return nil;
}
return self;
}
- (void)startSyncd {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
}
- (void)start {
LOGI(@"Connected to driver, activating.");
@@ -135,26 +170,27 @@
- (void)beginListeningForDecisionRequests {
dispatch_queue_t exec_queue = dispatch_queue_create(
"com.google.santad.execution_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
dispatch_set_target_queue(
exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
[self.driverManager listenForDecisionRequests:^(santa_message_t message) {
@autoreleasepool {
switch (message.action) {
case ACTION_REQUEST_SHUTDOWN: {
LOGI(@"Driver requested a shutdown");
exit(0);
}
case ACTION_REQUEST_BINARY: {
dispatch_async(exec_queue, ^{
dispatch_async(exec_queue, ^{
switch (message.action) {
case ACTION_REQUEST_SHUTDOWN: {
LOGI(@"Driver requested a shutdown");
exit(0);
}
case ACTION_REQUEST_BINARY: {
[_execController validateBinaryWithMessage:message];
});
break;
break;
}
default: {
LOGE(@"Received decision request without a valid action: %d", message.action);
exit(1);
}
}
default: {
LOGE(@"Received decision request without a valid action: %d", message.action);
exit(1);
}
}
});
}
}];
}
@@ -165,33 +201,36 @@
dispatch_set_target_queue(
log_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
// Limit number of threads the queue can create.
dispatch_semaphore_t concurrencyLimiter = dispatch_semaphore_create(15);
[self.driverManager listenForLogRequests:^(santa_message_t message) {
@autoreleasepool {
switch (message.action) {
case ACTION_NOTIFY_DELETE:
case ACTION_NOTIFY_EXCHANGE:
case ACTION_NOTIFY_LINK:
case ACTION_NOTIFY_RENAME:
case ACTION_NOTIFY_WRITE: {
dispatch_async(log_queue, ^{
dispatch_semaphore_wait(concurrencyLimiter, DISPATCH_TIME_FOREVER);
dispatch_async(log_queue, ^{
switch (message.action) {
case ACTION_NOTIFY_DELETE:
case ACTION_NOTIFY_EXCHANGE:
case ACTION_NOTIFY_LINK:
case ACTION_NOTIFY_RENAME:
case ACTION_NOTIFY_WRITE: {
NSRegularExpression *re = [[SNTConfigurator configurator] fileChangesRegex];
NSString *path = @(message.path);
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
[_eventLog logFileModification:message];
}
});
break;
}
case ACTION_NOTIFY_EXEC: {
dispatch_async(log_queue, ^{
break;
}
case ACTION_NOTIFY_EXEC: {
[_eventLog logAllowedExecution:message];
});
break;
break;
}
default:
LOGE(@"Received log request without a valid action: %d", message.action);
break;
}
default:
LOGE(@"Received log request without a valid action: %d", message.action);
break;
}
dispatch_semaphore_signal(concurrencyLimiter);
});
}
}];
}

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.h"
///

View File

@@ -12,10 +12,13 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTXPCControlInterface.h"
@class SNTDriverManager;
@class SNTNotificationQueue;
@class SNTSyncdQueue;
///
/// SNTDaemonControlController handles all of the RPCs from santactl
@@ -24,5 +27,6 @@
@property SNTDriverManager *driverManager;
@property SNTNotificationQueue *notQueue;
@property SNTSyncdQueue *syncdQueue;
@end

View File

@@ -14,17 +14,23 @@
#import "SNTDaemonControlController.h"
#import "SNTCachedDecision.h"
#import "SNTConfigurator.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTDropRootPrivs.h"
#import "SNTEventTable.h"
#import "SNTLogging.h"
#import "SNTNotificationQueue.h"
#import "SNTPolicyProcessor.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
#import "SNTStoredEvent.h"
#import "SNTStrengthify.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCBundleServiceInterface.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
#import "SNTXPCSyncdInterface.h"
// Globals used by the santad watchdog thread
uint64_t watchdogCPUEvents = 0;
@@ -34,7 +40,7 @@ double watchdogRAMPeak = 0;
@interface SNTDaemonControlController ()
@property NSString *_syncXsrfToken;
@property dispatch_source_t syncTimer;
@property SNTPolicyProcessor *policyProcessor;
@end
@implementation SNTDaemonControlController
@@ -42,44 +48,12 @@ double watchdogRAMPeak = 0;
- (instancetype)init {
self = [super init];
if (self) {
_syncTimer = [self createSyncTimer];
[self rescheduleSyncSecondsFromNow:30];
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:
[SNTDatabaseController ruleTable]];
}
return self;
}
- (dispatch_source_t)createSyncTimer {
dispatch_source_t syncTimerQ = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_source_set_event_handler(syncTimerQ, ^{
[self rescheduleSyncSecondsFromNow:600];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[[SNTConfigurator configurator] setSyncBackOff:NO];
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", NULL));
}
});
dispatch_resume(syncTimerQ);
return syncTimerQ;
}
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
uint64_t interval = seconds * NSEC_PER_SEC;
uint64_t leeway = (seconds * 0.05) * NSEC_PER_SEC;
dispatch_source_set_timer(self.syncTimer, dispatch_walltime(NULL, interval), interval, leeway);
}
#pragma mark Kernel ops
- (void)cacheCount:(void (^)(int64_t))reply {
@@ -122,10 +96,6 @@ double watchdogRAMPeak = 0;
reply([[SNTDatabaseController eventTable] pendingEventsCount]);
}
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply {
reply([[SNTDatabaseController eventTable] pendingEventForSHA256:sha256]);
}
- (void)databaseEventsPending:(void (^)(NSArray *events))reply {
reply([[SNTDatabaseController eventTable] pendingEvents]);
}
@@ -134,16 +104,30 @@ double watchdogRAMPeak = 0;
[[SNTDatabaseController eventTable] deleteEventsWithIds:ids];
}
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] binaryRuleForSHA256:sha256]);
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] ruleForBinarySHA256:binarySHA256
certificateSHA256:certificateSHA256]);
}
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] certificateRuleForSHA256:sha256]);
#pragma mark Decision Ops
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply {
reply([self.policyProcessor decisionForFilePath:filePath
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256].decision);
}
#pragma mark Config Ops
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply {
reply(watchdogCPUEvents, watchdogRAMEvents, watchdogCPUPeak, watchdogRAMPeak);
}
- (void)clientMode:(void (^)(SNTClientMode))reply {
reply([[SNTConfigurator configurator] clientMode]);
}
@@ -165,14 +149,13 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
[self rescheduleSyncSecondsFromNow:seconds];
[[SNTConfigurator configurator] setSyncBackOff:YES];
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setFullSyncLastSuccess:date];
reply();
}
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncLastSuccess:date];
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setRuleSyncLastSuccess:date];
reply();
}
@@ -186,6 +169,8 @@ double watchdogRAMPeak = 0;
options:0
error:NULL];
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
LOGI(@"Received new whitelist regex, flushing cache");
[self.driverManager flushCache];
reply();
}
@@ -194,11 +179,18 @@ double watchdogRAMPeak = 0;
options:0
error:NULL];
[[SNTConfigurator configurator] setBlacklistPathRegex:re];
LOGI(@"Received new blacklist regex, flushing cache");
[self.driverManager flushCache];
reply();
}
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply {
reply(watchdogCPUEvents, watchdogRAMEvents, watchdogCPUPeak, watchdogRAMPeak);
- (void)bundlesEnabled:(void (^)(BOOL))reply {
reply([SNTConfigurator configurator].bundlesEnabled);
}
- (void)setBundlesEnabled:(BOOL)bundlesEnabled reply:(void (^)())reply {
[[SNTConfigurator configurator] setBundlesEnabled:bundlesEnabled];
reply();
}
#pragma mark GUI Ops
@@ -210,4 +202,95 @@ double watchdogRAMPeak = 0;
self.notQueue.notifierConnection = c;
}
- (void)setBundleNotificationListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *bs = [[SNTXPCConnection alloc] initClientWithServiceName:@"com.google.santabs"];
bs.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[bs resume];
[[bs remoteObjectProxy] setBundleNotificationListener:listener];
}
#pragma mark syncd Ops
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
// Only allow one active syncd connection
if (self.syncdQueue.syncdConnection) return;
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
c.invalidationHandler = ^{
[self.syncdQueue stopSyncingEvents];
self.syncdQueue.syncdConnection = nil;
self.syncdQueue.invalidationHandler();
};
c.acceptedHandler = ^{
[self.syncdQueue startSyncingEvents];
};
[c resume];
self.syncdQueue.syncdConnection = c;
}
- (void)pushNotifications:(void (^)(BOOL))reply {
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
reply(response);
}];
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply {
[[self.notQueue.notifierConnection remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message];
reply();
}
#pragma mark Bundle ops
///
/// This method is only used for santactl's bundleinfo command. For blocked executions, SantaGUI
/// calls on santabs directly.
///
/// Hash a bundle for an event. The SNTBundleHashBlock will be called with nil parameters if a
/// failure or cancellation occurs.
///
/// @param event The event that includes the fileBundlePath to be hashed.
/// @param reply A SNTBundleHashBlock to be executed upon completion or cancellation.
///
/// @note If there is a current NSProgress when called this method will report back it's progress.
///
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
reply:(SNTBundleHashBlock)reply {
SNTXPCConnection *bs =
[[SNTXPCConnection alloc] initClientWithServiceName:[SNTXPCBundleServiceInterface serviceId]];
bs.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[bs resume];
[[bs remoteObjectProxy] hashBundleBinariesForEvent:event reply:reply];
}
///
/// Used by SantaGUI sync the offending event and potentially all the related events,
/// if the sync server has not seen them before.
///
/// @param event The offending event, fileBundleHash & fileBundleBinaryCount need to be populated.
/// @param relatedEvents Nexted bundle events.
///
- (void)syncBundleEvent:(SNTStoredEvent *)event
relatedEvents:(NSArray<SNTStoredEvent *> *)events {
SNTEventTable *eventTable = [SNTDatabaseController eventTable];
// Delete the event cached by the execution controller.
[eventTable deleteEventWithId:event.idx];
// Add the updated event.
[eventTable addStoredEvent:event];
WEAKIFY(self);
// Sync the updated event. If the sync server needs the related events, add them to the eventTable
// and upload them too.
[self.syncdQueue addBundleEvent:event reply:^(BOOL needRelatedEvents) {
STRONGIFY(self);
if (needRelatedEvents) {
[eventTable addStoredEvents:events];
[self.syncdQueue addBundleEvents:events];
}
}];
}
@end

Some files were not shown because too many files have changed in this diff Show More