Compare commits

...

498 Commits

Author SHA1 Message Date
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
Russell Hancox
6b0994a990 santad: Avoid properties in critical path 2016-07-13 12:44:48 -04:00
Russell Hancox
7dd616e891 santa-driver: Switch SantaCache from an array to a linked list 2016-07-12 14:54:43 -04:00
Russell Hancox
c672edbe4d Whitespace clean-up 2016-07-12 14:51:10 -04:00
Russell Hancox
687ecc7097 santad: Close more file descriptors on exec 2016-07-11 16:23:38 -04:00
Russell Hancox
b8882b4826 santactl/fileinfo: Wait longer for daemon response. 2016-07-11 15:59:30 -04:00
Russell Hancox
51de0b38a4 santad: Change watchdog thread interval to 30s 2016-07-11 15:59:30 -04:00
Russell Hancox
e0309c0482 SantaGUI: In keyPathsForValuesAffectingValueForKey, return an empty set rather than nil 2016-07-11 15:53:04 -04:00
Russell Hancox
5dbe86869d santad: Move event storage out of the high priority decision queue
As event storage needs to happen before attempting upload, use the same serial queue.
2016-07-01 17:56:52 -04:00
Russell Hancox
14a11279c7 Project: Remove activesupport from travis settings.
It no longer appears to be needed for CocoaPods and causes errors.
2016-07-01 17:56:52 -04:00
Russell Hancox
df0ce42377 Merge pull request #54 from georgekola/gk-avoid-string-copy
Avoid two string copies
2016-07-01 17:09:54 -04:00
George Kola
4c03411405 Avoid two string copies 2016-07-01 14:07:23 -07:00
Russell Hancox
f020e18238 Project: Update to MOLCertificate 1.5 2016-07-01 13:02:07 -04:00
Russell Hancox
629bd4aff9 santad: argsForPid: Don't replace last NULL but still count up to it 2016-07-01 12:48:50 -04:00
Russell Hancox
f20825a66c Project: Increase optimization level for Pods 2016-06-30 14:36:16 -04:00
Russell Hancox
f098ca0d02 santad: Update argsForPid to append to a given string. 2016-06-30 09:41:26 -04:00
Russell Hancox
1f96f74f4d Merge pull request #52 from georgekola/gk-pread
Correctly use pread
2016-06-29 14:27:22 -04:00
George Kola
7a3a98c27a Correctly use pread
pread can return less than the chunk size (e.g. signal caught in the
middle) and hence we need to handle it. This change also cleans up the
hash function and makes it more performant.
2016-06-29 11:21:56 -07:00
Russell Hancox
1130448cb9 Merge pull request #53 from georgekola/gk-cacheCalls
Cache method call
2016-06-29 08:20:59 -04:00
George Kola
d388e99c0e Cache method call
Minor optimization. Cache objc method call in local variable to avoid a
second call
2016-06-28 21:26:35 -07:00
Russell Hancox
2baea9a6b4 Project: Xcode recommended updates. 2016-06-28 17:34:58 -04:00
Russell Hancox
0629625a9a santad: Move log queue down to BACKGROUND priority. 2016-06-28 17:21:07 -04:00
Russell Hancox
a2d0acc761 santad: sanitizeString: Use cached length value, use lengthOfBytesUsingEncoding: instead of length. 2016-06-28 17:02:37 -04:00
Russell Hancox
28a6bce90f santad: sanitizeString: Only allocate buffer if necessary. 2016-06-28 16:35:50 -04:00
Russell Hancox
9058192ffe santad: Use memcpy instead of strncpy where appropriate 2016-06-28 16:23:06 -04:00
Russell Hancox
465b358271 SantaCache: Initialize count_ to 0. 2016-06-28 15:01:57 -04:00
Russell Hancox
7de585fe1d santad: Replace sanitizeString with simple loop.
This is ~70% faster on average and is faster in all cases compared to the regex and the previous method.
2016-06-28 14:54:31 -04:00
Russell Hancox
8479730c95 SNTFileInfo: Catch potential NULL-pointer deref in isScript and isXARArchive. 2016-06-28 14:54:21 -04:00
Russell Hancox
7102e2df4c SNTFileInfo: More speed-ups in hashing, use RDAHEAD, don't use NOCACHE, catch EINTR. 2016-06-28 14:52:28 -04:00
Russell Hancox
c3bd99ff93 santad: Use serial queues instead of NSLock 2016-06-28 14:51:27 -04:00
Russell Hancox
c560405a46 SNTFileInfo: Speed up hashing - increase chunksize, read directly, use fcntl
- Use fcntl to disable cache and issue an advisory read
- Increase default chunk size from 4KB to 256KB
- Use pread to read from file descriptor, rather than make NSData objects

This is ~15% faster.
2016-06-27 17:38:41 -04:00
Russell Hancox
0c0fb28ccc santad: Make argsForPid more reliable and 33% faster 2016-06-27 15:55:18 -04:00
Russell Hancox
a33fce942c santad: Use regex to sanitize incoming strings, which is ~6x faster. 2016-06-27 13:11:15 -04:00
Russell Hancox
369cd40ee5 santad: Also optimize file logging by using NSMutableString 2016-06-27 12:51:29 -04:00
Russell Hancox
577b431a41 santad: Put locks around NSMutableDictionary in SNTEventLog and SNTExecutionController. 2016-06-27 12:48:36 -04:00
Russell Hancox
75cf8acd33 Project: Enable more compiler optimizations.
Specifically, switch from -Os to -Ofast and enable link-time optimization.
2016-06-27 10:08:38 -04:00
Russell Hancox
d70983962b Merge pull request #50 from georgekola/gk-optimize-log1
Optimize logging by using one pre-allocated NSMutableString
2016-06-27 10:05:46 -04:00
Russell Hancox
ff440984b0 Merge pull request #51 from georgekola/gk-optimize-fprintf
Using fwrite as we know the length of string
2016-06-27 09:48:53 -04:00
George Kola
c631155be7 Using fwrite as we know the length of string
It is better to use fwrite as it is generally faster and we are not
using any fprintf feature
2016-06-25 18:50:03 -07:00
George Kola
6038930755 Optimize logging by using one pre-allocated NSMutableString 2016-06-25 18:47:05 -07:00
Tom Burgin
9edc119c62 Merge pull request #49 from russellhancox/kernel-hashtable
santa-driver: Re-implement caching to avoid OSDictionary.
2016-06-20 13:16:09 -04:00
Russell Hancox
269a94bf03 SantaCache: Updates from PR:
+ Added check that per_bucket is >=1 and reduced max from 126 to 64.
+ Added note about cache reset above set method
+ Moved modulo into the hash function
2016-06-20 13:14:50 -04:00
Russell Hancox
7f3e4d7468 santa-driver: Re-implement caching to avoid OSDictionary.
OSDictionary is not well-suited to our needs and locking is quite expensive.
This commit:

  + Replaces all uses of OSDictionary with a new SantaCache class, which
    is a size-limited array hash table with per-bucket locking. It works with
    uint64_t keys, which is perfect for our needs.
  + Adds a unit test for SantaCache.
  + Removes SantaCachedDecision and SantaPIDAndPPID, which only existed
    because OSDictionary can only store OSObject subclasses.
  + Removes a lot of locking logic from SantaDecisionManager as the
    locking is now handled inside SantaCache and is therefore and is
    much more granular.
  + Removes the timed cache expiration for ALLOW decisions. This was
    originally to ensure executions were logged regularly but as we're
    logging all executions nowadays this is longer particularly useful.

SantaCache's configured load factor and hashing function may need tweaking
over-time but this is already a little faster and uses less memory
than what existed before.
2016-06-17 16:39:39 -04:00
Russell Hancox
eb89891cdd Merge pull request #48 from tburgin/tom
Add checkcache command to santactl
2016-06-17 16:17:38 -04:00
Tom Burgin
038b068370 u_int64_t --> uint64_t. CacheCheck --> RemoveFromCache. 2016-06-17 15:53:54 -04:00
Tom Burgin
d2017a59de Get back file status from the kernel cache 2016-06-17 12:45:51 -04:00
Tom Burgin
3435b56a84 Add checkcache command to santactl. It will check to see if the vnode id of a file is in the kernel cache 2016-06-17 12:03:26 -04:00
Russell Hancox
a812558d2d santad: Remove hashes from file write logs 2016-06-16 17:31:40 -04:00
Russell Hancox
aefd85455e Project: s/OS X/macOS/g 2016-06-16 17:31:40 -04:00
Russell Hancox
e42f1347b7 santad: Use IORegistryEntryFromPath instead of IORegistryEntryCopyFromPath.
The latter was only introduced in 10.11. Fixes #47
2016-06-16 17:31:40 -04:00
Russell Hancox
c7442a03d1 santa-driver: Use KAUTH_VNODE_WRITE_DATA instead of KAUTH_FILEOP_CLOSE to catch writes
It turns out that the KAUTH_FILEOP_CLOSE action is not used when the kernel automatically closes file descriptors for exiting processes. Some things, like dd, don't close their file descriptors and let the kernel do it for them which we were previously missing.
2016-06-16 17:31:40 -04:00
Russell Hancox
1eda8bdd9d KernelTests: Add test for overwritten file that was auto-closed 2016-06-16 17:31:37 -04:00
Russell Hancox
c4d0628bdb santad: Increase detail in TTY messages. 2016-06-13 12:38:55 -04:00
Russell Hancox
d51ae66242 santactl: Only resume in CommandContoller when its required, otherwise leave it to individual command 2016-06-10 12:48:54 -04:00
Russell Hancox
121dde6b8b KernelTests: Add cache speed test and secondary client rejection test 2016-06-10 12:48:54 -04:00
Russell Hancox
98081b067d Merge pull request #45 from clburlison/patch-1
Update style guide links
2016-06-09 16:53:54 -04:00
Clayton Burlison
8cc9345b42 Update style guide links 2016-06-09 15:52:48 -05:00
Russell Hancox
f7528365b0 Project: Have rake dist make the correct folder name from the version tag 2016-06-07 12:05:17 -04:00
Russell Hancox
7baa1a345e SNTFileWatcher: Don't call handler on main thread, sleep between handler invocations 2016-06-07 11:40:12 -04:00
Russell Hancox
acf7f4fd52 SantaGUI: Don't reload config file if attributes change (as it will trigger an attribute change) 2016-06-07 11:38:48 -04:00
Russell Hancox
f43e8680b8 santad: Improve SNTFileWatcher, update config file permissions if they change 2016-06-06 16:15:28 -04:00
Russell Hancox
545a6c1b36 santad: Ensure config file reloading is handled on main thread 2016-06-06 11:31:36 -04:00
Russell Hancox
f01fd8c850 Project: Try and fix CocoaPods on Travis 2016-06-03 14:12:03 -04:00
Russell Hancox
c9ec69b0b5 Tests: Fix OCMock misuse in testPreflightDatabaseCounts.
The block expects int64_t, not NSNumber. For some reason this didn't fail in Xcode but does from the command-line. Using OCMOCK_VALUE works properly.
2016-06-03 12:43:03 -04:00
Russell Hancox
3640e2c5f0 santad: Add a workaround for PrinterProxy 2016-06-03 11:32:55 -04:00
Russell Hancox
b3659cb456 santad: Don't spawn an event upload if one for this hash happened in the last 10 minutes 2016-06-01 17:20:16 -04:00
Russell Hancox
76284a2916 santad: Log disk mount/unmount events 2016-06-01 17:20:16 -04:00
Russell Hancox
40b1e011bd SantaGUI/santad: Add option to send bundled binaries to a different detail URL 2016-06-01 17:13:11 -04:00
Russell Hancox
e0bebecd59 santactl/sync: Switch bundle binary uploading
Only upload bundle related events when the server asks for it. Do the search inside a bundle for longer
2016-06-01 17:13:11 -04:00
Russell Hancox
8ac0cf6831 santad: Catch exceptions writing to TTY 2016-06-01 17:13:10 -04:00
Russell Hancox
992163206d Project: Switch to MOLAuthenticatingURLSession Pod. 2016-06-01 17:13:10 -04:00
Russell Hancox
86dd5d8078 santactl/sync: Refactor to reduce repetition, support XSRF tokens and add tests.
Move common request generating and performing code into a common
superclass.
Add code to handle XSSI in JSON responses and support XSRF
tokens via headers.
Adds tests, finally.
Changes preflight hostname to be long instead of short
2016-06-01 17:13:02 -04:00
Russell Hancox
932aa9d052 santad: For single-event syncs, use syslog logging 2016-05-25 17:52:53 -04:00
Russell Hancox
5f7f5204ec santad: Flush cache when switching into lockdown mode 2016-05-25 11:04:53 -04:00
Russell Hancox
a154d23637 SantaGUI: Add customizable notifications when client switches modes. 2016-05-25 11:04:35 -04:00
Russell Hancox
ac2bb9d362 SNTBlockMessage: Move HTML stripping to separate method 2016-05-24 16:32:25 -04:00
Russell Hancox
b918958bfa santactl/fileinfo: Don't fail if santad isn't running, colorize rule output on a TTY. 2016-05-19 19:08:52 -04:00
Russell Hancox
215df4ffa6 santactl: Always try to get daemonConn but only log and exit if it's marked as required 2016-05-19 19:08:52 -04:00
Russell Hancox
bb28bc5875 SNTXPCConnection: Ensure validation completes before returning remoteObjectProxy 2016-05-19 19:08:52 -04:00
Russell Hancox
a82bc3f712 SNTXPCConnection: Don't track accepted connections, it isn't useful. 2016-05-19 19:08:52 -04:00
Russell Hancox
b3a507014b Project: Update to CocoaPods 1.0 2016-05-19 19:08:52 -04:00
Russell Hancox
49c5e35a14 santad: Improve TTY message output.
Bold Santa title, replace <br/> with \n, add link to EventDetailURL
2016-05-19 19:08:03 -04:00
Russell Hancox
869ed33bd4 santactl/fileinfo: Show when code signature is adhoc 2016-05-03 14:15:27 -04:00
Russell Hancox
0c4a9be482 santad: Write message to TTY when blocking execution
Sometimes the GUI isn't running. Sometimes the user is using SSH. Either way, printing a message to the TTY of the parent of the just denied process is user-friendly.
2016-04-28 16:07:36 -04:00
Russell Hancox
4410ec575a santactl/fileinfo: Include rule state info 2016-04-28 16:07:24 -04:00
Russell Hancox
e3b92fc948 santactl/sync: Upload rule counts in preflight 2016-04-28 16:07:04 -04:00
Russell Hancox
4ca4692a67 santactl/flushcache: Disable flushcache in release builds.
It really isn't a useful command outside of development and its existence
seems to confuse people.
2016-04-28 15:00:10 -04:00
Russell Hancox
c1284d3c23 Project: Re-organize file structure, again 2016-04-28 14:11:50 -04:00
Russell Hancox
c8c0eadf72 santactl/fileinfo: Make file type output more accurate for executables 2016-04-28 10:54:54 -04:00
Russell Hancox
f4bbc8abc7 santactl/sync: Log successful stages as debug 2016-04-27 14:41:50 -04:00
Russell Hancox
a0f6ea57f8 SantaGUI: If SilencedNotifications key doesn't exist, create it 2016-04-27 14:19:25 -04:00
Russell Hancox
88d21a07ac santad, santactl/sync: Include Bundle Path in event upload data. 2016-04-26 17:35:29 -04:00
Russell Hancox
88e3a606a0 SNTFileInfo: Use CFBundleDisplayName if available 2016-04-26 17:34:29 -04:00
Russell Hancox
fff693c3f0 santad: Close the FMResultSet left after locking db to prevent spurious messages. 2016-04-26 17:33:43 -04:00
Russell Hancox
1e8d792d39 santa-driver: Flush vnode-pid map periodically. 2016-04-15 17:10:14 -04:00
Russell Hancox
dfb149ac6a santa-driver: Try to get uid/gid from credential if available 2016-04-15 17:05:50 -04:00
Russell Hancox
b5cfc92261 santactl/sync: Check that singleevent took an argument 2016-04-11 17:52:03 -04:00
Russell Hancox
079f3e3868 santactl/sync: Re-organize 'main' to bail earlier if config is invalid 2016-04-11 17:51:47 -04:00
Russell Hancox
15a6d58785 santactl/sync: Add long help, document --clean flag 2016-04-11 17:51:08 -04:00
Russell Hancox
a404498f8a santactl help: If command doesn't have long help, use short help. 2016-04-11 17:40:58 -04:00
Russell Hancox
0d133e2df6 Project: Enable code coverage for "All" test target 2016-04-11 17:40:00 -04:00
Russell Hancox
488b28bfd5 SantaGUI: Log to syslog 2016-04-11 15:15:03 -04:00
Russell Hancox
0fceb7b2e1 SantaGUI: Post notifications to main thread using dispatch_async 2016-04-11 15:14:52 -04:00
Russell Hancox
a79d1a98e7 santactl/fileinfo: Only print "Signing Chain" header if certificates array isn't empty 2016-04-08 16:21:15 -04:00
Russell Hancox
43434fd445 santactl/fileinfo: Don't crash on <512b files 2016-04-08 16:20:49 -04:00
Russell Hancox
492e523884 Project: Move enums in SNTCommonEnums to NS_ENUM, part 2 2016-04-08 15:41:26 -04:00
Russell Hancox
3d1fdb7a2b Project: Move enums in SNTCommonEnums to NS_ENUM, part 1 2016-04-08 15:17:32 -04:00
Russell Hancox
95a4bf0ec7 santad: Ensure launchd/santad rules are whitelisted on every startup
If they weren't already and the database is not new, log an error.
2016-04-08 15:16:12 -04:00
Russell Hancox
0d4f261e14 santad: Have SNTRuleTable return NSError when failing to add rules so user can see why 2016-04-08 15:07:43 -04:00
Russell Hancox
e96288b41b santad: Exclusive-lock rules.db when opening 2016-04-08 12:11:08 -04:00
Russell Hancox
deda1abcf7 SantaGUI: Detect value type for silenced notifications to prevent crashes from bad plist 2016-04-08 10:32:48 -04:00
Russell Hancox
ee79d75483 santad: Set ThrottleInterval to 1s. 2016-04-07 17:03:49 -04:00
Russell Hancox
0e9e445ddf SantaGUI: Reconnect when listener loses a client.
Also move WEAKIFY/STRONGIFY macros into their own header.
2016-04-07 17:03:09 -04:00
Russell Hancox
e64720bcd9 Project: Tell Travis to use xcode7 2016-04-07 15:33:10 -04:00
Russell Hancox
6e27590b57 SantaGUI: Add 'dismiss for a day' checkbox.
Fixes #39.
2016-04-07 14:40:33 -04:00
Russell Hancox
916c3c7a2a SNTXPCConnection: Re-add forced establishment of clients, better tests.
Previously SNTXPCConnection had two-way validation which, due to the method of
implementation, forced a client to connect to a server straight away. Once that
was removed, it meant invalidationHandlers aren't called if either end dies
before the connection is established.

This also puts back the acceptedHandler, which can be used to know when the
connection has finished being established (particularly useful on the server
side), updates the __weak stuff to use WEAKIFY/STRONGIFY macros (and now
actually switch them back to strong within each block) and make the
tests a lot better by using in-process anonymousListener's rather than
lots of mocking.
2016-04-06 23:25:55 -04:00
Russell Hancox
8a5fde8ceb LogicTests: Stop instrumenting program flow, it causes 100s of log lines during tests 2016-04-06 17:30:08 -04:00
Russell Hancox
f5bd9bde7f SantaGUI: Use ultralight system font for title of message window 2016-04-06 15:23:18 -04:00
Russell Hancox
b987f61924 SantaGUI: Fix centering constraint for publisher label 2016-04-06 15:22:21 -04:00
Russell Hancox
482b51a2f9 santactl/sync: Fix userAgent not being set 2016-04-05 14:52:48 -04:00
Russell Hancox
93f2078eda SantaGUI: Fix some constraint bugs in MessageWindow 2016-04-04 18:15:20 -04:00
Russell Hancox
158ae11e61 Tests: Remove old stuff from XPCConnectionTest 2016-04-01 17:53:37 -04:00
Russell Hancox
d282388266 santactl/sync: Release certificate after use 2016-04-01 17:31:24 -04:00
Russell Hancox
6ecdfcba38 santactl/sync: If unable to find client identity, let default handling occur. 2016-04-01 09:54:15 -04:00
Russell Hancox
88dc8a547e README: Add video of block event, as an example 2016-03-31 13:51:35 -04:00
Russell Hancox
58e24b3c11 santagui: Remove old comment, don't activateIgnoringOtherApps twice. 2016-03-31 09:27:38 -04:00
Russell Hancox
5f1b3a2284 santad: Initialize ppath to (null) before calling proc_pidpath 2016-03-30 16:12:52 -04:00
Russell Hancox
31be2584f2 Conf: Use facility instead of sender for santad/santactl logs 2016-03-29 18:00:30 -04:00
Russell Hancox
a2311e5128 santad: Attempt to load santa-driver before connecting to it 2016-03-29 14:08:29 -04:00
Russell Hancox
e94d42187b santactl/sync: Don't log successful stages
Currently a standard succesful sync will print 4 success lines, one for
each stage that was run, followed by a line that the entire sync was
successful. As each stage will also log if it did anything, these
success messages aren't useful. Instead, just log if they failed.
2016-03-25 16:44:06 -04:00
Russell Hancox
2b99cc3f62 Logging: strncmp for binaryName 2016-03-25 14:20:46 -04:00
Russell Hancox
cb7f782893 santad: Fix typo in cert protection error 2016-03-23 17:45:08 -04:00
Russell Hancox
d5a0f8a74b Logging: Remove extraneous ; 2016-03-23 16:46:38 -04:00
Russell Hancox
2ebd71df24 santactl/sync: Fix single-event upload with extra arguments 2016-03-23 16:46:21 -04:00
Russell Hancox
479203f47c santa-driver: Style and type cleanups, inlining some small functions 2016-03-22 15:38:48 -04:00
Russell Hancox
022b9209d9 LogicTests: Delete resources that aren't used anymore. 2016-03-22 15:36:56 -04:00
Russell Hancox
771c2c868f SantaGUI: Increase contrast of user-defined block window messages. 2016-03-21 18:00:02 -04:00
Russell Hancox
5285a728b1 santa-driver: Don't record fileop events from santad 2016-03-21 16:15:20 -04:00
Russell Hancox
41e6583920 SantaGUI: Improve accessiblity of message dialog
+ VoiceOver: add more useful label descriptions
+ VoiceOver: skip some fields
+ Color: increase contrast
2016-03-21 13:49:50 -04:00
Russell Hancox
cbb60b3a05 SantaGUI: Have daemon reply when setting notification listener so GUI can ensure it connected 2016-03-17 17:55:31 -04:00
Russell Hancox
cf1d1e3557 santa-driver: Better handle secondary volumes 2016-03-15 15:10:41 -04:00
Russell Hancox
8f05ee7d79 santa-driver: Rename some action types 2016-03-15 12:53:44 -04:00
Russell Hancox
641bd07c0b Project: New icon 2016-03-14 16:38:07 -04:00
Russell Hancox
7d9dc0a853 Tests: Fix kernel tests 2016-03-14 16:13:28 -04:00
Russell Hancox
e0a46be1b7 santactl/fileinfo: When resolving path, store bundle ref if possible. 2016-03-14 12:55:20 -04:00
Russell Hancox
fd82c67b56 santactl/fileinfo: Add disk image file type 2016-03-14 12:55:20 -04:00
Russell Hancox
f0a83b6f19 santactl/fileinfo: Add simultaneous hashing. 2016-03-14 12:52:25 -04:00
Russell Hancox
736b45bb46 SNTXPCConnection: Remove client validation of server
Now that santad<->SantaGUI work more like the client/server they are,
having an SNTXPCConnection 'client' validate its server is no longer necessary.
Having the validation in the 'server' only simplifies the code.
2016-03-11 17:06:43 -05:00
Russell Hancox
8eae9b7cb7 santad/SantaGUI: Refactor GUI<>santad connection logic and add queuing.
Instead of having santad create a listener for SantaGUI to connect to
and then reverse the client-server relationship, have SantaGUI create an
anonymous listener that it sends to santad using the control interface.

Also add a queue for notifications so that blocks that occur while
SantaGUI isn't running will show up once it starts.
2016-03-11 14:58:12 -05:00
Russell Hancox
0aa2d2c613 santactl/fileinfo: Print useful info when codesign validation fails 2016-03-10 18:23:21 -05:00
Russell Hancox
ad43db10f2 Tests: Attempt to fix FileWatcher tests 2016-03-10 17:17:02 -05:00
Russell Hancox
606f507422 Project: Update CocoaPods 2016-03-10 16:34:08 -05:00
Russell Hancox
36b7778883 LogicTests: Fix SNTXPCConnection test 2016-03-10 15:53:40 -05:00
Russell Hancox
7b032a6a73 Project: Travis, build in local dir instead of DerivedData 2016-03-10 15:53:27 -05:00
Russell Hancox
0e00237e44 Project: Add clang-format file, apply most of the fixes it suggested 2016-03-10 15:53:06 -05:00
Russell Hancox
e9ec9a7d7f santad: Log quarantine URL if one exists.
Fixes #34
2016-03-10 13:24:31 -05:00
Russell Hancox
6834507f3a XPC: Allow multiple XPC clients to a server 2016-03-10 12:21:49 -05:00
Russell Hancox
90e99255b1 santa-driver/santad: Split decision making and logging onto 2 data queues
This resolves an issue where the data queue can be overwhelmed by logging requests and fail to respond to decisions for an extended period of time.
2016-03-10 12:21:17 -05:00
Russell Hancox
b6487000a3 SNTFileInfo: Use NSBundle to find executable path in bundles.
Fixes #37
2016-03-10 12:19:52 -05:00
Russell Hancox
18ce2f72ed Config: Fix config reloading 2016-03-10 12:18:05 -05:00
Russell Hancox
8a2d04bf69 santactl/rule: Fix print error 2016-03-09 15:41:27 -05:00
Russell Hancox
a210ffecec Logging: Create one ASL client per-thread. 2016-03-07 17:31:31 -05:00
Russell Hancox
aff96e8144 Config: Warn if SyncBaseURL is an invalid URL 2016-03-07 12:36:00 -05:00
Russell Hancox
3d4c639bb4 santactl/sync: Fix logic when auto-detecting certificates.
Now, instead of assuming an identity can be found that the server asked
for, look for a chain of certs resulting in an identity that matches the
server's request.
2016-03-07 12:32:32 -05:00
Russell Hancox
d507e79505 santad: Fix quarantine data collection.
This previously didn't work for root (santactl fileinfo was fine)
because quarantine data is per-user.
2016-03-07 12:30:36 -05:00
Russell Hancox
d3e242ff42 Project: Update Travis settings 2016-02-05 19:37:11 -05:00
Russell Hancox
df7616403d SantaGUI: Show entire SHA-256 in fixed-width font 2016-01-14 16:51:29 -05:00
Russell Hancox
962b15517a SantaGUI: Add a transparent button to be the first responder, so tabbing the dialog works. 2015-12-28 17:24:29 -05:00
Russell Hancox
d295f2391f santactl/sync: In --debug log the full NSError for failed requests 2015-12-15 12:36:07 -05:00
Russell Hancox
c042222eea santad: Add user/group info to file changelogs also 2015-12-14 22:32:59 -05:00
Russell Hancox
63f6596bc2 santactl: Rename binaryinfo -> fileinfo. 2015-12-14 18:09:40 -05:00
Russell Hancox
d8a8aba0ea SNTFileInfo: Move machoType method to binaryinfo command, add XAR archive detection. 2015-12-14 17:25:32 -05:00
Russell Hancox
d9d9682029 santactl/sync: Let related-binary search take up to 5s 2015-12-14 16:37:19 -05:00
Russell Hancox
4a27a8ac70 Rakefile: Use Xcode to figure out where built products went, to avoid relying on particular Xcode settings. 2015-12-14 16:36:11 -05:00
Russell Hancox
32857ff304 Project: Apply latest Xcode recommendations 2015-12-14 16:35:34 -05:00
Russell Hancox
375bfd3862 santa-driver: Put locks around vnode_pid_map, use an OSObject subclass to store PID/PPID.
Put a R/W lock around vnode_pid_map_ to prevent use-after-free.
Create SantaPIDAndPPID to use instead of creating and then scanning strings.
Also rename SantaMessage -> SantaCachedDecision, as that's what it is.
2015-12-14 16:34:38 -05:00
Russell Hancox
9430c41b8a santad: Include user and group names in execution logs 2015-12-11 12:58:09 -05:00
Russell Hancox
9b342e146a santactl/sync: Include code sign info with related executables and encode to dict. 2015-12-10 17:37:22 -05:00
Russell Hancox
e5685f2959 santad: Don't try to add empty argument to array when processing execution arguments 2015-12-10 17:02:11 -05:00
Russell Hancox
4150feece2 santactl/sync: When uploading events for bundles, look for other bundled executables.
Many application bundles have related helper tools, which will individually need to be whitelisted unless they're covered by a certificate. To help make user's lives easier, when an event is triggered for a binary inside a bundle look for other executables in the same bundle and upload an event for those too (with an obvious tag) so that the server can let the user vote to whitelist all the binaries together.
2015-12-10 17:01:49 -05:00
Russell Hancox
6879ec5deb santa-driver: in DecisionManager free locks before anything else 2015-12-10 16:56:13 -05:00
Russell Hancox
28ad00ffad SantaGUI: Split block messages into unknown and banned.
This is so that a message can be configured for banned executables without having to provide a custom message for every single one.
2015-12-10 12:13:52 -05:00
Russell Hancox
bf51049fbf santa-driver: Save pid/ppid from VFS context when decision making for use when logging
Previously the execution logging from fileop didn't work when using posix_spawn as proc_selfpid/proc_selfppid still refer to the process calling posix_spawn. We can get the correct pid/ppid from the vfs_context in the vnode scope but we can't log executions from there as the arguments end up being wrong. Instead, save the vnode_id->pid/ppid mapping in the vnode scope and use that in the fileop scope for logging.
2015-12-10 12:12:38 -05:00
Russell Hancox
36189e9122 santad: Update SNTFileInfo to always get strings from bundle Info.plist data.
Also perform a one-time update of any events created before this change.
2015-12-04 13:09:56 -05:00
Russell Hancox
4c747463ac santad: Separate execution requests and logging into separate queues with appropriate priorities. 2015-12-04 12:39:26 -05:00
Russell Hancox
b4b1fbb9e6 santad: Run watchdog thread loop once before sleeping 2015-10-31 14:01:44 -04:00
Russell Hancox
209eaff3c6 SNTFileInfo: Embed SHA hashing loop in an autoreleasepool to avoid temporary RAM spikes 2015-10-31 13:45:47 -04:00
Russell Hancox
c3f70703fd santactl/status: Expose peak CPU/RAM use from santad. 2015-10-29 16:20:57 -04:00
Russell Hancox
f2967e7b94 santad: Switch watchdog CPU counter from rusage to task_info, capture peak CPU/RAM use. 2015-10-29 16:20:25 -04:00
Russell Hancox
77c46b5c43 SNTFileInfo: switch from NSData to NSFileHandle.
This seems to work much better than NSData with either mapped (SIGBUS when file is deleted) or uncached (ballooning memory use) reading.
2015-10-29 16:17:12 -04:00
Russell Hancox
5fda5bc081 santactl/binaryinfo: Only print bundle lines if bundle info is present 2015-10-29 12:35:27 -04:00
Russell Hancox
33a7b38c6a SNTFileInfo: check for NULL ptrs when parsing for embedded plist 2015-10-27 18:35:11 -04:00
Russell Hancox
2a7c0bd58c SNTFileInfo: Go back to using mmap, uncached read balloons memory use 2015-10-27 18:08:16 -04:00
Russell Hancox
86e4d0db0f santactl: Use yyyy instead of YYYY in NSDateFormatter 2015-10-27 17:58:23 -04:00
Russell Hancox
1310fea64d santa-driver: Only try to use/release proc_t if proc_find found it. 2015-10-22 11:29:49 -04:00
Russell Hancox
382f5a5bb9 Merge pull request #30 from stephanemoore/patch-1
Fix application deadlock.
2015-10-22 08:39:54 -04:00
Stephane Moore
ff3303e312 Fix application deadlock.
Fix application deadlock by asynchronously dispatching to the main queue in -[SNTAppDelegate createConnection].
2015-10-21 17:45:59 -07:00
Russell Hancox
6ce0ef62e9 SantaGUI: Ensure connection is only made on main thread 2015-10-15 18:31:07 -04:00
Russell Hancox
2a03341fb6 santad: Add configuration option for turning off PAGEZERO protection. 2015-10-15 18:10:00 -04:00
Russell Hancox
77a55dde56 santad: Catch errors archiving/unarchiving SNTStoredEvent, delete events that fail 2015-10-15 18:09:46 -04:00
Russell Hancox
1a71cdff4a santad/santactl: Report back if rule adding/removing failed rather than assuming success. 2015-10-15 12:15:38 -04:00
Russell Hancox
63f65c51c3 SNTFileInfo: Use NSURL method for getting quarantine data, don't try to use <10.10 2015-10-15 12:14:53 -04:00
Russell Hancox
75de2526c1 santactl/binaryinfo: Only print quarantine fields if they're not empty 2015-10-14 23:37:16 -04:00
Russell Hancox
6fc4b7b120 santactl/binaryinfo: Increase key padding +1 2015-10-14 23:31:50 -04:00
Russell Hancox
7b8068139b santad, santactl/sync: Collect and upload quarantine data with events. 2015-10-14 23:02:20 -04:00
Russell Hancox
ced7de884f santactl/binaryinfo: Add quarantine data to output, add print method to simplify changes. 2015-10-14 20:12:04 -04:00
Russell Hancox
bc51c9f25b SNTFileInfo: Add com.apple.quarantine data accessors for downloaded files. 2015-10-14 20:11:32 -04:00
Russell Hancox
c412e8b9a7 SNTFileInfo: Fix embedded plist parsing, extract into separate method 2015-10-14 20:07:50 -04:00
Russell Hancox
4e0ff224b6 Project: Remove SNTCertificate/SNTCodesignChecker, use new CocoaPod versions 2015-10-12 17:23:42 -04:00
Russell Hancox
61c817c9cb Tests: Fix SNTRuleTable tests 2015-10-09 15:14:15 -04:00
Russell Hancox
2ed384f677 santactl/sync: Only update client mode at end of sync 2015-10-09 13:12:25 -04:00
Russell Hancox
7a851cb080 santad: Typo in comment 2015-10-08 19:54:23 -04:00
Russell Hancox
13aa889633 SNTFileInfo: Add fileSize method, use it in SNTEventLog 2015-10-08 17:57:02 -04:00
Russell Hancox
5c3fba5f41 santad: Prevent user/server from accidentally deleting rules that would kill the system. 2015-10-08 17:45:39 -04:00
Russell Hancox
145d9216bf Project: Don't bother with "xcodebuild clean" for Rakefile clean rule 2015-10-08 17:43:59 -04:00
Russell Hancox
84f46de940 Driver/Daemon: Collect process name in-kernel for file events, parent name for exec requests. For file events log process name and path, if possible. 2015-10-05 17:09:33 -04:00
Russell Hancox
cb9a5b6fbe santactl: Add --json option to both status and version commands. 2015-10-05 14:15:10 -04:00
Russell Hancox
d9718faba4 SNTFileInfo: Return non-embedded dict if locating embedded fails 2015-10-05 14:13:40 -04:00
Russell Hancox
5472ff41f0 santactl/status: Show timezone as UTF offset rather than name 2015-10-05 13:00:55 -04:00
Russell Hancox
4f94c3b310 santactl/status: Use fixed format for sync date output but still include TZ. 2015-10-03 19:57:19 -04:00
Russell Hancox
420f1efa50 santad: For file write events, print process name as well as pid. 2015-10-03 18:16:06 -04:00
Russell Hancox
5d2ce17817 santactl/status: When printing last sync date, use local timezone and locale settings 2015-10-03 18:15:41 -04:00
Russell Hancox
053cb823a1 santa-driver: Change C++ std to C++11
This is mostly just to quiet the warning about override not being set on getMetaClass, which is part of the OSDeclareDefaultStructors macro.
2015-10-03 18:15:11 -04:00
Russell Hancox
18a7992372 Config: Add more protected keys, only protect if a server is set 2015-10-02 16:35:30 -04:00
Russell Hancox
9e935f5bfb GUI: Include CFBundleName as first item in UI, if available. 2015-10-01 18:53:58 -04:00
Russell Hancox
9f49e24dc5 santad: Update file changes logging to use a configurable regex 2015-10-01 17:57:07 -04:00
Russell Hancox
dbf60f16bc santactl/sync: Fix typo causing clean sync on every run 2015-09-30 16:00:39 -04:00
Russell Hancox
0f3a228788 santactl/rule: Make help text a little clearer 2015-09-28 17:46:30 -04:00
Russell Hancox
d905f5b095 santactl/rule: Add ability to add certificate rules. Re-write argument parsing. 2015-09-28 17:20:34 -04:00
Russell Hancox
1c310486c7 santactl/status, santad: Show watchdog events in status output 2015-09-28 16:41:33 -04:00
Russell Hancox
4b01c6da91 santactl/status: Report some sync statuses. 2015-09-28 16:14:45 -04:00
Russell Hancox
5782378616 santactl/sync, santad: Add clean sync and last success options, use to initiate clean sync when database is re-created 2015-09-28 16:11:17 -04:00
Russell Hancox
64c97ebfba santad: If database open fails, delete and re-create. 2015-09-28 16:09:05 -04:00
Russell Hancox
5fd4d56b00 santactl/sync: Add ability to sync blacklist regex 2015-09-28 16:08:11 -04:00
Russell Hancox
e658b5167e Project: Update README a little 2015-09-24 18:15:03 -04:00
Russell Hancox
cea698d720 SNTCertificate: Add serialNumber and isCa properties. 2015-09-21 17:48:47 -04:00
Russell Hancox
c07f41c312 santad: Stop closing stdout/stderr 2015-09-21 15:59:32 -04:00
Russell Hancox
a837aa0334 santactl/status: Use dispatch group instead of sleeping 2015-09-21 15:59:20 -04:00
Russell Hancox
0050724e22 SNTXPCConnection: Use semaphore instead of variable & sleep. 2015-09-21 15:58:54 -04:00
Russell Hancox
adac4ac75c SantaGUI: windowWillClose and orderOut are being marked nonnull 2015-09-21 15:51:36 -04:00
Russell Hancox
718f37024a SNTConfigurator: Use NSPropertyListImmutable instead of kCFPropertyListImmutable 2015-09-21 15:51:03 -04:00
Russell Hancox
fcb3008539 Rakefile: Handle xcpretty missing better 2015-09-21 15:50:22 -04:00
Russell Hancox
8faf3eec53 santactl/sync: Validate incoming rules better 2015-09-16 15:59:50 -04:00
Russell Hancox
2bc3df3255 santad: Stop using mmap while reading files, it can be forced to crash by truncating the file. 2015-09-16 15:52:49 -04:00
Russell Hancox
5b0e550c85 santad: Add BlacklistRegex option, log a useful explanation when decision is made by scope 2015-09-16 14:19:33 -04:00
Russell Hancox
e52211abf2 santa-driver: Release proc_t acquired with proc_find. 2015-09-15 17:23:07 -04:00
Russell Hancox
9b6f231b34 santa-driver: Check for daemon earlier in FetchDecision 2015-09-14 18:20:33 -04:00
Russell Hancox
b71223705f santa-driver: If daemon fails to provide a response, print the path of the files it failed on 2015-09-14 18:19:56 -04:00
Russell Hancox
863fbe69bb santa-driver: Simplify AddToCache's locking 2015-09-14 18:19:28 -04:00
Russell Hancox
2d46279961 santa-driver: Use 0 as the client_pid when not connected 2015-09-14 18:18:51 -04:00
Russell Hancox
0d0207d77f santa-driver: lck_attr and lck_grp_attr need freeing 2015-09-14 18:18:20 -04:00
Russell Hancox
00bbade34f santa-driver: ClientConnected() should check if process is exiting/dying. 2015-09-14 18:08:57 -04:00
Russell Hancox
682f741ddc santad: Separate uid/gid fields in log. 2015-09-11 11:35:14 -04:00
Russell Hancox
3d2744c9e3 santactl/sync: Use lib compression for both preflight and event upload phases 2015-09-09 17:13:38 -04:00
Russell Hancox
cc286dcf16 santad: Fix event storage 2015-09-09 17:13:21 -04:00
Russell Hancox
27c6e2a7bd santa-driver: Don't send file mod messages unless daemon is connected 2015-09-09 14:22:31 -04:00
Russell Hancox
72c7a67ad5 Logging: Limit kernel messages to those actually sent by the kernel 2015-09-09 13:34:30 -04:00
Russell Hancox
8fe5e4e238 Logging: Update logMessage to use asl directly, adding a facility 2015-09-09 11:56:53 -04:00
Russell Hancox
02f23d0c62 santad: Add LogFileChanges option, remove LogAllEvents, fix key protection 2015-09-09 11:56:31 -04:00
Russell Hancox
ff6f4d4152 Common: Update SNTRule and SNTStoredEvent isEqual/hash/description methods. 2015-09-08 16:35:50 -04:00
Russell Hancox
2242f46792 Conf: Don't roll logs too regularly 2015-09-08 16:34:38 -04:00
Russell Hancox
642b5609b2 Tests: Fix tests after adding file write logging 2015-09-08 16:34:21 -04:00
Russell Hancox
98878f3e7c Kernel/santad: Add file write logging and exec argv's.
This necessitated a large refactoring of a bunch of code, hence being a large commit. This moves all event logging into a separate class, moves logging of executions to be from FileOp events rather than Vnode events (so we can get the argv after the execve call has finished) and implements the logging of cached execs.
2015-09-08 16:33:59 -04:00
Russell Hancox
3eb28deccf santa-driver: Verify input args are not nullptr's. 2015-09-08 14:41:34 -04:00
Russell Hancox
761a852156 santad: Always request sizeof(santa_message_t) regardless of previous message size 2015-09-08 14:40:50 -04:00
Russell Hancox
f4ddb11c1f santad: Force database permissions on startup 2015-09-08 14:33:25 -04:00
Russell Hancox
75158c11ea santa-driver: Don't create santa_message_t structs on the stack.
Also rename userId field to uid and add gid field to match
2015-08-31 15:21:25 -04:00
Russell Hancox
fe96706b0c KernelTests: Always unload kext and cleanup tmp after running 2015-08-27 18:03:40 -04:00
Russell Hancox
b87482e824 santad: Move page zero check to after binary/cert rule checks so 'bad' binaries can be whitelisted and notifications will be generated when they're blocked 2015-08-27 15:25:13 -04:00
Russell Hancox
a9ba99dc79 SNTFileInfo: Re-write mach header parsing 2015-08-27 15:25:12 -04:00
Russell Hancox
8884e92a1a Tests: Add test for missing/bad pagezero 2015-08-27 15:25:12 -04:00
Russell Hancox
6385514257 santad: Block 32-bit binaries with missing/invalid page zero 2015-08-27 15:25:12 -04:00
Russell Hancox
d3ad47022b Conf: Change log time format to ISO8601Z.3 2015-08-27 15:25:01 -04:00
Russell Hancox
138d4b507d SantaGUI: Fix fast-user-switching support. 2015-08-18 17:00:38 -04:00
Russell Hancox
3c0b195bcf Update travis.yml to add Cocoapod caching 2015-08-07 17:27:15 -04:00
Russell Hancox
d941a71bb5 Package: Forcibly make santactl symlink 2015-08-05 16:19:37 -04:00
Russell Hancox
08697d9daf KernelTests: Fix lots-of-executions test 2015-08-05 15:59:41 -04:00
Russell Hancox
8959871988 Rakefile: Clean before dist 2015-08-05 15:59:34 -04:00
Russell Hancox
bb43a04992 SNTFileInfo: Always try to get embedded info.plist before bundle plist 2015-08-05 12:01:05 -04:00
Russell Hancox
5f93dc7991 Project: Stop trying to be smart with logging destinations 2015-08-04 18:13:04 -04:00
Russell Hancox
9be8eb223c KernelTests: Stop blocking ps while tests are running, block ed instead. 2015-08-04 17:13:35 -04:00
Russell Hancox
e8b6c47e0f KernelTests: Remove timeout, chdir to tmp dir before executing, add lots-of-binaries test 2015-08-04 17:13:20 -04:00
Russell Hancox
697d442afb Project: Update Mac OS X -> OS X. 2015-08-04 13:54:55 -04:00
Russell Hancox
5dbd261b5a GUI: Allow selection of all fields and add ppid to end of parent name. 2015-08-04 13:53:47 -04:00
Russell Hancox
9bc94ca658 GUI: Add defaultBlockMessage configuration 2015-08-04 13:52:44 -04:00
Russell Hancox
4404b5f849 santactl/sync: Default to ephemeralSessionConfiguration to avoid caching 2015-08-03 18:03:51 -04:00
Russell Hancox
6a4b73b8a9 santa-driver: Before posting request to santa, ensure it exists in the cache already 2015-08-03 18:02:57 -04:00
Russell Hancox
b6146224b3 santa-driver: Make "cache too large" log info instead of debug 2015-08-03 18:02:34 -04:00
Russell Hancox
e3593c1b0c santad: fclose stderr for santactl sync too 2015-07-22 16:35:25 -04:00
Russell Hancox
90a2f10da6 santactl/rule: Print usage when args are bad, catch missing long arguments.
Fixes #20
2015-07-22 13:48:43 -04:00
Russell Hancox
60bab1c004 Rakefile: Don't miss santad/santactl dSYMs 2015-07-21 15:22:14 -04:00
Russell Hancox
0898940d0b santad: Pass santa_message_t straight to SNTExecutionController 2015-07-21 14:52:53 -04:00
Russell Hancox
38b65b0ca4 santad: Move uid->username lookup to where it's actually used 2015-07-21 14:52:53 -04:00
Russell Hancox
d36ce5eefc KernelTests: Update comments, add extra write-to-cached-file check 2015-07-21 14:52:48 -04:00
Russell Hancox
ff99ab9cfe santad: loggedInUsers:sessions: style clean-up 2015-07-21 14:22:42 -04:00
Russell Hancox
64995367c3 santad: Simplify eventStateForDecision:type: 2015-07-21 14:22:42 -04:00
Russell Hancox
c67f0ffc11 santad: Don't initiate event upload if syncing isn't enabled 2015-07-21 14:22:42 -04:00
Russell Hancox
d5403ae112 santa-driver: Move vnode-id and vnode-id-str fetching to avoid duplication 2015-07-21 14:22:42 -04:00
Russell Hancox
d21d64cbfe santad: Don't print log format every startup 2015-07-21 14:22:42 -04:00
Russell Hancox
347ee3c4f5 Merge pull request #19 from samsymons/podfile-fix
Fix an installation error with CocoaPods.
2015-07-21 14:20:41 -04:00
Sam Symons
77ed1cca29 Fix an installation error with CocoaPods.
In the later versions of CocoaPods, the `project` method was replaced
with `pods_project`. This was preventing the post_install hooks from
being run.
2015-07-21 10:41:03 -07:00
Russell Hancox
cfac7dbb37 Logging: Fix syslog logging and file rotation 2015-07-17 17:43:04 -04:00
Russell Hancox
f27d72f3f9 Tests: Update tests for new error argument to SNTFileInfo 2015-07-17 12:59:48 -04:00
Russell Hancox
3cd93b287e santa-driver: Simplify kauth callbacks, moving most of the logic to methods on SDM 2015-07-16 22:33:24 -04:00
Russell Hancox
5e5605881b santa-driver: remove client_proc, use ClientConnected() instead 2015-07-16 22:32:17 -04:00
Russell Hancox
a9b48610df santa-driver: Clear data queue if client crashes. Restore dataqueue lock, the docs lied. 2015-07-16 22:31:31 -04:00
Russell Hancox
3cca09a48c santa-driver: Remove request loops in GetFromDaemon
For large binaries it poses a risk of being overrun and as santad is working pretty reliably it's almost certainly not necessary anymore.
2015-07-15 18:55:05 -04:00
Russell Hancox
3134448eac santad: Close password database after getpwuid 2015-07-15 18:25:26 -04:00
Russell Hancox
663bdf945b KernelTest: Update for EACCES -> EPERM change 2015-07-15 18:24:13 -04:00
Russell Hancox
e94d1175e7 santad: If file can't be hashed, log an error and allow execution. 2015-07-13 11:20:39 -04:00
Russell Hancox
e20b761965 santa-driver: Change rejection errno to EPERM 2015-07-01 18:55:04 -04:00
Russell Hancox
90c64812d0 santad: close stdout before running santactl sync 2015-07-01 17:22:40 -04:00
Russell Hancox
08d368fc49 santad: Rename watchdog thread with reverse-dns name 2015-06-26 16:29:46 -04:00
Russell Hancox
39385f0bff santad: Put an autoreleasepool inside the watchdog thread. 2015-06-26 13:12:46 -04:00
Russell Hancox
8bc3418ce1 santad: Watchdog: only log memory use if it increased since last check. Increase threshold to 250MB 2015-06-25 17:58:17 -04:00
Russell Hancox
a145700398 Rakefile: Properly unload/load GUI during build. 2015-06-25 17:52:59 -04:00
Russell Hancox
409535e617 santactl: Style, indenting. 2015-06-25 11:04:57 -04:00
Russell Hancox
f625016efe santactl/sync: When rejecting a redirect, cancel the task to avoid hanging the task until timeout 2015-06-24 17:32:35 -04:00
Russell Hancox
f4c94ab1d7 santactl/sync: Failed log upload should not fail whole sync 2015-06-24 17:21:54 -04:00
Russell Hancox
8234706dd3 santad: Vacuum event database after removing multiple events. 2015-06-24 11:58:38 -04:00
Russell Hancox
1a31dc870f Merge branch 'whitelistscope'
* whitelistscope:
  Common: Rename kWhitelistDirsKey/WhitelistDirs to kWhitelistRegexKey/WhitelistRegex
  santad: NSRegularExpression doesn't work with XPC.
  santactl/sync: Sync whitelist regex
  santad: In a rule vs scope, rule wins.
  santad: Move whitelisted dirs feature to using regex instead of array. Faster and more flexible.
  santactl: Update help/error wordings
  santad: Add whitelisted directory support
2015-06-23 18:31:03 -04:00
Russell Hancox
a1712858c5 Common: Rename kWhitelistDirsKey/WhitelistDirs to kWhitelistRegexKey/WhitelistRegex 2015-06-23 18:30:42 -04:00
Russell Hancox
0059e768b9 Common: Add __NSString__ attribute to logMessage to catch format string bugs. Fix some warnings that found. 2015-06-23 18:10:46 -04:00
Russell Hancox
4fe1550bd2 santad: NSRegularExpression doesn't work with XPC. 2015-06-23 18:09:35 -04:00
Russell Hancox
0c182c8a7f santactl/sync: Sync whitelist regex 2015-06-23 17:33:43 -04:00
Russell Hancox
bcdf746def santad: In a rule vs scope, rule wins. 2015-06-23 17:33:08 -04:00
Russell Hancox
bc13ac3a98 santad: Move whitelisted dirs feature to using regex instead of array. Faster and more flexible. 2015-06-23 17:22:18 -04:00
Russell Hancox
a894e018cd santactl: Update help/error wordings 2015-06-23 17:21:17 -04:00
Russell Hancox
cbecfd444d santad: Add whitelisted directory support 2015-06-23 17:21:17 -04:00
Russell Hancox
357e5ef963 santactl/sync: NSString doesn't have an unsignedIntegerValue method 2015-06-23 17:14:47 -04:00
Russell Hancox
60594c9f03 santad/santactl-sync: Accept backoff interval from server, disable event uploads if back off is used, re-enable on next sync. 2015-06-23 15:54:30 -04:00
Russell Hancox
44b5bae8da santad: Add sync execution timer to santad 2015-06-23 15:52:39 -04:00
Russell Hancox
2e856196c5 santad: Move SIGCHLD SIG_IGN setting to main(), it doesn't need to be set repeatedly. 2015-06-23 15:36:59 -04:00
Russell Hancox
8672187c02 SantaGUI: Add keepalive to launchagent plist 2015-06-23 11:20:20 -04:00
Russell Hancox
cf251c45b8 Project: Update package Makefile for santad/santactl move 2015-06-22 15:57:10 -04:00
Russell Hancox
385c03096d Project: Missed santactl/santad move in Rakefile dist command 2015-06-22 15:35:03 -04:00
Russell Hancox
f323f5e3de santad: Up watchdog interval to 60s and CPU threshold to 20%.
Whilst during normal operation santad doesn't use more than 5% CPU, it does spike if lots
of processes start, such as during bootup. This change helps to reduce the noise.
2015-06-22 15:28:02 -04:00
Russell Hancox
9562ee86cd Project: Add missing copy to a few properties previously missed 2015-06-19 17:32:45 -04:00
Russell Hancox
adfb4bc861 SNTFileInfo: Better caching of properties 2015-06-19 17:31:48 -04:00
Russell Hancox
957232ca40 santactl: Fix event counting bug in status command 2015-06-16 18:02:41 -04:00
Russell Hancox
44c9d9aead santad: Add watchdog thread to print warnings if CPU/RAM usage seem high. 2015-06-15 16:31:55 -04:00
Russell Hancox
f95245cedd 10.11 Prep: Move santad,santactl from /usr/libexec,/usr/sbin to within santa-driver.kext 2015-06-15 16:18:51 -04:00
Russell Hancox
3c034adf48 GUI: Prevent reconnection loop when XPC connection dies 2015-06-10 16:46:32 -04:00
Russell Hancox
abd3c5a06d GUI: Restore constraint move Dismiss button when event detail URL is not set 2015-06-10 16:45:16 -04:00
Russell Hancox
ca4951a475 SNTFileWatcher: Update test file location 2015-06-09 13:50:43 -04:00
Russell Hancox
e751a3d307 SNTFileWatcher: Only get the fileSystemRepresentation once, to avoid high memory use when file doesn't exist 2015-06-09 13:10:29 -04:00
Russell Hancox
2a8bdfd714 santad: Use _exit instead of exit after fork. Oops. 2015-06-01 17:12:12 -04:00
Russell Hancox
be9dca3ee2 GUI: Add close button to About window. 2015-05-21 16:12:48 -04:00
Russell Hancox
32707fb501 santa-driver: Fix rare panic in CacheCheck where lock upgrade fails.
lck_rw_lock_shared_to_exclusive can return false if a previous reader upgraded. The result is the lock being unlocked and the panic is caused when unlocking a lock that isn't locked.
2015-05-20 11:13:19 -04:00
Russell Hancox
d72547e187 Project: Simplify package download URL generation in pkg Makefile 2015-05-18 18:36:30 -04:00
Russell Hancox
9150ddffb1 Project: Fix broken curl command in pkg Makefile 2015-05-18 17:52:33 -04:00
Russell Hancox
d5c1d66c2f KernelTests: Update tests for dataqueue usage changes 2015-05-18 16:39:27 -04:00
Russell Hancox
536b8969ed santactl/sync: LogUpload - logsToUpload is part of the same class, use self. 2015-05-18 16:31:43 -04:00
Russell Hancox
0db3b6d955 santactl/sync: Split out Log Upload request generation and Rule Download rule parsing from main request methods. 2015-05-18 14:33:21 -04:00
Russell Hancox
78bb9a1bd6 common: Correct comment on default loglevels 2015-05-18 13:06:31 -04:00
Russell Hancox
567e0b6431 santad: If exiting because of a failed dequeue, log the return code at ERROR level. 2015-05-18 13:06:31 -04:00
Russell Hancox
f2f27c5675 santa-driver: Up ACTION_REQUEST_SHUTDOWN from 60->90 2015-05-18 13:06:31 -04:00
Russell Hancox
5a7ac2287b santa-driver: Stop defining MAX_PATH_LEN, use MAXPATHLEN instead.
I can't recall why I did this.
2015-05-18 13:06:31 -04:00
Russell Hancox
f82da21b75 santactl/sync: Bug from 8cd9898, call completion handler even if no rules are downloaded. 2015-05-15 10:38:26 -04:00
Russell Hancox
969a5ef94e santactl/sync: Don't release SecAsn1Coder until we're done with its data. 2015-05-14 17:35:06 -04:00
Russell Hancox
fd7ad07193 santactl/sync: Handle DER decoding failures more gracefully. 2015-05-14 17:01:49 -04:00
Russell Hancox
3f5400b264 santa-driver: Split FetchDecision, notify daemon of missed executions, reorganize some methods. 2015-05-14 17:01:49 -04:00
Russell Hancox
466b5ed491 santa-driver: Make SantaDecisionManager::AddToCache default the microsecs parameter to the current uptime 2015-05-14 17:01:48 -04:00
Russell Hancox
25f1b71f10 santa-driver: Move dataqueue to SantaDecisionManager rather than
recreating it on every connect.
2015-05-14 17:01:48 -04:00
Russell Hancox
d1295f97b9 santa-driver: Rename owning_pid/proc to client_pid/proc. Minor style
cleanup
2015-05-14 17:01:48 -04:00
Russell Hancox
f5eb274aa0 santa-driver: remove unnecessary dataqueue lock 2015-05-14 17:01:48 -04:00
Russell Hancox
58b9dab74f santa-driver: SDM should call super::init 2015-05-14 17:01:48 -04:00
Russell Hancox
9f6b6d10dc santa-driver: Make room in santa_message_t->path for the terminator so we don't miss a character. 2015-05-14 17:01:47 -04:00
Russell Hancox
57f6e516c2 santa-driver: Mark all overriden methods with 'override' 2015-05-14 17:01:42 -04:00
Russell Hancox
8cd9898cf3 santactl/sync: Don't send empty rules array to daemon. 2015-05-11 16:27:02 -04:00
Russell Hancox
d53b04213a santa-driver: Remove empty spacing at end of line 2015-05-08 14:58:16 -04:00
Russell Hancox
ac99bd1070 santad: Add tests for SNTRuleTable 2015-05-08 14:57:53 -04:00
Russell Hancox
30df44df96 santad: Correctly delete corrupt events. Add test for this. 2015-05-08 14:57:37 -04:00
Russell Hancox
fc55b86f30 santad: Switch to uint32_t for table version numbers 2015-05-08 14:56:20 -04:00
Russell Hancox
59ffb67554 santad: Reject addRules requests with empty/nil array. Also switch to NSUInteger for rule counts. 2015-05-08 14:55:28 -04:00
Russell Hancox
d46b156b85 santa-driver: vnode_getattr sometimes panics if a vfs_context isn't available (when used with osxfuse for instance). 2015-05-07 18:24:29 -04:00
Russell Hancox
6492e70599 santactl: Instead of rejecting authentication challenges and trying again, cancel the whole request. 2015-05-07 17:56:47 -04:00
Russell Hancox
bc5d0f8685 santa-driver: Don't allow StopListener to return until both kauth scopes are done 2015-05-06 14:57:33 -04:00
Russell Hancox
838da16da1 santad: Delete events that fail to unarchive 2015-05-06 14:44:09 -04:00
Russell Hancox
6e242bf98d SantaGUI: Change Line to custom NSBox 2015-05-05 17:44:06 -04:00
Russell Hancox
be1e66c29d Project: Enable more warnings and then fix them. 2015-05-01 17:40:39 -04:00
Russell Hancox
57866308e3 santad: Consider scripts that are part of installer packages as in-scope. 2015-04-30 18:37:04 -04:00
Russell Hancox
63bc8fca2d santad: Don't post GUI notification for SILENT_BLACKLIST rules. 2015-04-30 18:36:43 -04:00
Russell Hancox
408712f00f santad: Don't log when client mode is not set in defaultDecision, as that can't actually happen. 2015-04-30 18:36:20 -04:00
Russell Hancox
8cb6046f94 GUI: Add parent process name, only show part of the shasum, resize window. 2015-04-30 18:24:54 -04:00
Russell Hancox
297fb4cb68 Add parent process name collection and upload 2015-04-30 18:21:13 -04:00
Russell Hancox
1501d413f0 Project: Add install.sh script and package Makefile
Adds an install.sh script that can be run from the distribution tarball
and a Luggage package makefile
2015-04-30 14:12:02 -04:00
Russell Hancox
e747ace0f3 santactl/binaryinfo: Add bundle details to file info 2015-04-30 12:34:31 -04:00
Russell Hancox
6b96f36b2b SantaGUI: Re-create AboutWindowController each time it's needed so that More Info button state is correct 2015-04-22 15:59:04 -04:00
Russell Hancox
f16fa691b5 santactl: include zlib.h instead of import 2015-04-21 16:43:09 -04:00
Russell Hancox
4fd5e1139f Project: Style clean-ups 2015-04-21 14:29:30 -04:00
Russell Hancox
0b33079833 Merge pull request #16 from marczak/readme-up
Updated README to clarify intentions and expectations
2015-04-21 09:14:50 -07:00
Edward Marczak
6069ed5801 Update README.md 2015-04-21 12:14:05 -04:00
Edward Marczak
c2a9061ea2 Updated README to clarify expectations. 2015-04-21 11:24:58 -04:00
Russell Hancox
ee963d62a4 Project: Update README to mention dyld issue 2015-04-21 10:02:32 -04:00
Russell Hancox
c12adbc8e6 Project: Update schemes to make Xcode be quiet. 2015-04-20 18:35:10 -04:00
Russell Hancox
e6b20bcce6 Project: update Podfile.lock 2015-04-20 18:07:33 -04:00
Russell Hancox
10333bba01 santa-driver: Change file-write cache check, use FileOp scope for most writes and use hasdirtyblks to catch flushed but still-open files. 2015-04-20 18:07:22 -04:00
203 changed files with 13514 additions and 6338 deletions

22
.clang-format Normal file
View File

@@ -0,0 +1,22 @@
BasedOnStyle: Google
Language: Cpp
Standard: Cpp11
# Disable ColumnLimit because it causes some very weird line breaks.
# For ObjC the limit is 100
# For Cpp the limit is 80
ColumnLimit: 0
# Allow short case statements to be on a single line
AllowShortCaseLabelsOnASingleLine: true
# Ban short loops and functions on a single line
AllowShortLoopsOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
# Allow spaces in NSArray/NSDictionary literals @[ and @{
SpacesInContainerLiterals: true
# For pointers, always put the * next to the variable name.
DerivePointerAlignment: false
PointerAlignment: Right

3
.gitignore vendored
View File

@@ -1,8 +1,9 @@
.DS_Store
Build
Dist
santa-*
Pods
Santa.xcodeproj/xcuserdata
Santa.xcodeproj/project.xcworkspace
Santa.xcworkspace/xcuserdata
Santa.xcworkspace/xcshareddata
Source/DevelopmentTeam.xcconfig

View File

@@ -1,8 +1,14 @@
---
language: objective-c
cache:
- bundler
- cocoapods
sudo: false
osx_image: xcode7
before_install:
- gem install cocoapods xcpretty
- pod setup >/dev/null
script:
- xcodebuild -workspace Santa.xcworkspace -scheme All build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}
- xcodebuild -workspace Santa.xcworkspace -scheme All -derivedDataPath build build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}

View File

@@ -29,8 +29,8 @@ rake tests:kernel # only necessary if you're changing the kext code
All code submissions should try to match the surrounding code. Wherever possible,
code should adhere to either the
[Google Objective-C Style Guide](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml)
or the [Google C++ Style Guide](http://google-styleguide.googlecode.com/svn/trunk/cppguide.html).
[Google Objective-C Style Guide](https://google.github.io/styleguide/objcguide.xml)
or the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html).
### The small print
Contributions made by corporations are covered by a different agreement than

85
Conf/Package/Makefile Normal file
View File

@@ -0,0 +1,85 @@
#
# Package Makefile for Santa
# Requires TheLuggage (github.com/unixorn/luggage) to be installed
#
# Will generate a package based on the latest release. You can replace
# the PACKAGE_VERSION variable with a specific variable instead if you wish.
#
LUGGAGE:=/usr/local/share/luggage/luggage.make
include ${LUGGAGE}
TITLE:=santa
REVERSE_DOMAIN:=com.google
# Get latest Release version using the GitHub API. Each release is bound to a
# git tag, which should always be a semantic version number. The most recent
# release is always first in the API result.
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
# Get the download URL for the latest Release. Each release should have a
# tarball named santa-$version.tar.bz2 containing all of the files associated
# with that release. The tarball layout is:
#
# santa-$version.tar.bz2
# +--santa-$version
# |-- binaries
# | |-- santa-driver.kext
# | |-- Santa.app
# |-- conf
# | |-- install.sh
# | |-- com.google.santad.plist
# | |-- com.google.santagui.plist
# | +-- com.google.santa.asl.conf
# +--dsym
# |-- santa-driver.kext.dSYM
# |-- Santa.app.dSYM
# |-- santad.dSYM
# +-- santactl.dSYM
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
pack-applications-Santa.app \
pack-Library-LaunchDaemons-com.google.santad.plist \
pack-Library-LaunchAgents-com.google.santagui.plist \
pack-etc-asl-com.google.santa.asl.conf \
pack-script-preinstall \
pack-script-postinstall
santa-driver.kext: download
Santa.app: download
com.google.santad.plist: download
com.google.santagui.plist: download
com.google.santa.asl.conf: download
download:
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
@rm -rf *.dSYM
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
@sudo mkdir -p ${WORK_D}/private/etc/asl
@sudo chown root:wheel ${WORK_D}/private/etc/asl
@sudo chmod 755 ${WORK_D}/private/etc/asl
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
@sudo mkdir -p ${WORK_D}/Library/Extensions
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
@sudo chown -R root:wheel ${WORK_D}/Library/Extensions/santa-driver.kext
@sudo chmod -R 755 ${WORK_D}/Library/Extensions/santa-driver.kext
clean: myclean
myclean:
@rm -rf *.dSYM
@rm -rf Santa.app
@rm -rf santa-driver.kext
@rm -f config.plist
@rm -f com.google.santa.asl.conf
@rm -f com.google.santad.plist
@rm -f com.google.santagui.plist
@rm -f install.sh
@rm -f uninstall.sh

28
Conf/Package/postinstall Normal file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
# Load the kernel extension, santad, sync client
# If a user is logged in, also load the GUI agent.
# If the target volume is not /, do nothing
[[ $3 != "/" ]] && exit 0
# Restart syslogd to pick up ASL configuration change
/usr/bin/killall -HUP syslogd
/sbin/kextload /Library/Extensions/santa-driver.kext
sleep 1
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
sleep 1
# Create hopefully useful symlink for santactl
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
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
exit 0

26
Conf/Package/preinstall Normal file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# Unload the kernel extension, santad, sync client
# If a user is logged in, also unload the GUI agent.
# If the target volume is not /, do nothing
[[ $3 != "/" ]] && exit 0
/bin/launchctl remove com.google.santad
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Remove cruft from old Santa versions
/bin/rm /usr/libexec/santad
/bin/rm /usr/sbin/santactl
/bin/launchctl remove com.google.santasync
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist
sleep 1
user=$(/usr/bin/stat -f '%u' /dev/console)
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
exit 0

View File

@@ -1,4 +1,6 @@
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
? [S= Message santa-driver:] claim only
? [S= Message santa-driver:] file /var/log/santa.log format="[$((Time)(utc.3))] $Message"
> /var/log/santa.log mode=0644 rotate=seq compress file_max=5M all_max=100M
> /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/db/santa/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/db/santa/santa.log

View File

@@ -6,24 +6,21 @@
<string>com.google.santad</string>
<key>ProgramArguments</key>
<array>
<string>/usr/libexec/santad</string>
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>SantaXPCNotifications</key>
<true/>
<key>SantaXPCControl</key>
<true/>
<key>SantaXPCControl</key>
<true/>
</dict>
<key>StandardOutPath</key>
<string>/var/log/santa.log</string>
<key>StandardErrorPath</key>
<string>/var/log/santa.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true />
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -7,8 +7,11 @@
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
<string>--syslog</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santasync</string>
<key>ProgramArguments</key>
<array>
<string>/usr/sbin/santactl</string>
<string>sync</string>
</array>
<key>StandardErrorPath</key>
<string>/var/log/santa.log</string>
<key>ProcessType</key>
<string>Background</string>
<key>StartInterval</key>
<integer>600</integer>
</dict>
</plist>

59
Conf/install.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" 1>&2
exit 1
fi
if [[ -d "binaries" ]]; then
SOURCE="."
elif [[ -d "../binaries" ]]; then
SOURCE=".."
else
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
exit 1
fi
# Determine if anyone is logged into the GUI
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Unload santad and scheduled sync job.
/bin/launchctl remove com.google.santad >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
# Unload GUI agent if someone is logged in.
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
# Cleanup cruft from old versions
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist >/dev/null 2>&1
/bin/rm /usr/libexec/santad >/dev/null 2>&1
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
# 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
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
# Reload syslogd to pick up ASL configuration change.
/usr/bin/killall -HUP syslogd
# Load kext.
/sbin/kextload /Library/Extensions/santa-driver.kext
# Load santad and scheduled sync jobs.
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
# Load GUI agent if someone is logged in.
[[ -n "$GUI_USER" ]] && \
/bin/launchctl asuser ${GUI_USER} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
exit 0

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

54
Podfile
View File

@@ -2,23 +2,53 @@ platform :osx, "10.9"
inhibit_all_warnings!
target :Santa do
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
end
target :santad do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
target :santabs do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
end
end
post_install do |rep|
rep.project.targets.each do |target|
target.build_configurations.each do |config|
if config.name != 'Release' then
break
end
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
target :santactl do
pod 'FMDB'
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'MOLFCMClient', '~> 1.3'
end
target :LogicTests do
pod 'FMDB'
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'OCMock'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.name != 'Release' then
break
end
# This is necessary to get FMDB to not NSLog stuff.
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
# Enable more compiler optimizations.
config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 'fast'
config.build_settings['LLVM_LTO'] = 'YES'
end
end
end
target :LogicTests do
pod 'OCMock'
pod 'FMDB'
end

View File

@@ -1,17 +1,32 @@
PODS:
- FMDB (2.5):
- FMDB/standard (= 2.5)
- FMDB/common (2.5)
- FMDB/standard (2.5):
- FMDB/common
- OCMock (3.1.2)
- FMDB (2.6.2):
- FMDB/standard (= 2.6.2)
- FMDB/standard (2.6.2)
- MOLAuthenticatingURLSession (2.2):
- MOLCertificate (~> 1.5)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- MOLFCMClient (1.3):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.4)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient (~> 1.3)
- OCMock
SPEC CHECKSUMS:
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
COCOAPODS: 0.36.1
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
COCOAPODS: 1.2.1

101
README.md
View File

@@ -1,7 +1,13 @@
Santa [![Build Status](https://travis-ci.org/google/santa.png?branch=master)](https://travis-ci.org/google/santa)
=====
Santa is a binary whitelisting/blacklisting system for Mac OS X. It consists of
<p align="center">
<a href="#santa--">
<img src="./Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
</a>
</p>
Santa is a binary whitelisting/blacklisting system for macOS. It consists of
a kernel extension that monitors for executions, a userland daemon that makes
execution decisions based on the contents of a SQLite database, a GUI agent that
notifies the user in case of a block decision and a command-line utility for
@@ -10,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.
@@ -38,19 +72,18 @@ 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.
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`. 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
found a good way to ensure the kext only accepts connections from a valid client.
@@ -59,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
@@ -74,12 +106,23 @@ 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.
Screenshots
===========
A tool like Santa doesn't really lend itself to screenshots, so here's a video instead.
<p align="center">
<img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif" alt="Santa Block Video" />
</p>
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
@@ -98,12 +141,10 @@ and for security-reasons parts of Santa will not operate properly if not signed.
Kext Signing
============
10.9 requires a special Developer ID certificate to sign kernel extensions and
if the kext is not signed with one of these special certificates a warning will
be shown when loading the kext for the first time. In 10.10 this is a hard error
and the kext will not load at all unless the machine is booted with a debug
boot-arg.
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
Developer ID certificate with a kernel extension flag. Without it, the only way
to load an extension is to enable kext-dev-mode or disable SIP, depending on the
OS version.
There are two possible solutions for this, for distribution purposes:
@@ -118,17 +159,11 @@ and distribute a new version of the pre-signed kext.
Apple will only grant this for broad distribution within an organization, they
won't issue them just for testing purposes.
If you just want to locally test changes to the kext code, you should enable
kext-dev mode, instructions for which can be found on the Apple developer site.
Contributing
============
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
file.
Disclaimer
==========
This is **not** an official Google product.

View File

@@ -1,27 +1,44 @@
require 'timeout'
require 'openssl'
WORKSPACE = 'Santa.xcworkspace'
DEFAULT_SCHEME = 'All'
OUTPUT_PATH = 'Build'
DIST_PATH = 'Dist'
BINARIES = ['Santa.app', 'santa-driver.kext', 'santad', 'santactl']
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
system("rake -sT")
end
def xcodebuild(opts)
if system "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts} | " \
"xcpretty #{XCPRETTY_DEFAULTS} && " \
"exit ${PIPESTATUS[0]}"
command = "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts}"
if not $DISABLE_XCPRETTY
command << " | xcpretty #{XCPRETTY_DEFAULTS} && exit ${PIPESTATUS[0]}"
end
if system command
puts "\e[32mPass\e[0m"
else
raise "\e[31mFail\e[0m"
end
end
def xcodebuilddir
if not $xcode_build_dir
output = `xcodebuild #{XCODEBUILD_DEFAULTS} -scheme All -showBuildSettings`
if match = output.match(/BUILD_DIR = (.*)/)
$xcode_build_dir = match.captures.first
puts "Found Xcode build dir #{$xcode_build_dir}"
end
end
$xcode_build_dir
end
task :init do
unless File.exists?(WORKSPACE) and File.exists?('Pods')
puts "Pods missing, running 'pod install'"
@@ -29,22 +46,27 @@ task :init do
end
unless system 'xcpretty -v >/dev/null 2>&1'
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
system 'sudo rm -rf /santa-driver.kext'
system 'sudo rm -rf /Library/Extensions/santa-driver.kext'
system 'sudo rm -rf /Applications/Santa.app'
system 'sudo rm /usr/libexec/santad'
system 'sudo rm /usr/sbin/santactl'
end
desc "Clean"
task :clean => :init do
puts "Cleaning"
xcodebuild("-scheme All clean")
FileUtils.rm_rf(OUTPUT_PATH)
FileUtils.rm_rf(DIST_PATH)
xcodebuild("-scheme All clean")
end
# Build
@@ -82,16 +104,14 @@ namespace :install do
task :install, [:configuration] do |t, args|
config = args[:configuration]
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
system 'sudo cp conf/com.google.santasync.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()
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/santa-driver.kext /"
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/Santa.app /Applications"
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santad /usr/libexec"
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santactl /usr/sbin"
system "sudo cp -r #{xcodebuilddir}/#{config}/santa-driver.kext /Library/Extensions"
system "sudo cp -r #{xcodebuilddir}/#{config}/Santa.app /Applications"
end
end
@@ -99,22 +119,29 @@ end
task :dist do
desc "Create distribution folder"
Rake::Task['clean'].invoke()
Rake::Task['build:build'].invoke("Release")
FileUtils.rm_rf(DIST_PATH)
dist_path = "santa-#{`defaults read #{xcodebuilddir}/Release/santa-driver.kext/Contents/Info.plist CFBundleVersion`.strip}"
FileUtils.mkdir_p("#{DIST_PATH}/binaries")
FileUtils.mkdir_p("#{DIST_PATH}/conf")
FileUtils.mkdir_p("#{DIST_PATH}/dsym")
FileUtils.rm_rf(dist_path)
FileUtils.mkdir_p("#{dist_path}/binaries")
FileUtils.mkdir_p("#{dist_path}/conf")
FileUtils.mkdir_p("#{dist_path}/dsym")
BINARIES.each do |x|
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}", "#{DIST_PATH}/binaries")
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}.dSYM", "#{DIST_PATH}/dsym")
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/binaries")
end
Dir.glob("Conf/*") {|x| FileUtils.cp(x, "#{DIST_PATH}/conf")}
DSYMS.each do |x|
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/dsym")
end
puts "Distribution folder created"
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{dist_path}/conf")}
puts "Distribution folder #{dist_path} created"
end
# Tests
@@ -130,16 +157,17 @@ namespace :tests do
Rake::Task['unload'].invoke()
Rake::Task['install:debug'].invoke()
Rake::Task['load_kext'].invoke
timeout = 30
puts "Running kernel tests with a #{timeout} second timeout"
FileUtils.mkdir_p("/tmp/santa_kerneltests_tmp")
begin
Timeout::timeout(timeout) {
system "sudo #{OUTPUT_PATH}/Products/Debug/KernelTests"
}
rescue Timeout::Error
puts "ERROR: tests ran for longer than #{timeout} seconds and were killed."
puts "\033[?25l\033[12h" # hide cursor
puts "Running kernel tests"
system "cd /tmp/santa_kerneltests_tmp && sudo #{xcodebuilddir}/Debug/KernelTests"
rescue Exception
ensure
puts "\033[?25h\033[12l\n\n" # unhide cursor
FileUtils.rm_rf("/tmp/santa_kerneltests_tmp")
Rake::Task['unload_kext'].execute
end
Rake::Task['unload_kext'].execute
end
end
@@ -156,7 +184,7 @@ end
task :unload_gui do
puts "Unloading GUI agent"
system "sudo killall Santa 2>/dev/null"
system "launchctl unload /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
end
desc "Unload"
@@ -169,12 +197,12 @@ end
task :load_kext do
puts "Loading kernel extension"
system "sudo kextload /santa-driver.kext"
system "sudo kextload /Library/Extensions/santa-driver.kext"
end
task :load_gui do
puts "Loading GUI agent"
system "open /Applications/Santa.app"
system "launchctl load /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
end
desc "Load"

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0510"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,12 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
codeCoverageEnabled = "YES"
enableAddressSanitizer = "YES">
<Testables>
<TestableReference
skipped = "NO">
@@ -39,24 +41,36 @@
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
BuildableName = "All"
BlueprintName = "All"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0600"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
@@ -38,17 +38,21 @@
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
@@ -61,12 +65,13 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0620"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
@@ -48,15 +48,18 @@
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
@@ -71,10 +74,10 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0510"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
@@ -38,17 +38,21 @@
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
@@ -61,12 +65,13 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0510"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,30 +23,42 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
BuildableName = "santa-driver.kext"
BlueprintName = "santa-driver"
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction

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,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0510"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
@@ -38,17 +38,21 @@
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
@@ -61,12 +65,13 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0510"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -23,10 +23,10 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
@@ -38,18 +38,22 @@
ReferencedContainer = "container:Santa.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
debugAsWhichUser = "root"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
@@ -62,12 +66,13 @@
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"

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="14C1514" 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">
@@ -14,9 +14,9 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES"/>
<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"/>
@@ -37,7 +37,7 @@
<rect key="frame" x="18" y="65" width="444" height="60"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
<font key="font" metaFont="system"/>
<string key="title">Santa is an application whitelisting system for Mac OS X.
<string key="title">Santa is an application whitelisting system for macOS.
There are no user-configurable settings.</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -65,6 +65,9 @@ There are no user-configurable settings.</string>
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uSw-o1-lWW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="orderOut:" target="-1" id="6oW-zI-zn5"/>

View File

@@ -31,25 +31,25 @@
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"idiom" : "mac",
"filename" : "santa-hat-icon-256.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"idiom" : "mac",
"filename" : "santa-hat-icon-256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "santa-hat-icon-512.png",
"size" : "256x256",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "santa-hat-icon-512.png",
"size" : "512x512",
"scale" : "1x"
},
{
@@ -62,4 +62,4 @@
"version" : 1,
"author" : "xcode"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,85 +1,135 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C1514" 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="16E195" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
<development version="6300" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<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="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>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
<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="550" height="331"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<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="550" height="331"/>
<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="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"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
<rect key="frame" x="234" y="261" width="83" height="40"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
<color key="textColor" red="0.18696189413265307" green="0.18696189413265307" blue="0.18696189413265307" alpha="1" colorSpace="calibratedRGB"/>
<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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="z5y-RR-IEH"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
<rect key="frame" x="25" y="214" width="500" height="17"/>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="43" y="369" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="496" id="XgJ-EV-tBa"/>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
<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="controlColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="VC7-bE-uHc"/>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ">
<rect key="frame" x="175" y="167" width="324" height="17"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="297" width="142" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="320" id="xVR-j3-dLw"/>
<constraint firstAttribute="width" constant="138" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="E7T-9h-ofr">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename" id="KgY-X1-ESG">
<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>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
<rect key="frame" x="8" y="272" width="142" height="17"/>
<constraints>
<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"/>
<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>
</textField>
<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="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"/>
<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="qfp-sR-Nmu"/>
<binding destination="-2" name="value" keyPath="self.event.filePath.lastPathComponent" id="bOu-gv-1Vh"/>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="175" y="117" width="304" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="300" id="4hh-R2-86s"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="File SHA-256" id="X4W-9e-eIu">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="SzX-Ep-rBa"/>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
<rect key="frame" x="175" y="142" width="309" height="17"/>
<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="187" y="247" width="315" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="305" 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"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<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.publisherInfo" id="CEI-Cu-7pC">
@@ -89,70 +139,18 @@
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
<rect key="frame" x="18" y="92" width="120" 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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
<rect key="frame" x="18" y="142" width="120" 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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
<rect key="frame" x="18" y="167" width="120" height="17"/>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="62" y="248" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="KgY-X1-ESG">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
<rect key="frame" x="18" y="117" width="120" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="SHA-256" id="eKN-Ic-5zy">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0">
<rect key="frame" x="175" y="92" width="368" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="364" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="xe2-U2-WrZ"/>
</connections>
</textField>
<box horizontalHuggingPriority="750" title="Box" boxType="separator" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="154" y="92" width="5" height="92"/>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs">
<rect key="frame" x="51" y="143" width="14" height="14"/>
<constraints>
<constraint firstAttribute="width" constant="14" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="14" id="YwG-0s-jop"/>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
</constraints>
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</buttonCell>
<connections>
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
@@ -163,30 +161,103 @@
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
<rect key="frame" x="282" y="33" width="110" height="25"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
<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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<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="110" id="6Uh-Bd-N64"/>
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
</constraints>
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" 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">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="SHA-256" id="X4W-9e-eIu">
<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>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
</connections>
</button>
</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="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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</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="187" y="157" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
<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="displayPatternValue1" keyPath="self.event.parentName" id="Lce-TO-q9V">
<dictionary key="options">
<string key="NSDisplayPattern">%{value1}@ (%{value2}@)</string>
</dictionary>
</binding>
<binding destination="-2" name="displayPatternValue2" keyPath="self.event.ppid" previousBinding="Lce-TO-q9V" id="ofI-kH-F2d">
<dictionary key="options">
<string key="NSDisplayPattern">%{value1}@ (%{value2}@)</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<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"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="158" 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>
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
@@ -196,51 +267,231 @@ DQ
</buttonCell>
<connections>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
<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="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="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">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
</buttonCell>
<accessibility description="Dismiss Dialog"/>
<connections>
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
</connections>
</button>
<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="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>
<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="width" constant="217" id="M22-Dv-KIP"/>
</constraints>
</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.event.fileBundleHash" id="CnT-q6-bot"/>
</connections>
</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="h6f-PY-cc0" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="1Nc-gl-xMe"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
<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="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="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
<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="-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="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="20" id="IwX-ja-ZIs"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="leading" priority="999" id="MVr-jY-GDj"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="Nsl-zf-poH"/>
<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="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="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
<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 firstAttribute="centerX" secondItem="BbV-3h-mmL" secondAttribute="centerX" priority="900" id="acs-5J-vQY"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
<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"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aOk-S0-0n2"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="30" id="dYg-zP-wh2"/>
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="eSz-lz-Fdh"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="top" id="fzY-94-y2n"/>
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" constant="-0.5" id="h3d-Kc-q88"/>
<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="lvJ-Rk-UT5" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="jlD-Lo-abc"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
<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 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="PXc-xv-A28" firstAttribute="bottom" secondItem="h6f-PY-cc0" secondAttribute="top" constant="-8" id="sG1-gQ-Qoo"/>
<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 firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="baseline" secondItem="pc8-G9-4pJ" secondAttribute="baseline" id="xGd-Xr-3Z0"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
<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>
</view>
<point key="canvasLocation" x="113" y="777.5"/>
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<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

@@ -0,0 +1,23 @@
/// 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 Cocoa;
/**
An NSTextField subclass that provides an accessiblity label equal to:
(self.toolTip + self.stringValue) where available. It also sets the
accessibilityRoleDescription to "label".
*/
@interface SNTAccessibleTextField : NSTextField
@end

View File

@@ -0,0 +1,39 @@
/// 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 "SNTAccessibleTextField.h"
@implementation SNTAccessibleTextField
- (BOOL)accessibilityIsIgnored {
return NO;
}
- (NSString *)accessibilityLabel {
if (self.toolTip && self.stringValue) {
return [NSString stringWithFormat:@"%@: %@", self.toolTip, self.stringValue];
} else if (self.stringValue) {
return self.stringValue;
} else if (self.toolTip) {
return self.toolTip;
} else {
return nil;
}
}
- (NSString *)accessibilityRoleDescription {
return @"label";
}
@end

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

@@ -18,13 +18,16 @@
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *listener;
@property SNTXPCConnection *daemonListener;
@property SNTXPCConnection *bundleListener;
@end
@implementation SNTAppDelegate
@@ -35,60 +38,107 @@
[self setupMenu];
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^{
[[SNTConfigurator configurator] reloadConfigData];
handler:^(unsigned long data) {
if (! (data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
}];
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
self.notificationManager = [[SNTNotificationManager alloc] init];
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];
[workspaceNotifications addObserver:self
selector:@selector(killConnection)
name:NSWorkspaceSessionDidResignActiveNotification
object:nil];
[workspaceNotifications addObserver:self
selector:@selector(createConnection)
name:NSWorkspaceSessionDidBecomeActiveNotification
object:nil];
[self createConnection];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidResignActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
self.daemonListener.invalidationHandler = nil;
[self.daemonListener invalidate];
self.daemonListener = nil;
}];
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
object:nil
queue:[NSOperationQueue currentQueue]
usingBlock:^(NSNotification *note) {
[self attemptDaemonReconnection];
}];
[self createDaemonConnection];
[self createBundleConnection];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
[self.aboutWindowController showWindow:self];
return NO;
}
#pragma mark Connection handling
- (void)createConnection {
__weak __typeof(self) weakSelf = self;
- (void)createDaemonConnection {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
self.listener = [[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
options:NSXPCConnectionPrivileged];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.rejectedHandler = ^{
[weakSelf performSelectorInBackground:@selector(attemptReconnection)
withObject:nil];
WEAKIFY(self);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
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.listener.rejectedHandler;
[self.listener resume];
self.daemonListener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptDaemonReconnection];
};
[self.daemonListener resume];
// Tell daemon to connect back to the above listener.
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setNotificationListener: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 attemptDaemonReconnection];
}
}
- (void)killConnection {
self.listener.invalidationHandler = nil;
[self.listener invalidate];
self.listener = nil;
- (void)attemptDaemonReconnection {
[self performSelectorInBackground:@selector(createDaemonConnection) withObject:nil];
}
- (void)attemptReconnection {
// TODO(rah): Make this smarter.
sleep(10);
[self performSelectorOnMainThread:@selector(createConnection)
withObject:nil
waitUntilDone:NO];
- (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

@@ -31,7 +31,7 @@
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.15f];
[[NSAnimationContext currentContext] setCompletionHandler:^{
[NSApp activateIgnoringOtherApps:YES];
[NSApp activateIgnoringOtherApps:YES];
}];
[[self animator] setAlphaValue:1.f];
[NSAnimationContext endGrouping];
@@ -43,9 +43,9 @@
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.15f];
[[NSAnimationContext currentContext] setCompletionHandler:^{
[weakSelf.windowController windowWillClose:nil];
[weakSelf orderOut:nil];
[weakSelf setAlphaValue:1.f];
[weakSelf.windowController windowWillClose:sender];
[weakSelf orderOut:sender];
[weakSelf setAlphaValue:1.f];
}];
[[self animator] setAlphaValue:0.f];
[NSAnimationContext endGrouping];

View File

@@ -12,10 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
@class SNTStoredEvent;
@protocol SNTMessageWindowControllerDelegate
- (void)windowDidClose;
- (void)windowDidCloseSilenceHash:(NSString *)hash;
@end
///
@@ -29,35 +31,39 @@
- (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;
///
/// 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 SNTStoredEvent *event;
@property(readonly) SNTStoredEvent *event;
///
/// The custom message to display for this event
/// The root progress object. Child nodes are vended to santad to report on work being done.
///
@property NSString *customMessage;
@property NSProgress *progress;
///
/// The delegate to inform when the notification is dismissed
///
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
///
/// A 'friendly' string representing the certificate information
///
@property(readonly) NSString *publisherInfo;
///
/// An optional message to display with this block.
///
@property(readonly) 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;
@end

View File

@@ -14,25 +14,69 @@
#import "SNTMessageWindowController.h"
#import <SecurityInterface/SFCertificatePanel.h>
@import SecurityInterface.SFCertificatePanel;
#import "SNTCertificate.h"
#import "MOLCertificate.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTFileInfo.h"
#import "SNTMessageWindow.h"
#import "SNTStoredEvent.h"
@interface SNTMessageWindowController ()
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// A 'friendly' string representing the certificate information
@property(readonly, nonatomic) NSString *publisherInfo;
/// An optional message to display with this block.
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property(weak) IBOutlet NSTextField *applicationNameLabel;
/// Linked to checkbox in UI to prevent future notifications for this binary.
@property BOOL silenceFutureNotifications;
@end
@implementation SNTMessageWindowController
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
self = [super initWithWindowNibName:@"MessageWindow"];
if (self) {
_event = event;
_customMessage = (message != (NSString *)[NSNull null] ? message : nil);
_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.foundFileCountLabel removeFromSuperview];
}
self.hashingIndicator.doubleValue = progress.fractionCompleted;
});
}
}
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
@@ -46,6 +90,22 @@
[self.openEventButton setTitle:eventDetailText];
}
}
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];
}
}
- (IBAction)showWindow:(id)sender {
@@ -53,17 +113,24 @@
}
- (IBAction)closeWindow:(id)sender {
[self.progress cancel];
[(SNTMessageWindow *)self.window fadeOut:sender];
}
- (void)windowWillClose:(NSNotification *)notification {
if (self.delegate) [self.delegate windowDidClose];
if (!self.delegate) return;
if (self.silenceFutureNotifications) {
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
} else {
[self.delegate windowDidCloseSilenceHash:nil];
}
}
- (IBAction)showCertInfo:(id)sender {
// SFCertificatePanel expects an NSArray of SecCertificateRef's
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
for (SNTCertificate *cert in self.event.signingChain) {
for (MOLCertificate *cert in self.event.signingChain) {
[certArray addObject:(id)cert.certRef];
}
@@ -76,18 +143,9 @@
}
- (IBAction)openEventDetails:(id)sender {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *formatStr = config.eventDetailURL;
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:self.event.fileSHA256];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
withString:self.event.executingUser];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
withString:config.machineID];
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
[self closeWindow:sender];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:formatStr]];
[[NSWorkspace sharedWorkspace] openURL:url];
}
#pragma mark Generated properties
@@ -96,12 +154,12 @@
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return nil;
return [NSSet set];
}
}
- (NSString *)publisherInfo {
SNTCertificate *leafCert = [self.event.signingChain firstObject];
MOLCertificate *leafCert = [self.event.signingChain firstObject];
if (leafCert.commonName && leafCert.orgName) {
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
@@ -115,30 +173,8 @@
}
- (NSAttributedString *)attributedCustomMessage {
NSString *htmlHeader = @"<html><head><style>"
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: #AAA;"
@" text-align: center;"
@"}"
@"</style></head><body>";
NSString *htmlFooter = @"</body></html>";
NSString *message;
if ([self.customMessage length] > 0) {
message = self.customMessage;
} else {
message = @"The following application has been blocked from executing<br />"
@"because its trustworthiness cannot be determined.";
}
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
NSAttributedString *returnStr = [[NSAttributedString alloc] initWithHTML:htmlData
documentAttributes:NULL];
return returnStr;
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
customMessage:self.customMessage];
}
@end

View File

@@ -12,13 +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

@@ -14,83 +14,245 @@
#import "SNTNotificationManager.h"
#import "SNTBlockMessage.h"
#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
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
- (instancetype)init {
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;
}
- (void)windowDidClose {
- (void)windowDidCloseSilenceHash:(NSString *)hash {
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
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];
}
}
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
if (!d) d = [NSMutableDictionary dictionary];
if (date) {
d[hash] = date;
} else {
[d removeObjectForKey:hash];
}
[ud setObject:d forKey:silencedNotificationsKey];
}
#pragma mark SNTNotifierXPC protocol methods
- (void)postClientModeNotification:(SNTClientMode)clientmode {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
NSString *customMsg;
switch (clientmode) {
case SNTClientModeMonitor:
un.informativeText = @"Switching into Monitor mode";
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
break;
case SNTClientModeLockdown:
un.informativeText = @"Switching into Lockdown mode";
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
break;
default:
return;
}
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
// See if this binary is already in the list of pending notifications.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"event.fileSHA256==%@",
event.fileSHA256];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
// See if this binary is silenced.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
if ([silenceDate isKindOfClass:[NSDate class]]) {
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
LOGI(@"Notification silence: date is in the future, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
LOGI(@"Notification silence: date is more than one day ago, ignoring");
[self updateSilenceDate:nil forHash:event.fileSHA256];
} else {
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
return;
}
}
if (!event) {
NSLog(@"Error: Missing event object in message received from daemon!");
LOGI(@"Error: Missing event object in message received from daemon!");
return;
}
if (!message) message = (NSString *)[NSNull null];
// Notifications arrive on a background thread but UI updates must happen on the main thread.
// This includes making windows.
[self performSelectorOnMainThread:@selector(postBlockNotificationMainThread:)
withObject:@{ @"event": event, @"custommsg": message }
waitUntilDone:NO];
dispatch_async(dispatch_get_main_queue(), ^{
SNTMessageWindowController *pendingMsg =
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
// If a notification isn't currently being displayed, display the incoming one.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[pendingMsg showWindow:nil];
if (self.currentWindowController.event.needsBundleHash) {
dispatch_async(self.hashBundleBinariesQueue, ^{
[self hashBundleBinariesForEvent:self.currentWindowController.event];
});
}
}
});
}
- (void)postBlockNotificationMainThread:(NSDictionary *)dict {
SNTStoredEvent *event = dict[@"event"];
NSString *msg = dict[@"custommsg"];
- (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];
}
// Create message window
SNTMessageWindowController *pendingMsg = [[SNTMessageWindowController alloc] initWithEvent:event
andMessage:msg];
pendingMsg.delegate = self;
[self.pendingNotifications addObject:pendingMsg];
#pragma mark SNTBundleNotifierXPC protocol methods
// If a notification isn't currently being displayed, display the incoming one.
if (!self.currentWindowController) {
self.currentWindowController = pendingMsg;
[NSApp activateIgnoringOtherApps:YES];
// It's quite likely that we're currently on a background thread, and GUI code should always be
// on main thread. Open the window on the main thread so any code it runs is also.
[pendingMsg showWindow:nil];
- (void)updateCountsForEvent:(SNTStoredEvent *)event
binaryCount:(uint64_t)binaryCount
fileCount:(uint64_t)fileCount {
if ([self.currentWindowController.event.idx isEqual:event.idx]) {
dispatch_async(dispatch_get_main_queue(), ^{
self.currentWindowController.foundFileCountLabel.stringValue =
[NSString stringWithFormat:@"%llu binaries / %llu files", binaryCount, fileCount];
});
}
}
- (void)setBundleServiceListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCBundleServiceInterface bundleServiceInterface];
[c resume];
self.bundleServiceConnection = c;
dispatch_semaphore_signal(self.bundleServiceSema);
}
#pragma mark SNTBundleNotifierXPC helper methods
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event {
// 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:1];
// 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;
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.event.fileBundleHash = bundleHash;
[self.currentWindowController.foundFileCountLabel removeFromSuperview];
[self.currentWindowController.hashingIndicator setHidden:YES];
[self.currentWindowController.openEventButton setEnabled:YES];
}
});
}
@end

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

@@ -0,0 +1,47 @@
/// 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.
#ifdef SANTAGUI
@import Cocoa;
#else
@import Foundation;
#endif
@class SNTStoredEvent;
@interface SNTBlockMessage : NSObject
///
/// Return a message suitable for presenting to the user.
/// Uses either the configured message depending on the event type or a custom message
/// if the rule that blocked this file included one.
///
/// In SantaGUI this will return an NSAttributedString with links and formatting included
/// while for santad all HTML will be properly stripped.
///
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage;
///
/// Return a URL generated from the EventDetailURL configuration key
/// after replacing templates in the URL with values from the event.
///
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event;
///
/// Strip HTML from a string, replacing <br /> with newline.
///
+ (NSString *)stringFromHTML:(NSString *)html;
@end

View File

@@ -0,0 +1,128 @@
/// 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 "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@implementation SNTBlockMessage
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
NSString *htmlHeader = @"<html><head><style>"
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: #666;"
@" text-align: center;"
@"}"
@"</style></head><body>";
NSString *htmlFooter = @"</body></html>";
NSString *message;
if (customMessage.length) {
message = customMessage;
} else if (event.decision == SNTEventStateBlockUnknown) {
message = [[SNTConfigurator configurator] unknownBlockMessage];
if (!message) {
message = @"The following application has been blocked from executing<br />"
@"because its trustworthiness cannot be determined.";
}
} else {
message = [[SNTConfigurator configurator] bannedBlockMessage];
if (!message) {
message = @"The following application has been blocked from executing<br />"
@"because it has been deemed malicious.";
}
}
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef NSAppKitVersionNumber10_0
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
}
return [[NSAttributedString alloc] initWithString:strippedHTML];
#endif
}
+ (NSString *)stringFromHTML:(NSString *)html {
NSError *error;
NSXMLDocument *xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
if (!xml && error.code == NSXMLParserEmptyDocumentError) {
html = [NSString stringWithFormat:@"<html><body>%@</body></html>", html];
xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
if (!xml) return html;
}
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
// replace <br> elements with a newline.
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
@"<xsl:output method='text'/>"
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
@"<xsl:template match='style'/>"
@"</xsl:stylesheet>";
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
if (error || ![data isKindOfClass:[NSData class]]) {
return html;
}
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
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;
}
if (!formatStr.length) return nil;
if (event.fileSHA256) {
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:event.fileBundleHash ?: event.fileSHA256];
}
if (event.executingUser) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
withString:event.executingUser];
}
if (config.machineID) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
withString:config.machineID];
}
return [NSURL URLWithString:formatStr];
}
@end

View File

@@ -1,111 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// SNTCertificate wraps a @c SecCertificateRef to provide Objective-C accessors to
/// commonly used certificate data. Accessors cache data for repeated access.
///
@interface SNTCertificate : NSObject<NSSecureCoding>
///
/// Initialize a SNTCertificate object with a valid SecCertificateRef. Designated initializer.
///
/// @param certRef valid SecCertificateRef, which will be retained.
///
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef;
///
/// Initialize a SNTCertificate object with certificate data in DER format.
///
/// @param certData DER-encoded certificate data.
/// @return initialized SNTCertificate or nil if certData is not a DER-encoded certificate.
///
- (instancetype)initWithCertificateDataDER:(NSData *)certData;
///
/// Initialize a SNTCertificate object with certificate data in PEM format.
/// If multiple PEM certificates exist within the string, the first is used.
///
/// @param certData PEM-encoded certificate data.
/// @return initialized SNTCertifcate or nil if certData is not a PEM-encoded certificate.
///
- (instancetype)initWithCertificateDataPEM:(NSString *)certData;
///
/// Returns an array of SNTCertificate's for all of the certificates in @c pemData.
///
/// @param pemData PEM-encoded certificates.
/// @return array of SNTCertificate objects.
///
+ (NSArray *)certificatesFromPEM:(NSString *)pemData;
///
/// Access the underlying certificate ref.
///
@property(readonly) SecCertificateRef certRef;
///
/// SHA-1 hash of the certificate data.
///
@property(readonly) NSString *SHA1;
///
/// SHA-256 hash of the certificate data.
///
@property(readonly) NSString *SHA256;
///
/// Certificate data.
///
@property(readonly) NSData *certData;
///
/// Common Name e.g: "Software Signing"
///
@property(readonly) NSString *commonName;
///
/// Country Name e.g: "US"
///
@property(readonly) NSString *countryName;
///
/// Organizational Name e.g: "Apple Inc."
///
@property(readonly) NSString *orgName;
///
/// Organizational Unit Name e.g: "Apple Software"
///
@property(readonly) NSString *orgUnit;
///
/// Issuer details, same fields as above.
///
@property(readonly) NSString *issuerCommonName;
@property(readonly) NSString *issuerCountryName;
@property(readonly) NSString *issuerOrgName;
@property(readonly) NSString *issuerOrgUnit;
///
/// Validity Not Before
///
@property(readonly) NSDate *validFrom;
///
/// Validity Not After
///
@property(readonly) NSDate *validUntil;
@end

View File

@@ -1,362 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCertificate.h"
#import <CommonCrypto/CommonDigest.h>
#import <Security/Security.h>
@interface SNTCertificate ()
/// A container for cached property values
@property NSMutableDictionary *memoizedData;
@end
@implementation SNTCertificate
static NSString *const kCertDataKey = @"certData";
#pragma mark Init/Dealloc
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef {
self = [super init];
if (self) {
_certRef = certRef;
CFRetain(_certRef);
}
return self;
}
- (instancetype)initWithCertificateDataDER:(NSData *)certData {
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
if (cert) {
// Despite the header file claiming that SecCertificateCreateWithData will return NULL if
// @c certData doesn't contain a valid DER-encoded X509 cert, this isn't always true.
// radar://problem/16124651
// To workaround, check that the certificate serial number can be retrieved. According to
// RFC5280, the serial number field is required.
NSData *ser = CFBridgingRelease(SecCertificateCopySerialNumber(cert, NULL));
if (ser) {
self = [self initWithSecCertificateRef:cert];
} else {
self = nil;
}
CFRelease(cert); // was retained in initWithSecCertificateRef
} else {
self = nil;
}
return self;
}
- (instancetype)initWithCertificateDataPEM:(NSString *)certData {
// Find the PEM and extract the base64-encoded DER data from within
NSScanner *scanner = [NSScanner scannerWithString:certData];
NSString *base64der;
// Locate and parse DER data into |base64der|
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
if (!([scanner scanString:@"-----BEGIN CERTIFICATE-----" intoString:NULL] &&
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&base64der] &&
[scanner scanString:@"-----END CERTIFICATE-----" intoString:NULL])) {
return nil;
}
// base64-decode the DER
SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL);
if (!transform) return nil;
NSData *input = [base64der dataUsingEncoding:NSUTF8StringEncoding];
NSData *output = nil;
if (SecTransformSetAttribute(transform,
kSecTransformInputAttributeName,
(__bridge CFDataRef)input,
NULL)) {
output = CFBridgingRelease(SecTransformExecute(transform, NULL));
}
if (transform) CFRelease(transform);
return [self initWithCertificateDataDER:output];
}
+ (NSArray *)certificatesFromPEM:(NSString *)pemData {
NSScanner *scanner = [NSScanner scannerWithString:pemData];
NSMutableArray *certs = [[NSMutableArray alloc] init];
while (YES) {
NSString *curCert;
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&curCert];
// If there was no data, break.
if (!curCert) break;
curCert = [curCert stringByAppendingString:@"-----END CERTIFICATE-----"];
SNTCertificate *cert = [[SNTCertificate alloc] initWithCertificateDataPEM:curCert];
// If the data couldn't be turned into a valid SNTCertificate, continue.
if (!cert) continue;
[certs addObject:cert];
}
return certs;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)dealloc {
if (_certRef) CFRelease(_certRef);
}
#pragma mark Equality & description
- (BOOL)isEqual:(SNTCertificate *)other {
if (self == other) return YES;
if (![other isKindOfClass:[SNTCertificate class]]) return NO;
return [self.certData isEqual:other.certData];
}
- (NSUInteger)hash {
return [self.certData hash];
}
- (NSString *)description {
return [NSString stringWithFormat:@"/O=%@/OU=%@/CN=%@",
self.orgName,
self.orgUnit,
self.commonName];
}
#pragma mark NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.certData forKey:kCertDataKey];
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
NSData *certData = [decoder decodeObjectOfClass:[NSData class] forKey:kCertDataKey];
if ([certData length] == 0) return nil;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
self = [self initWithSecCertificateRef:cert];
if (cert) CFRelease(cert);
return self;
}
#pragma mark Private Accessors
///
/// For a given selector, caches the value that selector would return on subsequent invocations,
/// using the provided block to get the value on the first invocation.
/// Assumes the selector's value will never change.
///
- (id)memoizedSelector:(SEL)selector forBlock:(id (^)(void))block {
NSString *selName = NSStringFromSelector(selector);
if (!self.memoizedData) {
self.memoizedData = [NSMutableDictionary dictionary];
}
if (!self.memoizedData[selName]) {
id val = block();
if (val) {
self.memoizedData[selName] = val;
} else {
self.memoizedData[selName] = [NSNull null];
}
}
// Return the value if there is one, or nil if the value is NSNull
return self.memoizedData[selName] != [NSNull null] ? self.memoizedData[selName] : nil;
}
- (NSDictionary *)allCertificateValues {
return [self memoizedSelector:_cmd forBlock:^id{
return CFBridgingRelease(SecCertificateCopyValues(self.certRef, NULL, NULL));
}];
}
- (NSDictionary *)x509SubjectName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1SubjectName];
}];
}
- (NSDictionary *)x509IssuerName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1IssuerName];
}];
}
///
/// Retrieve the value with the specified label from the X509 dictionary provided
///
/// @param desiredLabel The label you want, e.g: kSecOIDOrganizationName.
/// @param dict The dictionary to look in (Subject or Issuer)
/// @return An @c NSString, the value for the specified label.
///
- (NSString *)x509ValueForLabel:(NSString *)desiredLabel fromDictionary:(NSDictionary *)dict {
@try {
NSArray *valArray = dict[(__bridge NSString *)kSecPropertyKeyValue];
for (NSDictionary *curCertVal in valArray) {
NSString *valueLabel = curCertVal[(__bridge NSString *)kSecPropertyKeyLabel];
if ([valueLabel isEqual:desiredLabel]) {
return curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
}
}
return nil;
}
@catch (NSException *exception) {
return nil;
}
}
///
/// Retrieve the specified date from the certificate's values and convert from a reference date
/// to an NSDate object.
///
/// @param key The identifier for the date: @c kSecOIDX509V1ValiditityNot{Before,After}
/// @return An @c NSDate representing the date and time the certificate is valid from or expires.
///
- (NSDate *)dateForX509Key:(NSString *)key {
NSDictionary *curCertVal = [self allCertificateValues][key];
NSNumber *value = curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
NSTimeInterval interval = [value doubleValue];
if (interval) {
return [NSDate dateWithTimeIntervalSinceReferenceDate:interval];
}
return nil;
}
#pragma mark Public Accessors
- (NSString *)SHA1 {
return [self memoizedSelector:_cmd forBlock:^id{
NSMutableData *SHA1Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH];
CC_SHA1([self.certData bytes], (CC_LONG)[self.certData length], [SHA1Buffer mutableBytes]);
const unsigned char *bytes = (const unsigned char *)[SHA1Buffer bytes];
NSMutableString *hexDigest = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[hexDigest appendFormat:@"%02x", bytes[i]];
}
return hexDigest;
}];
}
- (NSString *)SHA256 {
return [self memoizedSelector:_cmd forBlock:^id{
NSMutableData *SHA256Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH];
CC_SHA256([self.certData bytes], (CC_LONG)[self.certData length], [SHA256Buffer mutableBytes]);
const unsigned char *bytes = (const unsigned char *)[SHA256Buffer bytes];
NSMutableString *hexDigest = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[hexDigest appendFormat:@"%02x", bytes[i]];
}
return hexDigest;
}];
}
- (NSData *)certData {
return CFBridgingRelease(SecCertificateCopyData(self.certRef));
}
- (NSString *)commonName {
return [self memoizedSelector:_cmd forBlock:^id{
CFStringRef commonName = NULL;
SecCertificateCopyCommonName(self.certRef, &commonName);
return CFBridgingRelease(commonName);
}];
}
- (NSString *)countryName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
fromDictionary:[self x509SubjectName]];
}];
}
- (NSString *)orgName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
fromDictionary:[self x509SubjectName]];
}];
}
- (NSString *)orgUnit {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
fromDictionary:[self x509SubjectName]];
}];
}
- (NSDate *)validFrom {
return [self memoizedSelector:_cmd forBlock:^id{
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotBefore];
}];
}
- (NSDate *)validUntil {
return [self memoizedSelector:_cmd forBlock:^id{
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotAfter];
}];
}
- (NSString *)issuerCommonName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCommonName
fromDictionary:[self x509IssuerName]];
}];
}
- (NSString *)issuerCountryName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
fromDictionary:[self x509IssuerName]];
}];
}
- (NSString *)issuerOrgName {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
fromDictionary:[self x509IssuerName]];
}];
}
- (NSString *)issuerOrgUnit {
return [self memoizedSelector:_cmd forBlock:^id{
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
fromDictionary:[self x509IssuerName]];
}];
}
@end

View File

@@ -1,90 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCertificate;
///
/// SNTCodesignChecker validates a binary (either on-disk or in memory) has been signed
/// and if so allows for pulling out the certificates that were used to sign it.
///
@interface SNTCodesignChecker : NSObject
///
/// The SecStaticCodeRef that this SNTCodesignChecker is working around
///
@property(readonly) SecStaticCodeRef codeRef;
///
/// Returns a dictionary of raw signing information
///
@property(readonly) NSDictionary *signingInformation;
///
/// Returns an array of @c SNTCertificate objects representing the chain that signed this binary.
///
@property(readonly) NSArray *certificates;
///
/// Returns the leaf certificate that this binary was signed with
///
@property(readonly) SNTCertificate *leafCertificate;
///
/// Returns the on-disk path of this binary.
///
@property(readonly) NSString *binaryPath;
///
/// Designated initializer
/// Takes ownership of the codeRef reference.
///
/// @param codeRef a SecStaticCodeRef or SecCodeRef representing a binary.
/// @return an initialized SNTCodesignChecker if the binary is validly signed, nil otherwise.
///
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef;
///
/// Convenience initializer for a binary on disk.
///
/// @param binaryPath A binary file on disk
/// @return an initialized SNTCodesignChecker if file is a binary and is signed, nil otherwise.
///
- (instancetype)initWithBinaryPath:(NSString *)binaryPath;
///
/// Convenience initializer for a binary that is running, by its process ID.
///
/// @param PID Id of a running process.
/// @return an initialized SNTCodesignChecker if binary is signed, nil otherwise.
///
- (instancetype)initWithPID:(pid_t)PID;
///
/// Convenience initializer for the currently running process.
///
/// @return an initialized SNTCodesignChecker if current binary is signed, nil otherwise.
///
- (instancetype)initWithSelf;
///
/// Compares the signatures of the binaries represented by this SNTCodesignChecker and
/// @c otherChecker.
///
/// If both binaries are correctly signed and the leaf signatures are identical.
///
/// @return YES if both binaries are signed with the same leaf certificate.
///
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker;
@end

View File

@@ -1,196 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCodesignChecker.h"
#import <Security/Security.h>
#import "SNTCertificate.h"
/**
* kStaticSigningFlags are the flags used when validating signatures on disk.
*
* Don't validate resources but do validate nested code. Ignoring resources _dramatically_ speeds
* up validation (see below) but does mean images, plists, etc will not be checked and modifying
* these will not be considered invalid. To ensure any code inside the binary is still checked,
* we check nested code.
*
* Timings with different flags:
* Checking Xcode 5.1.1 bundle:
* kSecCSDefaultFlags: 3.895s
* kSecCSDoNotValidateResources: 0.013s
* kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.013s
*
* Checking Google Chrome 36.0.1985.143 bundle:
* kSecCSDefaultFlags: 0.529s
* kSecCSDoNotValidateResources: 0.032s
* kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.033s
*/
const SecCSFlags kStaticSigningFlags = kSecCSDoNotValidateResources | kSecCSCheckNestedCode;
/**
* kSigningFlags are the flags used when validating signatures for running binaries.
*
* No special flags needed currently.
*/
const SecCSFlags kSigningFlags = kSecCSDefaultFlags;
@interface SNTCodesignChecker ()
/// Array of @c SNTCertificate's representing the chain of certs this executable was signed with.
@property NSMutableArray *certificates;
@end
@implementation SNTCodesignChecker
#pragma mark Init/dealloc
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef {
self = [super init];
if (self) {
// First check the signing is valid
if (CFGetTypeID(codeRef) == SecStaticCodeGetTypeID()) {
if (SecStaticCodeCheckValidity(codeRef, kStaticSigningFlags, NULL) != errSecSuccess) {
return nil;
}
} else if (CFGetTypeID(codeRef) == SecCodeGetTypeID()) {
if (SecCodeCheckValidity((SecCodeRef)codeRef, kSigningFlags, NULL) != errSecSuccess) {
return nil;
}
} else {
return nil;
}
// Get CFDictionary of signing information for binary
OSStatus status = errSecSuccess;
CFDictionaryRef signingDict = NULL;
status = SecCodeCopySigningInformation(codeRef, kSecCSSigningInformation, &signingDict);
_signingInformation = CFBridgingRelease(signingDict);
if (status != errSecSuccess) return nil;
// Get array of certificates.
NSArray *certs = _signingInformation[(id)kSecCodeInfoCertificates];
if (!certs) return nil;
// Wrap SecCertificateRef objects in SNTCertificate and put in a new NSArray
NSMutableArray *mutableCerts = [[NSMutableArray alloc] initWithCapacity:certs.count];
for (int i = 0; i < certs.count; ++i) {
SecCertificateRef certRef = (__bridge SecCertificateRef)certs[i];
SNTCertificate *newCert = [[SNTCertificate alloc] initWithSecCertificateRef:certRef];
[mutableCerts addObject:newCert];
}
_certificates = [mutableCerts copy];
_codeRef = codeRef;
CFRetain(_codeRef);
}
return self;
}
- (instancetype)initWithBinaryPath:(NSString *)binaryPath {
SecStaticCodeRef codeRef = NULL;
// Get SecStaticCodeRef for binary
if (SecStaticCodeCreateWithPath((__bridge CFURLRef)[NSURL fileURLWithPath:binaryPath
isDirectory:NO],
kSecCSDefaultFlags,
&codeRef) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeRef];
} else {
self = nil;
}
if (codeRef) CFRelease(codeRef);
return self;
}
- (instancetype)initWithPID:(pid_t)PID {
SecCodeRef codeRef = NULL;
NSDictionary *attributes = @{(__bridge NSString *)kSecGuestAttributePid: @(PID)};
if (SecCodeCopyGuestWithAttributes(NULL,
(__bridge CFDictionaryRef)attributes,
kSecCSDefaultFlags,
&codeRef) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeRef];
} else {
self = nil;
}
if (codeRef) CFRelease(codeRef);
return self;
}
- (instancetype)initWithSelf {
SecCodeRef codeSelf = NULL;
if (SecCodeCopySelf(kSecCSDefaultFlags, &codeSelf) == errSecSuccess) {
self = [self initWithSecStaticCodeRef:codeSelf];
} else {
self = nil;
}
if (codeSelf) CFRelease(codeSelf);
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (void)dealloc {
if (_codeRef) {
CFRelease(_codeRef);
_codeRef = NULL;
}
}
#pragma mark Description
- (NSString *)description {
NSString *binarySource;
if (CFGetTypeID(self.codeRef) == SecStaticCodeGetTypeID()) {
binarySource = @"On-disk";
} else {
binarySource = @"In-memory";
}
return [NSString stringWithFormat:@"%@ binary, signed by %@, located at: %@",
binarySource,
self.leafCertificate.orgName,
self.binaryPath];
}
#pragma mark Public accessors
- (SNTCertificate *)leafCertificate {
return [self.certificates firstObject];
}
- (NSString *)binaryPath {
CFURLRef path;
OSStatus status = SecCodeCopyPath(_codeRef, kSecCSDefaultFlags, &path);
NSURL *pathURL = CFBridgingRelease(path);
if (status != errSecSuccess) return nil;
return [pathURL path];
}
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker {
return [self.certificates isEqual:otherChecker.certificates];
}
@end

View File

@@ -12,57 +12,66 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__COMMON__COMMONENUMS_H
#define SANTA__COMMON__COMMONENUMS_H
@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.
///
typedef enum {
RULETYPE_UNKNOWN,
typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeUnknown,
RULETYPE_BINARY = 1,
RULETYPE_CERT = 2,
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
};
RULETYPE_MAX
} santa_ruletype_t;
typedef NS_ENUM(NSInteger, SNTRuleState) {
SNTRuleStateUnknown,
typedef enum {
RULESTATE_UNKNOWN,
SNTRuleStateWhitelist = 1,
SNTRuleStateBlacklist = 2,
SNTRuleStateSilentBlacklist = 3,
SNTRuleStateRemove = 4,
};
RULESTATE_WHITELIST = 1,
RULESTATE_BLACKLIST = 2,
RULESTATE_SILENT_BLACKLIST = 3,
RULESTATE_REMOVE = 4,
typedef NS_ENUM(NSInteger, SNTClientMode) {
SNTClientModeUnknown,
RULESTATE_MAX
} santa_rulestate_t;
SNTClientModeMonitor = 1,
SNTClientModeLockdown = 2,
};
typedef enum {
CLIENTMODE_UNKNOWN,
typedef NS_ENUM(NSInteger, SNTEventState) {
// Bits 0-15 bits store non-decision types
SNTEventStateUnknown = 0,
SNTEventStateBundleBinary = 1,
CLIENTMODE_MONITOR = 1,
CLIENTMODE_LOCKDOWN = 2,
// Bits 16-23 store deny decision types
SNTEventStateBlockUnknown = 1 << 16,
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
CLIENTMODE_MAX
} santa_clientmode_t;
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
SNTEventStateAllowBinary = 1 << 25,
SNTEventStateAllowCertificate = 1 << 26,
SNTEventStateAllowScope = 1 << 27,
typedef enum {
EVENTSTATE_UNKNOWN,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
SNTEventStateAllow = 0xFF << 24
};
EVENTSTATE_ALLOW_UNKNOWN = 1,
EVENTSTATE_ALLOW_BINARY = 2,
EVENTSTATE_ALLOW_CERTIFICATE = 3,
EVENTSTATE_ALLOW_SCOPE = 4,
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
SNTRuleTableErrorEmptyRuleArray,
SNTRuleTableErrorInsertOrReplaceFailed,
SNTRuleTableErrorInvalidRule,
SNTRuleTableErrorMissingRequiredRule,
SNTRuleTableErrorRemoveFailed
};
EVENTSTATE_BLOCK_UNKNOWN = 5,
EVENTSTATE_BLOCK_BINARY = 6,
EVENTSTATE_BLOCK_CERTIFICATE = 7,
EVENTSTATE_BLOCK_SCOPE = 8,
EVENTSTATE_MAX
} santa_eventstate_t;
#endif // SANTA__COMMON__COMMONENUMS_H
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";

View File

@@ -12,7 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
@import Foundation;
#import "SNTCommonEnums.h"
///
/// Singleton that provides an interface for managing configuration values on disk
@@ -21,102 +23,183 @@
@interface SNTConfigurator : NSObject
/// Default config file path
extern NSString * const kDefaultConfigFilePath;
extern NSString *const kDefaultConfigFilePath;
#pragma mark - Daemon Settings
///
/// The operating mode.
///
@property santa_clientmode_t clientMode;
@property(nonatomic) SNTClientMode clientMode;
///
/// Whether or not to log all events, even for whitelisted binaries.
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
///
@property BOOL logAllEvents;
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *fileChangesRegex;
# pragma mark - GUI Settings
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
///
/// The regex of blacklisted paths. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
///
/// Enable __PAGEZERO protection, defaults to YES
/// If this flag is set to NO, 32-bit binaries that are missing
/// the __PAGEZERO segment will not be blocked.
///
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
#pragma mark - GUI Settings
///
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
/// If unset, the button will not be displayed.
///
@property(readonly) NSURL *moreInfoURL;
@property(readonly, nonatomic) NSURL *moreInfoURL;
///
/// When the user gets a block notification, a button can be displayed which will
/// take them to a web page with more information about that event.
/// There are two properties, one for individual binaries and one for binaries that are part
/// of a bundle. If the latter is not set the former will be used.
///
/// This property contains a kind of format string to be turned into the URL to send them to.
/// The following sequences will be replaced in the final URL:
///
/// %file_sha% -- SHA-256 of the file that was blocked.
/// %machine_id% -- ID of the machine.
/// %username% -- executing user.
/// %bundle_id% -- bundle id of the binary, if applicable.
/// %bundle_ver% -- bundle version of the binary, if applicable.
///
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
///
/// If this item isn't set, the Open Event button will not be displayed.
///
@property(readonly) NSString *eventDetailURL;
@property(readonly, nonatomic) NSString *eventDetailURL;
@property(readonly, nonatomic) NSString *eventDetailBundleURL;
///
/// Related to the above property, this string represents the text to show on the button.
///
@property(readonly) NSString *eventDetailText;
@property(readonly, nonatomic) NSString *eventDetailText;
# pragma mark - Sync Settings
///
/// In lockdown mode this is the message shown to the user when an unknown binary
/// is blocked. If this message is not configured, a reasonable default is provided.
///
@property(readonly, nonatomic) NSString *unknownBlockMessage;
///
/// This is the message shown to the user when a binary is blocked because of a rule,
/// if that rule doesn't provide a custom message. If this is not configured, a reasonable
/// default is provided.
///
@property(readonly, nonatomic) NSString *bannedBlockMessage;
///
/// The notification text to display when the client goes into MONITOR mode.
/// Defaults to "Switching into Monitor mode"
///
@property(readonly, nonatomic) NSString *modeNotificationMonitor;
///
/// The notification text to display when the client goes into LOCKDOWN mode.
/// Defaults to "Switching into Lockdown mode"
///
@property(readonly, nonatomic) NSString *modeNotificationLockdown;
#pragma mark - Sync Settings
///
/// The base URL of the sync server.
///
@property(readonly) NSURL *syncBaseURL;
@property(readonly, nonatomic) NSURL *syncBaseURL;
///
/// The machine owner.
///
@property(readonly) NSString *machineOwner;
@property(readonly, nonatomic) NSString *machineOwner;
///
/// The last date of a successful full sync.
///
@property(nonatomic) NSDate *fullSyncLastSuccess;
///
/// The last date of a successful rule sync.
///
@property(nonatomic) NSDate *ruleSyncLastSuccess;
///
/// If YES a clean sync is required.
///
@property(nonatomic) BOOL syncCleanRequired;
///
/// If set, this over-rides the default machine ID used for syncing.
///
@property(readonly) NSString *machineID;
@property(readonly, nonatomic) NSString *machineID;
# pragma mark Server Auth Settings
///
/// 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
///
/// If set, this is valid PEM containing one or more certificates to be used to evaluate the
/// server's SSL chain, overriding the list of trusted CAs distributed with the OS.
///
@property(readonly) NSData *syncServerAuthRootsData;
@property(readonly, nonatomic) NSData *syncServerAuthRootsData;
///
/// This property is the same as the above but is a file on disk containing the PEM data.
///
@property(readonly) NSString *syncServerAuthRootsFile;
@property(readonly, nonatomic) NSString *syncServerAuthRootsFile;
# pragma mark Client Auth Settings
#pragma mark Client Auth Settings
///
/// If set, this contains the location of a PKCS#12 certificate to be used for sync authentication.
///
@property(readonly) NSString *syncClientAuthCertificateFile;
@property(readonly, nonatomic) NSString *syncClientAuthCertificateFile;
///
/// Contains the password for the pkcs#12 certificate.
///
@property(readonly) NSString *syncClientAuthCertificatePassword;
@property(readonly, nonatomic) NSString *syncClientAuthCertificatePassword;
///
/// If set, this is the Common Name of a certificate in the System keychain to be used for
/// sync authentication. The corresponding private key must also be in the keychain.
///
@property(readonly) NSString *syncClientAuthCertificateCn;
@property(readonly, nonatomic) NSString *syncClientAuthCertificateCn;
///
/// If set, this is the Issuer Name of a certificate in the System keychain to be used for
/// sync authentication. The corresponding private key must also be in the keychain.
///
@property(readonly) NSString *syncClientAuthCertificateIssuer;
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
///
/// Retrieve an initialized singleton configurator object using the default file path.

View File

@@ -20,38 +20,56 @@
@interface SNTConfigurator ()
@property NSString *configFilePath;
@property NSMutableDictionary *configData;
/// Creating NSRegularExpression objects is not fast, so cache them.
@property NSRegularExpression *cachedFileChangesRegex;
@property NSRegularExpression *cachedWhitelistDirRegex;
@property NSRegularExpression *cachedBlacklistDirRegex;
/// Array of keys that cannot be changed while santad is running if santad didn't make the change.
@property(readonly) NSArray *protectedKeys;
@end
@implementation SNTConfigurator
/// The hard-coded path to the config file
NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
NSString *const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
/// The keys in the config file
static NSString * const kClientModeKey = @"ClientMode";
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString * const kLogAllEventsKey = @"LogAllEvents";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailBundleURLKey = @"EventDetailBundleURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString * const kMoreInfoURLKey = @"MoreInfoURL";
static NSString * const kEventDetailURLKey = @"EventDetailURL";
static NSString * const kEventDetailTextKey = @"EventDetailText";
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
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";
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
static NSString *const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
static NSString *const kServerAuthRootsDataKey = @"ServerAuthRootsData";
static NSString *const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
static NSString * const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString * const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
static NSString * const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
static NSString * const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
static NSString * const kServerAuthRootsDataKey = @"ServerAuthRootsData";
static NSString * const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
static NSString *const kMachineOwnerKey = @"MachineOwner";
static NSString *const kMachineIDKey = @"MachineID";
static NSString * const kMachineOwnerKey = @"MachineOwner";
static NSString * const kMachineIDKey = @"MachineID";
static NSString *const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString * const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
static NSString * const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString * const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
- (instancetype)initWithFilePath:(NSString *)filePath {
self = [super init];
@@ -62,45 +80,120 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return self;
}
# pragma mark Singleton retriever
#pragma mark Singleton retriever
+ (instancetype)configurator {
static SNTConfigurator *sharedConfigurator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
});
return sharedConfigurator;
}
# pragma mark Public Interface
#pragma mark Protected Keys
- (santa_clientmode_t)clientMode {
int cm = [self.configData[kClientModeKey] intValue];
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
return cm;
- (NSArray *)protectedKeys {
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey,
kFileChangesRegexKey, kSyncBaseURLKey ];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
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 {
self.configData[kClientModeKey] = @(CLIENTMODE_MONITOR);
return CLIENTMODE_MONITOR;
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.clientMode = SNTClientModeMonitor;
return SNTClientModeMonitor;
}
}
- (void)setClientMode:(santa_clientmode_t)newMode {
if (newMode > CLIENTMODE_UNKNOWN && newMode < CLIENTMODE_MAX) {
- (void)setClientMode:(SNTClientMode)newMode {
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
self.configData[kClientModeKey] = @(newMode);
[self saveConfigToDisk];
} else {
LOGW(@"Ignoring request to change client mode to %ld", newMode);
}
}
- (BOOL)logAllEvents {
return [self.configData[kLogAllEventsKey] boolValue];
- (NSRegularExpression *)whitelistPathRegex {
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
NSString *re = self.configData[kWhitelistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedWhitelistDirRegex;
}
- (void)setLogAllEvents:(BOOL)logAllEvents {
self.configData[kLogAllEventsKey] = @(logAllEvents);
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kWhitelistRegexKey];
} else {
self.configData[kWhitelistRegexKey] = [re pattern];
}
self.cachedWhitelistDirRegex = nil;
[self saveConfigToDisk];
}
- (NSRegularExpression *)blacklistPathRegex {
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
NSString *re = self.configData[kBlacklistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedBlacklistDirRegex;
}
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kBlacklistRegexKey];
} else {
self.configData[kBlacklistRegexKey] = [re pattern];
}
self.cachedBlacklistDirRegex = nil;
[self saveConfigToDisk];
}
- (NSRegularExpression *)fileChangesRegex {
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
NSString *re = self.configData[kFileChangesRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedFileChangesRegex;
}
- (void)setFileChangesRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kFileChangesRegexKey];
} else {
self.configData[kFileChangesRegexKey] = [re pattern];
}
self.cachedFileChangesRegex = nil;
[self saveConfigToDisk];
}
- (BOOL)enablePageZeroProtection {
NSNumber *keyValue = self.configData[kEnablePageZeroProtectionKey];
return keyValue ? [keyValue boolValue] : YES;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
}
@@ -109,12 +202,38 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kEventDetailURLKey];
}
- (NSString *)eventDetailBundleURL {
return self.configData[kEventDetailBundleURLKey];
}
- (NSString *)eventDetailText {
return self.configData[kEventDetailTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configData[kUnknownBlockMessage];
}
- (NSString *)bannedBlockMessage {
return self.configData[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configData[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configData[kModeNotificationLockdown];
}
- (NSURL *)syncBaseURL {
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
NSString *urlStr = self.configData[kSyncBaseURLKey];
if (urlStr) {
NSURL *url = [NSURL URLWithString:urlStr];
if (!url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
return nil;
}
- (NSString *)syncClientAuthCertificateFile {
@@ -141,6 +260,34 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kServerAuthRootsFileKey];
}
- (NSDate *)fullSyncLastSuccess {
return self.configData[kFullSyncLastSuccess];
}
- (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];
}
- (BOOL)syncCleanRequired {
return [self.configData[kSyncCleanRequired] boolValue];
}
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
[self saveConfigToDisk];
}
- (NSString *)machineOwner {
NSString *machineOwner;
@@ -180,40 +327,59 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
}
- (void)reloadConfigData {
if (!self.configData) self.configData = [NSMutableDictionary dictionary];
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
options:NSDataReadingMappedIfSafe
error:&error];
if (error) {
LOGE(@"Could not read configuration file: %@", [error localizedDescription]);
LOGE(@"Could not read configuration file: %@, replacing.", [error localizedDescription]);
[self saveConfigToDisk];
return;
}
NSDictionary *configData =
NSMutableDictionary *configData =
[NSPropertyListSerialization propertyListWithData:readData
options:kCFPropertyListImmutable
options:NSPropertyListMutableContainers
format:NULL
error:&error];
if (error) {
LOGE(@"Could not parse configuration file: %@", [error localizedDescription]);
LOGE(@"Could not parse configuration file: %@, replacing.", [error localizedDescription]);
[self saveConfigToDisk];
return;
}
// Ensure no-one is trying to change the client mode behind Santa's back.
if (self.configData[kClientModeKey] && configData[kClientModeKey] &&
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]] &&
geteuid() == 0) {
NSMutableDictionary *configDataMutable = [configData mutableCopy];
configDataMutable[kClientModeKey] = self.configData[kClientModeKey];
self.configData = configDataMutable;
[self saveConfigToDisk];
if (self.syncBaseURL) {
// Ensure no-one is trying to change protected keys behind our back.
BOOL changed = NO;
if (geteuid() == 0) {
for (NSString *key in self.protectedKeys) {
if (((self.configData[key] && !configData[key]) ||
(!self.configData[key] && configData[key]) ||
(self.configData[key] && ![self.configData[key] isEqual:configData[key]]))) {
if (self.configData[key]) {
configData[key] = self.configData[key];
} else {
[configData removeObjectForKey:key];
}
changed = YES;
LOGI(@"Ignoring changed configuration key: %@", key);
}
}
}
self.configData = configData;
if (changed) [self saveConfigToDisk];
} else {
self.configData = [configData mutableCopy];
self.configData = configData;
}
}
@@ -223,6 +389,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
/// Saves the current @c self.configData to disk.
///
- (void)saveConfigToDisk {
if (geteuid() != 0) return;
[self.configData writeToFile:self.configFilePath atomically:YES];
}

View File

@@ -12,9 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Simple function to check and drop root privileges.
///
/// @return YES if dropping was successful or unnecessary.
///
BOOL DropRootPrivileges();
BOOL DropRootPrivileges(void);

View File

@@ -16,8 +16,9 @@
BOOL DropRootPrivileges() {
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {
if (setgid(-2) != 0 || setgroups(0, NULL) != 0 || setegid(-2) != 0 ||
setuid(-2) != 0 || seteuid(-2) != 0) {
uid_t nobody = (uid_t)-2;
if (setgid(nobody) != 0 || setgroups(0, NULL) != 0 || setegid(nobody) != 0 ||
setuid(nobody) != 0 || seteuid(nobody) != 0) {
return false;
}

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.
@@ -23,6 +25,16 @@
///
/// @param path The path of the file this instance is to represent. The path will be
/// converted to an absolute, standardized path if it isn't already.
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
/// describing the problem.
///
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
///
/// Convenience initializer.
///
/// @param path The path to the file this instance is to represent. The path will be
/// converted to an absolute, standardized path if it isn't already.
///
- (instancetype)initWithPath:(NSString *)path;
@@ -31,6 +43,14 @@
///
- (NSString *)path;
///
/// Hash this file with SHA-1 and SHA-256 simultaneously.
///
/// @param sha1 If not NULL, will be filled with the SHA-1 of the file.
/// @param sha256 If not NULL, will be filled with the SHA-256 of the file.
///
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256;
///
/// @return SHA-1 hash of this binary.
///
@@ -41,12 +61,6 @@
///
- (NSString *)SHA256;
///
/// @return The type of Mach-O file, one of:
/// Dynamic Library, Kernel Extension, Fat Binary or Thin Binary.
///
- (NSString *)machoType;
///
/// @return The architectures included in this binary (e.g. x86_64, ppc).
///
@@ -72,6 +86,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.
///
@@ -82,6 +101,36 @@
///
- (BOOL)isScript;
///
/// @return YES if this file is an XAR archive.
///
- (BOOL)isXARArchive;
///
/// @return YES if this file is a disk image.
///
- (BOOL)isDMG;
///
/// @return YES if this file has a bad/missing __PAGEZERO .
///
- (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.
///
@@ -94,8 +143,8 @@
///
/// @return Either the Info.plist in the bundle this file is part of, or an embedded plist if there
/// is one. In the odd case that a file has both an embedded Info.plist and is part of a bundle,
/// the Info.plist from the bundle will be returned.
/// is one. In the unlikely event that a file has both an embedded Info.plist and is part of a
/// bundle, the embedded plist will be returned.
///
- (NSDictionary *)infoPlist;
@@ -120,9 +169,28 @@
- (NSString *)bundleShortVersionString;
///
/// @return any URLs this file may have been downloaded from, using the
/// @c com.apple.metadata:kMDItemWhereFroms extended attribute.
/// @return LaunchServices quarantine data - download URL as an absolute string.
///
- (NSArray *)downloadURLs;
- (NSString *)quarantineDataURL;
///
/// @return LaunchServices quarantine data - referer URL as an absolute string.
///
- (NSString *)quarantineRefererURL;
///
/// @return LaunchServices quarantine data - agent bundle ID.
///
- (NSString *)quarantineAgentBundleID;
///
/// @return LaunchServices quarantine data - timestamp.
///
- (NSDate *)quarantineTimestamp;
///
/// @return The size of the file in bytes.
///
- (NSUInteger)fileSize;
@end

View File

@@ -18,326 +18,596 @@
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#import <FMDB/FMDB.h>
// Simple class to hold the data of a mach_header and the offset within the file
// in which that header was found.
@interface MachHeaderWithOffset : NSObject
@property NSData *data;
@property uint32_t offset;
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset;
@end
@implementation MachHeaderWithOffset
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset {
self = [super init];
if (self) {
_data = data;
_offset = offset;
}
return self;
}
@end
@interface SNTFileInfo ()
@property NSString *path;
@property NSData *fileData;
@property NSFileHandle *fileHandle;
@property NSUInteger fileSize;
@property NSString *fileOwnerHomeDir;
// Cached properties
@property NSBundle *bundleRef;
@property NSDictionary *infoDict;
@property NSDictionary *quarantineDict;
@property NSDictionary *cachedHeaders;
@end
@implementation SNTFileInfo
- (instancetype)initWithPath:(NSString *)path {
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
self = [super init];
if (self) {
// Convert to absolute, standardized path
path = [path stringByResolvingSymlinksInPath];
if (![path isAbsolutePath]) {
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
path = [cwd stringByAppendingPathComponent:path];
}
path = [path stringByStandardizingPath];
// Determine if file exists.
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
BOOL directory;
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
return nil;
} else if (directory) {
NSString *infoPath = [path stringByAppendingPathComponent:@"Contents/Info.plist"];
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:infoPath];
if (d && d[@"CFBundleExecutable"]) {
path = [path stringByAppendingPathComponent:@"Contents/MacOS"];
_path = [path stringByAppendingPathComponent:d[@"CFBundleExecutable"]];
} else {
return nil;
NSBundle *bndl;
_path = [self resolvePath:path bundle:&bndl];
_bundleRef = bndl;
if (_path.length == 0) {
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}];
}
} else {
_path = path;
return nil;
}
_fileData = [NSData dataWithContentsOfFile:_path options:NSDataReadingMappedIfSafe error:nil];
if (!_fileData) return nil;
int fd = open([_path UTF8String], O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (error) {
NSString *errStr = [NSString stringWithFormat:@"Unable to open file: %s", strerror(errno)];
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
code:280
userInfo:@{NSLocalizedDescriptionKey : errStr}];
}
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;
}
- (NSString *)SHA1 {
unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
CC_SHA1([self.fileData bytes], (unsigned int)[self.fileData length], sha1);
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path error:NULL];
}
// Convert the binary SHA into hex
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[buf appendFormat:@"%02x", (unsigned char)sha1[i]];
#pragma mark Hashing
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
const int MAX_CHUNK_SIZE = 256 * 1024; // 256 KB
const size_t chunkSize = _fileSize > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : _fileSize;
char chunk[chunkSize];
CC_SHA1_CTX c1;
CC_SHA256_CTX c256;
if (sha1) CC_SHA1_Init(&c1);
if (sha256) CC_SHA256_Init(&c256);
int fd = self.fileHandle.fileDescriptor;
fcntl(fd, F_RDAHEAD, 1);
struct radvisory radv;
radv.ra_offset = 0;
const int MAX_ADVISORY_READ = 10 * 1024 * 1024;
radv.ra_count = (int)_fileSize < MAX_ADVISORY_READ ? (int)_fileSize : MAX_ADVISORY_READ;
fcntl(fd, F_RDADVISE, &radv);
ssize_t bytesRead;
for (uint64_t offset = 0; offset < _fileSize;) {
bytesRead = pread(fd, chunk, chunkSize, offset);
if (bytesRead > 0) {
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
offset += bytesRead;
} else if (bytesRead == -1 && errno == EINTR) {
continue;
} else {
return;
}
}
return buf;
// We turn off Read Ahead that we turned on
fcntl(fd, F_RDAHEAD, 0);
if (sha1) {
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(digest, &c1);
NSString *const SHA1FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha1 = [[NSString alloc]
initWithFormat:SHA1FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19]];
}
if (sha256) {
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &c256);
NSString *const SHA256FormatString =
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x";
*sha256 = [[NSString alloc]
initWithFormat:SHA256FormatString, digest[0], digest[1], digest[2],
digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12],
digest[13], digest[14], digest[15], digest[16],
digest[17], digest[18], digest[19], digest[20],
digest[21], digest[22], digest[23], digest[24],
digest[25], digest[26], digest[27], digest[28],
digest[29], digest[30], digest[31]];
}
}
- (NSString *)SHA1 {
NSString *sha1;
[self hashSHA1:&sha1 SHA256:NULL];
return sha1;
}
- (NSString *)SHA256 {
unsigned char sha2[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(self.fileData.bytes, (unsigned int)self.fileData.length, sha2);
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH *2];
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[buf appendFormat:@"%02x", (unsigned char)sha2[i]];
}
return buf;
NSString *sha256;
[self hashSHA1:NULL SHA256:&sha256];
return sha256;
}
- (NSString *)machoType {
if ([self isDylib]) { return @"Dynamic Library"; }
if ([self isKext]) { return @"Kernel Extension"; }
if ([self isFat]) { return @"Fat Binary"; }
if ([self isMachO]) { return @"Thin Binary"; }
if ([self isScript]) { return @"Script"; }
return @"Unknown (not executable?)";
}
#pragma mark File Type Info
- (NSArray *)architectures {
if (![self isMachO]) return nil;
if ([self isFat]) {
NSMutableArray *ret = [[NSMutableArray alloc] init];
// Retrieve just the fat_header, if possible.
NSData *head = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct fat_header))];
if (!head) return nil;
struct fat_header *fat_header = (struct fat_header *)[head bytes];
// Get number of architectures in the binary
uint32_t narch = NSSwapBigIntToHost(fat_header->nfat_arch);
// Retrieve just the fat_arch's, make a mutable copy and if necessary swap the bytes
NSData *archs = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
sizeof(struct fat_arch) * narch)];
if (!archs) return nil;
struct fat_arch *fat_archs = (struct fat_arch *)[archs bytes];
// For each arch, get the name of it's architecture
for (int i = 0; i < narch; ++i) {
[ret addObject:[self nameForCPUType:NSSwapBigIntToHost(fat_archs[i].cputype)]];
}
return ret;
} else {
struct mach_header *hdr = [self firstMachHeader];
return @[ [self nameForCPUType:hdr->cputype] ];
}
return nil;
return [self.machHeaders allKeys];
}
- (BOOL)isDylib {
- (uint32_t)machFileType {
struct mach_header *mach_header = [self firstMachHeader];
if (!mach_header) return NO;
if (mach_header->filetype == MH_DYLIB ||
mach_header->filetype == MH_FVMLIB) {
return YES;
}
return NO;
}
- (BOOL)isKext {
struct mach_header *mach_header = [self firstMachHeader];
if (!mach_header) return NO;
if (mach_header->filetype == MH_KEXT_BUNDLE) {
return YES;
}
return NO;
}
- (BOOL)isMachO {
return ([self.fileData length] >= 160 &&
([self isMachHeader:(struct mach_header *)[self.fileData bytes]] || [self isFat]));
}
- (BOOL)isFat {
return ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]);
}
- (BOOL)isScript {
if ([self.fileData length] < 1) return NO;
char magic[2];
[self.fileData getBytes:&magic length:2];
return (strncmp("#!", magic, 2) == 0);
if (mach_header) return mach_header->filetype;
return -1;
}
- (BOOL)isExecutable {
struct mach_header *mach_header = [self firstMachHeader];
if (!mach_header) return NO;
if (mach_header->filetype == MH_OBJECT ||
mach_header->filetype == MH_EXECUTE ||
mach_header->filetype == MH_PRELOAD) {
return YES;
}
return NO;
return [self machFileType] == MH_EXECUTE;
}
# pragma mark Bundle Information
- (BOOL)isDylib {
return [self machFileType] == MH_DYLIB;
}
- (BOOL)isBundle {
return [self machFileType] == MH_BUNDLE;
}
- (BOOL)isKext {
return [self machFileType] == MH_KEXT_BUNDLE;
}
- (BOOL)isMachO {
return (self.machHeaders.count > 0);
}
- (BOOL)isFat {
return (self.machHeaders.count > 1);
}
- (BOOL)isScript {
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 2)] bytes];
return (magic && memcmp("#!", magic, 2) == 0);
}
- (BOOL)isXARArchive {
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 4)] bytes];
return (magic && memcmp("xar!", magic, 4) == 0);
}
- (BOOL)isDMG {
NSUInteger last512 = self.fileSize - 512;
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
return (magic && memcmp("koly", magic, 4) == 0);
}
#pragma mark Page Zero
- (BOOL)isMissingPageZero {
// This method only checks i386 arch because the kernel enforces this for other archs
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86]];
if (!x86Header) return NO;
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
if (mh->filetype != MH_EXECUTE) return NO;
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
sizeof(struct segment_command));
NSData *lcData = [self safeSubdataWithRange:range];
if (!lcData) return NO;
// This code assumes the __PAGEZERO is always the first load-command in the file.
// Given that the macOS ABI says "the static linker creates a __PAGEZERO segment
// as the first segment of an executable file." this should be OK.
struct load_command *lc = (struct load_command *)[lcData bytes];
if (lc->cmd == LC_SEGMENT) {
struct segment_command *segment = (struct segment_command *)lc;
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
segment->initprot == 0 && segment->maxprot == 0 &&
strcmp("__PAGEZERO", segment->segname) == 0) {
return NO;
}
}
return YES;
}
#pragma mark Bundle Information
///
/// Try and determine the bundle that the represented executable is contained within, if any.
///
/// 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 to count
/// as being part of the bundle.
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
/// 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) return self.bundleRef;
NSArray *pathComponents = [self.path pathComponents];
// Check that the full path is at least 4-levels deep:
// e.g: /Calendar.app/Contents/MacOS/Calendar
if ([pathComponents count] < 4) return nil;
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
self.bundleRef = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
// Clear the bundle if it doesn't have a bundle ID
if (![self.bundleRef objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = nil;
return self.bundleRef;
if (!self.bundleRef) {
self.bundleRef =
[self findBundleWithAncestor:self.useAncestorBundle] ?: (NSBundle *)[NSNull null];
}
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
}
- (NSString *)bundlePath {
return [self.bundle bundlePath];
}
- (NSDictionary *)infoPlist {
if (self.infoDict) return self.infoDict;
if ([self bundle]) {
self.infoDict = [[self bundle] infoDictionary];
return self.infoDict;
- (void)setUseAncestorBundle:(BOOL)useAncestorBundle {
if (self.useAncestorBundle != useAncestorBundle) {
self.bundleRef = nil;
self.infoDict = nil;
}
_useAncestorBundle = useAncestorBundle;
}
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
self.infoDict =
(__bridge_transfer NSDictionary*)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef) url);
return self.infoDict;
- (NSDictionary *)infoPlist {
if (!self.infoDict) {
NSDictionary *d = [self embeddedPlist];
if (d) {
self.infoDict = d;
return self.infoDict;
}
d = self.bundle.infoDictionary;
if (d) {
self.infoDict = d;
return self.infoDict;
}
self.infoDict = (NSDictionary *)[NSNull null];
}
return self.infoDict == (NSDictionary *)[NSNull null] ? nil : self.infoDict;
}
- (NSString *)bundleIdentifier {
return [[self infoPlist] objectForKey:@"CFBundleIdentifier"];
return [[self.infoPlist objectForKey:@"CFBundleIdentifier"] description];
}
- (NSString *)bundleName {
return [[self infoPlist] objectForKey:@"CFBundleName"];
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
[[self.infoPlist objectForKey:@"CFBundleName"] description];
}
- (NSString *)bundleVersion {
return [[self infoPlist] objectForKey:@"CFBundleVersion"];
return [[self.infoPlist objectForKey:@"CFBundleVersion"] description];
}
- (NSString *)bundleShortVersionString {
return [[self infoPlist] objectForKey:@"CFBundleShortVersionString"];
return [[self.infoPlist objectForKey:@"CFBundleShortVersionString"] description];
}
- (NSArray *)downloadURLs {
char *path = (char *)[self.path fileSystemRepresentation];
size_t size = getxattr(path, "com.apple.metadata:kMDItemWhereFroms", NULL, 0, 0, 0);
char *value = malloc(size);
if (!value) return nil;
#pragma mark Quarantine Data
if (getxattr(path, "com.apple.metadata:kMDItemWhereFroms", value, size, 0, 0) == -1) {
free(value);
return nil;
- (NSString *)quarantineDataURL {
NSURL *dataURL = [self quarantineData][@"LSQuarantineDataURL"];
if (dataURL == (NSURL *)[NSNull null]) dataURL = nil;
return [dataURL absoluteString];
}
- (NSString *)quarantineRefererURL {
NSURL *originURL = [self quarantineData][@"LSQuarantineOriginURL"];
if (originURL == (NSURL *)[NSNull null]) originURL = nil;
return [originURL absoluteString];
}
- (NSString *)quarantineAgentBundleID {
NSString *agentBundle = [self quarantineData][@"LSQuarantineAgentBundleIdentifier"];
if (agentBundle == (NSString *)[NSNull null]) agentBundle = nil;
return agentBundle;
}
- (NSDate *)quarantineTimestamp {
NSDate *timeStamp = [self quarantineData][@"LSQuarantineTimeStamp"];
return timeStamp;
}
#pragma mark Internal Methods
- (NSDictionary *)machHeaders {
if (self.cachedHeaders) return self.cachedHeaders;
// Sanity check file length
if (self.fileSize < sizeof(struct mach_header)) {
self.cachedHeaders = [NSDictionary dictionary];
return self.cachedHeaders;
}
NSData *data = [NSData dataWithBytes:value length:size];
free(value);
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
if (data) {
NSArray *urls = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:NULL
error:NULL];
return urls;
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0,
4096)]];
if (machHeader) {
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
machHeaders[[self nameForCPUType:mh->cputype]] = mhwo;
} else {
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
NSData *fatHeader = [self safeSubdataWithRange:range];
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
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];
if (fatArchs) {
struct fat_arch *fat_arch = (struct fat_arch *)[fatArchs mutableBytes];
for (int i = 0; i < nfat_arch; ++i) {
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
int size = OSSwapBigToHostInt32(fat_arch[i].size);
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
range = NSMakeRange(offset, size);
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
if (machHeader) {
NSString *key = [self nameForCPUType:cputype];
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
offset:offset];
machHeaders[key] = mhwo;
}
}
}
}
}
self.cachedHeaders = [machHeaders copy];
return self.cachedHeaders;
}
- (NSData *)parseSingleMachHeader:(NSData *)inputData {
if (inputData.length < sizeof(struct mach_header)) return nil;
struct mach_header *mh = (struct mach_header *)[inputData bytes];
if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) {
NSMutableData *mutableInput = [inputData mutableCopy];
mh = (struct mach_header *)[mutableInput mutableBytes];
swap_mach_header(mh, NXHostByteOrder());
}
if (mh->magic == MH_MAGIC || mh->magic == MH_MAGIC_64) {
return [NSData dataWithBytes:mh length:sizeof(struct mach_header)];
}
return nil;
}
# pragma mark Internal Methods
///
/// Locate an embedded plist in the file
///
- (NSDictionary *)embeddedPlist {
// Look for an embedded Info.plist if there is one.
// This could (and used to) use CFBundleCopyInfoDictionaryForURL but that uses mmap to read
// the file and so can cause SIGBUS if the file is deleted/truncated while it's working.
MachHeaderWithOffset *mhwo = [[self.machHeaders allValues] firstObject];
if (!mhwo) return nil;
struct mach_header *mh = (struct mach_header *)mhwo.data.bytes;
if (mh->filetype != MH_EXECUTE) return self.infoDict;
BOOL is64 = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64);
uint32_t ncmds = mh->ncmds;
uint32_t nsects = 0;
uint64_t offset = mhwo.offset;
uint32_t sz_header = is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header);
uint32_t sz_segment = is64 ? sizeof(struct segment_command_64) : sizeof(struct segment_command);
uint32_t sz_section = is64 ? sizeof(struct section_64) : sizeof(struct section);
offset += sz_header;
// Loop through the load commands looking for the segment named __TEXT
for (uint32_t i = 0; i < ncmds; ++i) {
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
if (!cmdData) return nil;
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
nsects = lc->nsects;
offset += sz_segment;
break;
}
}
offset += lc->cmdsize;
}
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
for (uint32_t i = 0; i < nsects; ++i) {
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
if (!sectData) return nil;
struct section_64 *sect = (struct section_64 *)[sectData bytes];
if (sect && memcmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
if (!plistData) return nil;
NSDictionary *plist;
plist = [NSPropertyListSerialization propertyListWithData:plistData
options:NSPropertyListImmutable
format:NULL
error:NULL];
if (plist) return plist;
}
offset += sz_section;
}
return nil;
}
///
/// Look through the file for the first mach_header. If the file is thin, this will be the
/// header at the beginning of the file. If the file is fat, it will be the first
/// architecture-specific header.
/// Return the first mach_header in this file.
///
- (struct mach_header *)firstMachHeader {
if (![self isMachO]) return NULL;
struct mach_header *mach_header = (struct mach_header *)[self.fileData bytes];
struct fat_header *fat_header = (struct fat_header *)[self.fileData bytes];
if ([self isFatHeader:fat_header]) {
// Get the bytes for the fat_arch
NSData *archHdr = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
sizeof(struct fat_arch))];
if (!archHdr) return nil;
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
// Get bytes for first mach_header
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
sizeof(struct mach_header))];
if (!machHdr) return nil;
mach_header = (struct mach_header *)[machHdr bytes];
}
if ([self isMachHeader:mach_header]) {
return mach_header;
}
return NULL;
}
- (BOOL)isMachHeader:(struct mach_header *)header {
return (header->magic == MH_MAGIC || header->magic == MH_MAGIC_64 ||
header->magic == MH_CIGAM || header->magic == MH_CIGAM_64);
}
- (BOOL)isFatHeader:(struct fat_header *)header {
return (header->magic == FAT_MAGIC || header->magic == FAT_CIGAM);
return (struct mach_header *)([[[[self.machHeaders allValues] firstObject] data] bytes]);
}
///
/// Wrap @c subdataWithRange: in a @@try/@@catch, returning nil on exception.
/// Useful for when the range is beyond the end of the file.
/// Extract a range of the file as an NSData, handling any exceptions.
/// Returns nil if the requested range is outside of the range of the file.
///
- (NSData *)safeSubdataWithRange:(NSRange)range {
@try {
return [self.fileData subdataWithRange:range];
if ((range.location + range.length) > self.fileSize) return nil;
[self.fileHandle seekToFileOffset:range.location];
NSData *d = [self.fileHandle readDataOfLength:range.length];
if (d.length != range.length) return nil;
return d;
}
@catch (NSException *exception) {
@catch (NSException *e) {
return nil;
}
}
///
/// Retrieve quarantine data for a file and caches the dictionary
/// This method attempts to handle fetching the quarantine data even if the running user
/// is not the one who downloaded the file.
///
- (NSDictionary *)quarantineData {
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
self.quarantineDict = (NSDictionary *)[NSNull null];
NSURL *url = [NSURL fileURLWithPath:self.path];
NSDictionary *d = [url resourceValuesForKeys:@[ NSURLQuarantinePropertiesKey ] error:NULL];
if (d[NSURLQuarantinePropertiesKey]) {
d = d[NSURLQuarantinePropertiesKey];
if (d[@"LSQuarantineIsOwnedByCurrentUser"]) {
self.quarantineDict = d;
} else if (d[@"LSQuarantineEventIdentifier"]) {
NSMutableDictionary *quarantineDict = [d mutableCopy];
// If self.path is on a quarantine disk image, LSQuarantineDiskImageURL will point to the
// disk image and self.fileOwnerHomeDir will be incorrect (probably root).
NSString *fileOwnerHomeDir = self.fileOwnerHomeDir;
if (d[@"LSQuarantineDiskImageURL"]) {
struct stat fileStat;
stat([d[@"LSQuarantineDiskImageURL"] fileSystemRepresentation], &fileStat);
if (fileStat.st_uid != 0) {
struct passwd *pwd = getpwuid(fileStat.st_uid);
if (pwd) {
fileOwnerHomeDir = @(pwd->pw_dir);
}
}
}
NSURL *dbPath = [NSURL fileURLWithPathComponents:@[
fileOwnerHomeDir,
@"Library",
@"Preferences",
@"com.apple.LaunchServices.QuarantineEventsV2"
]];
FMDatabase *db = [FMDatabase databaseWithPath:[dbPath absoluteString]];
db.logsErrors = NO;
if ([db open]) {
FMResultSet *rs = [db executeQuery:@"SELECT * FROM LSQuarantineEvent "
@"WHERE LSQuarantineEventIdentifier=?",
d[@"LSQuarantineEventIdentifier"]];
if ([rs next]) {
NSString *agentBundleID = [rs stringForColumn:@"LSQuarantineAgentBundleIdentifier"];
NSString *dataURLString = [rs stringForColumn:@"LSQuarantineDataURLString"];
NSString *originURLString = [rs stringForColumn:@"LSQuarantineOriginURLString"];
double timeStamp = [rs doubleForColumn:@"LSQuarantineTimeStamp"];
quarantineDict[@"LSQuarantineAgentBundleIdentifier"] = agentBundleID;
quarantineDict[@"LSQuarantineDataURL"] = [NSURL URLWithString:dataURLString];
quarantineDict[@"LSQuarantineOriginURL"] = [NSURL URLWithString:originURLString];
quarantineDict[@"LSQuarantineTimestamp"] =
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
self.quarantineDict = quarantineDict;
}
[rs close];
[db close];
}
}
}
}
return (self.quarantineDict == (NSDictionary *)[NSNull null]) ? nil : self.quarantineDict;
}
///
/// Return a human-readable string for a cpu_type_t.
///
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
switch (cpuType) {
case CPU_TYPE_X86:
@@ -354,4 +624,35 @@
return nil;
}
///
/// Resolves a given path:
/// + Follows symlinks
/// + Converts relative paths to absolute
/// + If path is a directory, checks to see if that directory is a bundle and if so
/// returns the path to that bundles CFBundleExecutable and stores a reference to the
/// bundle in the bundle out-param.
///
- (NSString *)resolvePath:(NSString *)path bundle:(NSBundle **)bundle {
// Convert to absolute, standardized path
path = [path stringByResolvingSymlinksInPath];
if (![path isAbsolutePath]) {
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
path = [cwd stringByAppendingPathComponent:path];
}
path = [path stringByStandardizingPath];
// Determine if file exists.
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
BOOL directory;
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
return nil;
} else if (directory && ![path isEqualToString:@"/"]) {
NSBundle *bndl = [NSBundle bundleWithPath:path];
if (bundle) *bundle = bndl;
return [bndl executablePath];
} else {
return path;
}
}
@end

View File

@@ -12,9 +12,11 @@
/// 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. Will continue watching for
/// reload the watch if the file is deleted and continue watching for
/// events until deallocated.
///
@interface SNTFileWatcher : NSObject
@@ -24,11 +26,10 @@
/// Initializes the watcher and begins watching for modifications.
///
/// @param filePath the file to watch.
/// @param handler the handler to call when changes happen.
/// @param handler the handler to call when changes happen. The argument to the block is the
/// type of change that happened as a bitmask to be compared with DISPATCH_VNODE_* constants.
///
/// @note Shortly after the file has been opened and monitoring has begun, the provided handler
/// will be called.
///
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler;
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
handler:(nonnull void (^)(unsigned long))handler;
@end

View File

@@ -14,13 +14,13 @@
#import "SNTFileWatcher.h"
#import "SNTStrengthify.h"
@interface SNTFileWatcher ()
@property NSString *filePath;
@property dispatch_source_t monitoringSource;
@property(copy) void (^handler)(unsigned long);
@property(strong) void (^eventHandler)(void);
@property(strong) void (^internalEventHandler)(void);
@property(strong) void (^internalCancelHandler)(void);
@property dispatch_source_t source;
@end
@implementation SNTFileWatcher
@@ -30,15 +30,13 @@
return nil;
}
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler {
- (instancetype)initWithFilePath:(nonnull NSString *)filePath
handler:(nonnull void (^)(unsigned long))handler {
self = [super init];
if (self) {
_filePath = filePath;
_eventHandler = handler;
if (!_filePath || !_eventHandler) return nil;
[self beginWatchingFile];
_handler = handler;
[self startWatchingFile];
}
return self;
}
@@ -47,56 +45,50 @@
[self stopWatchingFile];
}
- (void)beginWatchingFile {
__weak typeof(self) weakSelf = self;
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE |
DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_RENAME);
- (void)startWatchingFile {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME |
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB);
self.internalEventHandler = ^{
unsigned long l = dispatch_source_get_data(weakSelf.monitoringSource);
if (l & DISPATCH_VNODE_DELETE || l & DISPATCH_VNODE_RENAME) {
if (weakSelf.monitoringSource) dispatch_source_cancel(weakSelf.monitoringSource);
} else {
weakSelf.eventHandler();
dispatch_async(queue, ^{
int fd = -1;
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);
WEAKIFY(self);
dispatch_source_set_event_handler(self.source, ^{
STRONGIFY(self);
unsigned long data = dispatch_source_get_data(self.source);
self.handler(data);
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
[self stopWatchingFile];
[self startWatchingFile];
}
};
sleep(2);
});
self.internalCancelHandler = ^{
int fd;
dispatch_source_set_registration_handler(self.source, ^{
STRONGIFY(self);
self.handler(0);
});
if (weakSelf.monitoringSource) {
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
close(fd);
}
dispatch_source_set_cancel_handler(self.source, ^{
close(fd);
});
while ((fd = open([weakSelf.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
usleep(1000);
}
weakSelf.monitoringSource = dispatch_source_create(
DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
dispatch_source_set_event_handler(weakSelf.monitoringSource, weakSelf.internalEventHandler);
dispatch_source_set_cancel_handler(weakSelf.monitoringSource, weakSelf.internalCancelHandler);
dispatch_resume(weakSelf.monitoringSource);
weakSelf.eventHandler();
};
dispatch_async(queue, self.internalCancelHandler);
dispatch_resume(self.source);
});
}
- (void)stopWatchingFile {
if (!self.monitoringSource) return;
int fd = (int)dispatch_source_get_handle(self.monitoringSource);
dispatch_source_set_event_handler_f(self.monitoringSource, NULL);
dispatch_source_set_cancel_handler(self.monitoringSource, ^{
close(fd);
});
dispatch_source_cancel(self.monitoringSource);
self.monitoringSource = nil;
if (!self.source) return;
dispatch_source_set_event_handler_f(self.source, NULL);
dispatch_source_cancel(self.source);
self.source = nil;
}
@end

View File

@@ -16,17 +16,22 @@
/// Common defines between kernel <-> userspace
///
#include <sys/param.h>
#ifndef SANTA__COMMON__KERNELCOMMON_H
#define SANTA__COMMON__KERNELCOMMON_H
// Defines the lengths of paths and Vnode IDs passed around.
#define MAX_PATH_LEN 1024 // ==PATH_LEN from syslimits.h
#define MAX_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
// Defines the name of the userclient class and the driver bundle ID.
#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,
@@ -34,40 +39,61 @@ enum SantaDriverMethods {
kSantaUserClientDenyBinary,
kSantaUserClientClearCache,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
// Any methods supported by the driver should be added above this line to
// ensure this remains the count of methods.
kSantaUserClientNMethods,
};
typedef enum {
QUEUETYPE_DECISION,
QUEUETYPE_LOG
} santa_queuetype_t;
// Enum defining actions that can be passed down the IODataQueue and in
// response methods.
typedef enum {
ACTION_UNSET = 0,
// CHECKBW
ACTION_REQUEST_CHECKBW = 10,
ACTION_RESPOND_CHECKBW_ALLOW = 11,
ACTION_RESPOND_CHECKBW_DENY = 12,
// REQUESTS
ACTION_REQUEST_SHUTDOWN = 10,
ACTION_REQUEST_BINARY = 11,
// SHUTDOWN
ACTION_REQUEST_SHUTDOWN = 60,
// RESPONSES
ACTION_RESPOND_ALLOW = 20,
ACTION_RESPOND_DENY = 21,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
ACTION_NOTIFY_WRITE = 31,
ACTION_NOTIFY_RENAME = 32,
ACTION_NOTIFY_LINK = 33,
ACTION_NOTIFY_EXCHANGE = 34,
ACTION_NOTIFY_DELETE = 35,
// ERROR
ACTION_ERROR = 99,
} santa_action_t;
#define CHECKBW_RESPONSE_VALID(x) (x == ACTION_RESPOND_CHECKBW_ALLOW || \
x == ACTION_RESPOND_CHECKBW_DENY)
#define RESPONSE_VALID(x) \
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)
// Message struct that is sent down the IODataQueue.
typedef struct {
santa_action_t action;
uint64_t vnode_id;
uid_t userId;
uid_t uid;
gid_t gid;
pid_t pid;
pid_t ppid;
char path[MAX_PATH_LEN];
char path[MAXPATHLEN];
char newpath[MAXPATHLEN];
// For file events, this is the process name.
// For exec requests, this is the parent process name.
// While process names can technically be 4*MAXPATHLEN, that never
// actually happens, so only take MAXPATHLEN and throw away any excess.
char pname[MAXPATHLEN];
} santa_message_t;
#endif // SANTA__COMMON__KERNELCOMMON_H

View File

@@ -21,8 +21,10 @@
#ifdef KERNEL
#include <IOKit/IOLib.h>
#ifdef DEBUG
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n");
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
#else // DEBUG
#define LOGD(...)
#endif // DEBUG
@@ -32,26 +34,31 @@
#else // KERNEL
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_WARN 2
#define LOG_LEVEL_INFO 3
#define LOG_LEVEL_DEBUG 4
@import Foundation;
typedef enum : NSUInteger {
LOG_LEVEL_ERROR,
LOG_LEVEL_WARN,
LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG
} LogLevel;
///
/// Logging function.
///
/// @param level one of the levels defined above
/// @param destination a FILE, generally should be stdout or stderr
/// @param destination a FILE, generally stdout/stderr. If the file is closed, the log
/// will instead be sent to syslog.
/// @param format the printf style format string
/// @param ... the arguments to format.
///
void logMessage(int level, FILE *destination, NSString *format, ...);
void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
__attribute__((format(__NSString__, 3, 4)));
/// Simple logging macros
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__);
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__);
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__);
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__);
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__)
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
#endif // KERNEL

View File

@@ -14,50 +14,80 @@
#import "SNTLogging.h"
#import <asl.h>
#import <pthread.h>
#ifdef DEBUG
static int logLevel = LOG_LEVEL_DEBUG; // default to info
static LogLevel logLevel = LOG_LEVEL_DEBUG;
#else
static int logLevel = LOG_LEVEL_INFO;
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
#endif
void logMessage(int level, FILE *destination, NSString *format, ...) {
static NSDateFormatter *dateFormatter;
static NSString *binaryName;
void syslogClientDestructor(void *arg) {
asl_close((aslclient)arg);
}
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
static BOOL useSyslog = NO;
static const char *binaryName;
static dispatch_once_t pred;
static pthread_key_t syslogKey = 0;
dispatch_once(&pred, ^{
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS'Z"];
binaryName = [[[NSProcessInfo processInfo] processName] UTF8String];
binaryName = [[NSProcessInfo processInfo] processName];
// If debug logging is enabled, the process must be restarted.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
logLevel = LOG_LEVEL_DEBUG;
}
// If debug logging is enabled, the process must be restarted.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
logLevel = LOG_LEVEL_DEBUG;
}
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
useSyslog = YES;
pthread_key_create(&syslogKey, syslogClientDestructor);
}
});
if (logLevel < level) return;
va_list args;
va_start(args, format);
NSString *s = [[NSString alloc] initWithFormat:format arguments:args];
NSMutableString *s = [[NSMutableString alloc] initWithFormat:format arguments:args];
va_end(args);
// Only prepend timestamp, severity and binary name if stdout is not a TTY
if (isatty(fileno(destination))) {
fprintf(destination, "%s\n", [s UTF8String]);
} else {
NSString *levelName;
switch (level) {
case LOG_LEVEL_ERROR: levelName = @"E"; break;
case LOG_LEVEL_WARN: levelName = @"W"; break;
case LOG_LEVEL_INFO: levelName = @"I"; break;
case LOG_LEVEL_DEBUG: levelName = @"D"; break;
if (useSyslog) {
aslclient client = (aslclient)pthread_getspecific(syslogKey);
if (client == NULL) {
client = asl_open(NULL, "com.google.santa", 0);
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
pthread_setspecific(syslogKey, client);
}
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
char *levelName;
int syslogLevel = ASL_LEVEL_DEBUG;
switch (level) {
case LOG_LEVEL_ERROR:
levelName = "E";
syslogLevel = ASL_LEVEL_ERR;
break;
case LOG_LEVEL_WARN:
levelName = "W";
syslogLevel = ASL_LEVEL_WARNING;
break;
case LOG_LEVEL_INFO:
levelName = "I";
syslogLevel = ASL_LEVEL_INFO;
break;
case LOG_LEVEL_DEBUG:
levelName = "D";
syslogLevel = ASL_LEVEL_DEBUG;
break;
}
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
} else {
[s appendString:@"\n"];
size_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
fwrite([s UTF8String], len, 1, destination);
}
}

View File

@@ -12,7 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
@import Foundation;
#import "SNTCommonEnums.h"
///
/// Represents a Rule.
@@ -22,29 +24,29 @@
///
/// The hash of the object this rule is for
///
@property NSString *shasum;
@property(copy) NSString *shasum;
///
/// The state of this rule
///
@property santa_rulestate_t state;
@property SNTRuleState state;
///
/// The type of object this rule is for (binary, certificate)
///
@property santa_ruletype_t type;
@property SNTRuleType type;
///
/// A custom message that will be displayed if this rule blocks a binary from executing
///
@property NSString *customMsg;
@property(copy) NSString *customMsg;
///
/// Designated initializer.
///
- (instancetype)initWithShasum:(NSString *)shasum
state:(santa_rulestate_t)state
type:(santa_ruletype_t)type
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;
@end

View File

@@ -17,12 +17,12 @@
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(santa_rulestate_t)state
type:(santa_ruletype_t)type
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [super init];
if (self) {
_shasum = shasum;
_shasum = shasum;
_state = state;
_type = type;
_customMsg = customMsg;
@@ -34,11 +34,10 @@
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
#define DECODEARRAY(cls, key) \
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
forKey:key]
+ (BOOL)supportsSecureCoding { return YES; }
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.shasum, @"shasum");
@@ -58,4 +57,28 @@
return self;
}
#undef DECODE
#undef ENCODE
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
if (![other isKindOfClass:[SNTRule class]]) return NO;
SNTRule *o = other;
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [self.shasum hash];
result = prime * result + self.state;
result = prime * result + self.type;
return result;
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
self.shasum, self.state, self.type];
}
@end

View File

@@ -12,7 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
@import Foundation;
#import "SNTCommonEnums.h"
///
/// Represents an event stored in the database.
@@ -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;
@@ -35,10 +37,38 @@
@property NSString *filePath;
///
/// If the executed file was part of the bundle, this is the CFBundleName.
/// 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.
///
@property NSString *fileBundleName;
///
/// If the executed file was part of the bundle, this is the path to the bundle.
///
@property NSString *fileBundlePath;
///
/// If the executed file was part of the bundle, this is the CFBundleID.
///
@@ -55,7 +85,7 @@
@property NSString *fileBundleVersionString;
///
/// If the executed file was signed, this is an NSArray of SNTCertificate's
/// If the executed file was signed, this is an NSArray of MOLCertificate's
/// representing the signing chain.
///
@property NSArray *signingChain;
@@ -73,7 +103,7 @@
///
/// The decision santad returned.
///
@property santa_eventstate_t decision;
@property SNTEventState decision;
///
/// NSArray of logged in users when the decision was made.
@@ -95,4 +125,17 @@
///
@property NSNumber *ppid;
///
/// The name of the parent process.
///
@property NSString *parentName;
///
/// Quarantine data about the executed file, if any.
///
@property NSString *quarantineDataURL;
@property NSString *quarantineRefererURL;
@property NSDate *quarantineTimestamp;
@property NSString *quarantineAgentBundleID;
@end

View File

@@ -14,7 +14,7 @@
#import "SNTStoredEvent.h"
#import "SNTCertificate.h"
#import "MOLCertificate.h"
@implementation SNTStoredEvent
@@ -22,16 +22,23 @@
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
#define DECODEARRAY(cls, key) \
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
forKey:key]
forKey:key]
+ (BOOL)supportsSecureCoding { return YES; }
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.idx, @"idx");
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.fileBundleID, @"fileBundleID");
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
@@ -43,9 +50,23 @@
ENCODE(@(self.decision), @"decision");
ENCODE(self.pid, @"pid");
ENCODE(self.ppid, @"ppid");
ENCODE(self.parentName, @"parentName");
ENCODE(self.loggedInUsers, @"loggedInUsers");
ENCODE(self.currentSessions, @"currentSessions");
ENCODE(self.quarantineDataURL, @"quarantineDataURL");
ENCODE(self.quarantineRefererURL, @"quarantineRefererURL");
ENCODE(self.quarantineTimestamp, @"quarantineTimestamp");
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
}
- (instancetype)init {
self = [super init];
if (self) {
_idx = @(arc4random());
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
@@ -55,30 +76,41 @@
_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");
_fileBundleID = DECODE(NSString, @"fileBundleID");
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
_signingChain = DECODEARRAY(SNTCertificate, @"signingChain");
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
_executingUser = DECODE(NSString, @"executingUser");
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
_decision = [DECODE(NSNumber, @"decision") intValue];
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
_pid = DECODE(NSNumber, @"pid");
_ppid = DECODE(NSNumber, @"ppid");
_parentName = DECODE(NSString, @"parentName");
_loggedInUsers = DECODEARRAY(NSString, @"loggedInUsers");
_currentSessions = DECODEARRAY(NSString, @"currentSessions");
_quarantineDataURL = DECODE(NSString, @"quarantineDataURL");
_quarantineRefererURL = DECODE(NSString, @"quarantineRefererURL");
_quarantineTimestamp = DECODE(NSDate, @"quarantineTimestamp");
_quarantineAgentBundleID = DECODE(NSString, @"quarantineAgentBundleID");
}
return self;
}
- (BOOL)isEqual:(SNTStoredEvent *)other {
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
if (![other isKindOfClass:[SNTStoredEvent class]]) return NO;
return ([self.fileSHA256 isEqual:other.fileSHA256] &&
[self.idx isEqual:other.idx]);
SNTStoredEvent *o = other;
return ([self.fileSHA256 isEqual:o.fileSHA256] && [self.idx isEqual:o.idx]);
}
- (NSUInteger)hash {
@@ -91,8 +123,8 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@",
self.idx, self.fileSHA256];
return
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
}
@end

View File

@@ -0,0 +1,22 @@
/// 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.
#define STRONGIFY(var) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
__strong __typeof(var) var = (Weak_##var); \
_Pragma("clang diagnostic pop")
#define WEAKIFY(var) \
__weak __typeof(var) Weak_##var = (var);

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

@@ -21,11 +21,8 @@
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (!platformExpert) return nil;
NSString *serial = CFBridgingRelease(
IORegistryEntryCreateCFProperty(platformExpert,
CFSTR(kIOPlatformSerialNumberKey),
kCFAllocatorDefault,
0));
NSString *serial = CFBridgingRelease(IORegistryEntryCreateCFProperty(
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
IOObjectRelease(platformExpert);
@@ -37,10 +34,8 @@
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
if (!platformExpert) return nil;
NSString *uuid = CFBridgingRelease(
IORegistryEntryCreateCFProperty(platformExpert,
CFSTR(kIOPlatformUUIDKey),
kCFAllocatorDefault, 0));
NSString *uuid = CFBridgingRelease(IORegistryEntryCreateCFProperty(
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
IOObjectRelease(platformExpert);
@@ -65,11 +60,11 @@
return @(hostname);
}
# pragma mark - Internal
#pragma mark - Internal
+ (NSDictionary *)_systemVersionDictionary {
return [NSDictionary dictionaryWithContentsOfFile:
@"/System/Library/CoreServices/SystemVersion.plist"];
return [NSDictionary
dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
}
@end

View File

@@ -0,0 +1,56 @@
/// 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.
/// @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,108 +12,124 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
/// A validating XPC connection/listener which uses codesigning to validate that both ends of the
/// connection were signed by the same certificate chain.
///
/// Example server started by @c launchd where the @c launchd job has a @c MachServices key:
///
/// @code
/// SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
/// conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
/// conn.exportedObject = myObject;
/// conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
/// [conn resume];
/// @endcode
///
/// Example client, connecting to above server:
///
/// @code
/// SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
/// withOptions:0];
/// conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
/// conn.exportedObject = myObject;
/// conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
/// conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
/// [conn resume];
/// @endcode
///
/// Either side can then send a message to the other with:
///
/// @code
/// [conn.remoteObjectProxy selectorInRemoteInterface];
/// @endcode
///
/// @note messages are always delivered on a background thread!
///
@import Foundation;
/**
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
validation of connecting clients and forced connection establishment.
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
@code
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
conn.exportedObject = myObject;
[conn resume];
@endcode
Example client, connecting to above server:
@code
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
withOptions:0];
conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
[conn resume];
@endcode
The client can send a message to the server with:
@code
[conn.remoteObjectProxy selectorInRemoteInterface];
@endcode
One advantage of the way that SNTXPCConnection works over using NSXPCConnection directly is that
from the client-side once the resume method has finished, the connection is either valid or the
invalidation handler will be called. Ordinarily, the connection doesn't actually get made until
the first message is sent across it.
@note messages are always delivered on a background thread!
*/
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
typedef void (^SNTXPCInvalidationBlock)(void);
typedef void (^SNTXPCAcceptedBlock)(void);
typedef void (^SNTXPCRejectedBlock)(void);
/**
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
*/
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
///
/// The interface the remote object should conform to.
///
@property(retain) NSXPCInterface *remoteInterface;
/**
Initializer for the 'server' side of the connection, started by launchd.
///
/// A proxy to the object at the other end of the connection.
///
/// @warning Do not send a message to this object if you didn't set @c remoteInterface above
/// before calling the @c resume method. Doing so will throw an exception.
///
@property(readonly) id remoteObjectProxy;
@param name MachService name, must match the MachServices key in the launchd.plist
*/
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
///
/// The interface this object exports.
///
@property(retain) NSXPCInterface *exportedInterface;
/**
Initialize a new client to a service exported by a LaunchDaemon.
///
/// The object that responds to messages from the other end.
///
@property(retain) id exportedObject;
@param name MachService name
@param privileged Use YES if the server is running as root.
*/
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
///
/// A block to run when the connection is invalidated.
///
@property(copy) SNTXPCInvalidationBlock invalidationHandler;
/**
Initialize a new client to a service within a bundle.
///
/// A block to run when the connection has been accepted.
///
@property(copy) SNTXPCAcceptedBlock acceptedHandler;
@param name service name
*/
- (nullable instancetype)initClientWithServiceName:(nonnull NSString *)name;
///
/// A block to run when the connection has been rejected.
///
@property(copy) SNTXPCRejectedBlock rejectedHandler;
/**
Initialize a new client with a listener endpoint sent from another process.
///
/// Initializer for the 'server' side of the connection, the binary that was started by launchd.
///
/// @param name MachService name
///
- (instancetype)initServerWithName:(NSString *)name;
@param listener An NSXPCListenerEndpoint to connect to.
*/
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
///
/// Initializer for the 'client' side of the connection.
///
/// @param name MachService name
/// @param options Use NSXPCConnectionPrivileged if the server is running as root, otherwise use 0.
///
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options;
/**
Call when the properties of the object have been set-up and you're ready for connections.
///
/// Call when the properties of the object have been set-up and you're ready for connections.
/// Blocks the executing thread for up to 5s while waiting for the verification to complete.
///
For clients, this call can take up to 2s to complete for connection to finish establishing though
in basically all cases it will actually complete in a few milliseconds.
*/
- (void)resume;
///
/// Invalidate the connection. This must be done before the connection can be released.
///
/**
Invalidate the connection(s). This must be done before the object can be released.
*/
- (void)invalidate;
/**
The interface the remote object should conform to. (client)
*/
@property(retain, nullable) NSXPCInterface *remoteInterface;
/**
A proxy to the object at the other end of the connection. (client)
@note If the connection to the server failed, this will be nil, so you can safely send messages
and rely on the invalidationHandler for handling the failure.
*/
@property(readonly, nonatomic, nullable) id remoteObjectProxy;
/**
The interface this object exports. (server)
*/
@property(retain, nullable) NSXPCInterface *exportedInterface;
/**
The object that responds to messages from the other end. (server)
*/
@property(retain, nullable) id exportedObject;
/**
A block to run when a/the connection is accepted and fully established.
*/
@property(copy, nullable) void (^acceptedHandler)(void);
/**
A block to run when a/the connection is invalidated/interrupted/rejected.
*/
@property(copy, nullable) void (^invalidationHandler)(void);
@end

View File

@@ -14,55 +14,94 @@
#import "SNTXPCConnection.h"
#import "SNTCodesignChecker.h"
#import "MOLCodesignChecker.h"
@protocol XPCConnectionValidityRequest
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block;
#import "SNTStrengthify.h"
/**
Protocol used during connection establishment, @see SNTXPCConnectionInterface
*/
@protocol SNTXPCConnectionProtocol
- (void)connectWithReply:(void (^)())reply;
@end
/**
Recipient object used during connection establishment. Each incoming connection
has one of these objects created which accept the message in the protocol
and call the block provided during creation before replying.
This allows the server to reset the connection's exported interface and
object to the correct values after the client has sent the establishment message.
*/
@interface SNTXPCConnectionInterface : NSObject<SNTXPCConnectionProtocol>
@property(strong) void (^block)(void);
@end
@implementation SNTXPCConnectionInterface
- (void)connectWithReply:(void (^)())reply {
if (self.block) self.block();
reply();
}
@end
@interface SNTXPCConnection ()
@property NSXPCInterface *validationInterface;
///
/// The XPC listener (used on server-side only).
///
/// The XPC listener (server only).
@property NSXPCListener *listenerObject;
///
/// The current connection object.
///
/// The current connection object (client only).
@property NSXPCConnection *currentConnection;
///
/// The remote interface to use while the connection hasn't been validated.
///
@property NSXPCInterface *validatorInterface;
@end
@implementation SNTXPCConnection
#pragma mark Initializers
- (instancetype)initServerWithName:(NSString *)name {
- (instancetype)initServerWithListener:(NSXPCListener *)listener {
self = [super init];
if (self) {
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
_listenerObject = [[NSXPCListener alloc] initWithMachServiceName:name];
if (!_validatorInterface || !_listenerObject) return nil;
_listenerObject = listener;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options {
- (instancetype)initServerWithName:(NSString *)name {
return [self initServerWithListener:[[NSXPCListener alloc] initWithMachServiceName:name]];
}
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener {
self = [super init];
if (self) {
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name
options:options];
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
if (!_validatorInterface || !_currentConnection) return nil;
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged {
self = [super init];
if (self) {
NSXPCConnectionOptions options = (privileged ? NSXPCConnectionPrivileged : 0);
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
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;
}
@@ -75,148 +114,90 @@
#pragma mark Connection set-up
- (void)resume {
if (_listenerObject) {
// A new listener doesn't do anything until a client connects.
if (self.listenerObject) {
self.listenerObject.delegate = self;
[self.listenerObject resume];
} else {
// A new client begins the validation process.
NSXPCConnection *connection = _currentConnection;
WEAKIFY(self);
connection.remoteObjectInterface = _validatorInterface;
connection.invalidationHandler = ^{
[self invokeInvalidationHandler];
self.currentConnection = nil;
// Set-up the connection with the remote interface set to the validation interface,
// 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.currentConnection.invalidationHandler = ^{
STRONGIFY(self);
if (self.invalidationHandler) self.invalidationHandler();
};
connection.interruptionHandler = ^{
[self.currentConnection invalidate];
};
[connection resume];
__block BOOL verificationComplete = NO;
[[connection remoteObjectProxy] isConnectionValidWithBlock:^void(BOOL response) {
pid_t pid = self.currentConnection.processIdentifier;
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
if (response && [otherCS signingInformationMatches:selfCS]) {
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
self.currentConnection.exportedInterface = self.exportedInterface;
self.currentConnection.exportedObject = self.exportedObject;
[self invokeAcceptedHandler];
[self.currentConnection resume];
verificationComplete = YES;
} else {
[self invokeRejectedHandler];
[self.currentConnection invalidate];
self.currentConnection = nil;
verificationComplete = YES;
}
[self.currentConnection resume];
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
STRONGIFY(self);
// The connection is now established
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
[self.currentConnection resume];
dispatch_semaphore_signal(sema);
if (self.acceptedHandler) self.acceptedHandler();
}];
// Wait for validation to complete, at most 5s
for (int sleepLoops = 0; sleepLoops < 1000 && !verificationComplete; sleepLoops++) {
usleep(5000);
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
// Connection was not established in a reasonable time, invalidate.
self.currentConnection.remoteObjectInterface = nil; // ensure clients don't try to use it.
[self.currentConnection invalidate];
}
if (!verificationComplete) [self invalidate];
}
}
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
// Reject connection if a connection already exists. As the invalidation/interruption handlers
// both cause the currentConnection to be nil'd out, this should be OK.
if (self.currentConnection) return NO;
pid_t pid = connection.processIdentifier;
MOLCodesignChecker *otherCS = [[MOLCodesignChecker alloc] initWithPID:pid];
if (![otherCS signingInformationMatches:[[MOLCodesignChecker alloc] initWithSelf]]) {
return NO;
}
connection.exportedObject = self;
connection.exportedInterface = _validatorInterface;
// The client passed the code signature check, now we need to resume the listener and
// return YES so that the client can send the connectWithReply message. Once the client does
// we reset the connection's exportedInterface and exportedObject.
SNTXPCConnectionInterface *ci = [[SNTXPCConnectionInterface alloc] init];
WEAKIFY(self);
WEAKIFY(connection);
ci.block = ^{
STRONGIFY(self)
STRONGIFY(connection);
[connection suspend];
connection.invalidationHandler = connection.interruptionHandler = ^{
if (self.invalidationHandler) self.invalidationHandler();
};
connection.exportedInterface = self.exportedInterface;
connection.exportedObject = self.exportedObject;
[connection resume];
connection.invalidationHandler = ^{
[self invokeInvalidationHandler];
self.currentConnection = nil;
// The connection is now established.
if (self.acceptedHandler) self.acceptedHandler();
};
connection.interruptionHandler = ^{
// Invalidate the connection, causing the handler above to run
[self.currentConnection invalidate];
};
// At this point the client is connected and can send messages but the only message it can send
// is isConnectionValidWithBlock: and we won't send anything to it until it has.
self.currentConnection = connection;
connection.exportedInterface = self.validationInterface;
connection.exportedObject = ci;
[connection resume];
return YES;
}
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block {
pid_t pid = self.currentConnection.processIdentifier;
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
if ([otherCS signingInformationMatches:selfCS]) {
[self.currentConnection suspend];
self.currentConnection.remoteObjectInterface = self.remoteInterface;
self.currentConnection.exportedInterface = self.exportedInterface;
self.currentConnection.exportedObject = self.exportedObject;
[self.currentConnection resume];
[self invokeAcceptedHandler];
// Let remote end know that we accepted. In acception this must come last otherwise
// the remote end might start sending messages before the interface is fully set-up.
block(YES);
} else {
// Let remote end know that we rejected. In rejection this must come first otherwise
// the connection is invalidated before the client ever realizes.
block(NO);
[self invokeRejectedHandler];
[self.currentConnection invalidate];
self.currentConnection = nil;
}
}
- (id)remoteObjectProxy {
if (self.currentConnection && self.currentConnection.remoteObjectInterface) {
if (self.currentConnection.remoteObjectInterface &&
self.currentConnection.remoteObjectInterface != self.validationInterface) {
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
[self.currentConnection invalidate];
[self.currentConnection invalidate];
}];
}
return nil;
}
- (void)invokeAcceptedHandler {
if (self.acceptedHandler) {
self.acceptedHandler();
}
}
- (void)invokeRejectedHandler {
if (self.rejectedHandler) {
self.rejectedHandler();
}
}
- (void)invokeInvalidationHandler {
if (self.invalidationHandler) {
self.invalidationHandler();
}
}
#pragma mark Connection tear-down
- (void)invalidate {
if (self.currentConnection) {
[self.currentConnection invalidate];
self.currentConnection = nil;
} else if (self.listenerObject) {
[self.listenerObject invalidate];
}
}

View File

@@ -12,10 +12,18 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SNTCommonEnums.h"
@import Foundation;
#import <MOLCertificate/MOLCertificate.h>
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTRule;
@class SNTStoredEvent;
@class SNTXPCConnection;
///
/// Protocol implemented by santad and utilized by santactl
@@ -25,26 +33,74 @@
///
/// Kernel ops
///
- (void)cacheCount:(void (^)(uint64_t))reply;
- (void)cacheCount:(void (^)(int64_t))reply;
- (void)flushCache:(void (^)(BOOL))reply;
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply;
///
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(uint64_t binary, uint64_t certificate))reply;
- (void)databaseRuleAddRule:(SNTRule *)rule cleanSlate:(BOOL)cleanSlate reply:(void (^)())reply;
- (void)databaseRuleAddRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate reply:(void (^)())reply;
- (void)databaseEventCount:(void (^)(uint64_t count))reply;
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTRule *))reply;
///
/// Decision ops
///
///
/// Misc 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)clientMode:(void (^)(santa_clientmode_t))reply;
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token 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
@@ -61,4 +117,10 @@
///
+ (NSXPCInterface *)controlInterface;
///
/// Retrieve a pre-configured SNTXPCConnection for communicating with santad.
/// Connections just needs any handlers set and then can be resumed and used.
///
+ (SNTXPCConnection *)configuredConnection;
@end

View File

@@ -16,6 +16,7 @@
#import "SNTRule.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
@implementation SNTXPCControlInterface
@@ -36,7 +37,24 @@
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;
}
+ (SNTXPCConnection *)configuredConnection {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithName:[self serviceId]
privileged:YES];
c.remoteInterface = [self controlInterface];
return c;
}
@end

View File

@@ -12,23 +12,40 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
/// Protocol implemented by SantaNotifier and utilized by santad
@import Foundation;
#import "SNTCommonEnums.h"
#import "SNTXPCBundleServiceInterface.h"
@class SNTStoredEvent;
/// Protocol implemented by SantaGUI and utilized by santad
@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;
- (void)setBundleServiceListener:(NSXPCListenerEndpoint *)listener;
@end
@interface SNTXPCNotifierInterface : NSObject
///
/// @return the MachService ID for this service.
///
+ (NSString *)serviceId;
///
/// @return an initialized NSXPCInterface for the SNTNotifierXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (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

@@ -16,13 +16,12 @@
@implementation SNTXPCNotifierInterface
+ (NSString *)serviceId {
return @"SantaXPCNotifications";
+ (NSXPCInterface *)notifierInterface {
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
}
+ (NSXPCInterface *)notifierInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
return r;
+ (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

@@ -0,0 +1,271 @@
/// 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.
#ifndef SANTA__SANTA_DRIVER__SANTACACHE_H
#define SANTA__SANTA_DRIVER__SANTACACHE_H
#include <libkern/OSAtomic.h>
#include <libkern/OSTypes.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include "SNTKernelCommon.h"
#ifdef KERNEL
#include <IOKit/IOLib.h>
#else // KERNEL
// Support for unit testing.
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define panic(args...) printf(args); printf("\n"); abort()
#define IOMalloc malloc
#define IOMallocAligned(sz, alignment) malloc(sz);
#define IOFree(addr, sz) free(addr)
#define IOFreeAligned(addr, sz) free(addr)
#define OSTestAndSet OSAtomicTestAndSet
#define OSTestAndClear(bit, addr) OSAtomicTestAndClear(bit, addr) == 0
#define OSIncrementAtomic(addr) OSAtomicIncrement64((volatile int64_t *)addr)
#define OSDecrementAtomic(addr) OSAtomicDecrement64((volatile int64_t *)addr)
#endif // KERNEL
/**
A somewhat simple, concurrent linked-list hash table intended for use in IOKit kernel extensions.
Maps 64-bit unsigned integer keys to values.
Enforces a maximum size by clearing all entries if a new value
is added that would go over the maximum size declared at creation.
The number of buckets is calculated as `maximum_size` / `per_bucket`
rounded up to the next power of 2. Locking is done per-bucket.
*/
template<class T> class SantaCache {
public:
/**
Initialize a newly created cache.
@param maximum_size The maximum number of entries in this cache. Once this
number is reached all the entries will be purged.
@param per_bucket The target number of entries in each bucket when cache is full.
A higher number will result in better performance but higher memory usage.
Cannot be higher than 64 to try and ensure buckets don't overflow.
*/
SantaCache(uint64_t maximum_size = 10000, uint8_t per_bucket = 5) {
if (unlikely(per_bucket < 1)) per_bucket = 1;
if (unlikely(per_bucket > 64)) per_bucket = 64;
max_size_ = maximum_size;
bucket_count_ = 1 << (32 - __builtin_clz(
((uint32_t)max_size_ / per_bucket) - 1));
buckets_ = (struct bucket *)IOMalloc(bucket_count_ * sizeof(struct bucket));
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
}
/**
Clear and free memory
*/
~SantaCache() {
clear();
IOFree(buckets_, bucket_count_ * sizeof(struct bucket));
}
/**
Get an element from the cache. Returns zero_ if item doesn't exist.
*/
T get(uint64_t key) {
struct bucket *bucket = &buckets_[hash(key)];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
if (entry->key == key) {
T val = entry->value;
unlock(bucket);
return val;
}
entry = entry->next;
}
unlock(bucket);
return zero_;
}
/**
Set an element in the cache.
@note If the cache is full when this is called, this will empty the cache before
inserting the new value.
@return if an existing value was replaced, the previous value, otherwise zero_
*/
T set(uint64_t key, T value) {
struct bucket *bucket = &buckets_[hash(key)];
lock(bucket);
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
struct entry *previous_entry = nullptr;
while (entry != nullptr) {
if (entry->key == key) {
T existing_value = entry->value;
entry->value = value;
if (value == zero_) {
if (previous_entry != nullptr) {
previous_entry->next = entry->next;
} else {
bucket->head = (struct entry *)((uintptr_t)entry->next + 1);
}
IOFreeAligned(entry, sizeof(struct entry));
OSDecrementAtomic(&count_);
}
unlock(bucket);
return existing_value;
}
previous_entry = entry;
entry = entry->next;
}
// If value is zero_, we're clearing but there's nothing to clear
// so we don't need to do anything else.
if (value == zero_) {
unlock(bucket);
return zero_;
}
// Check that adding this new item won't take the cache over its maximum size.
if (count_ + 1 > max_size_) {
unlock(bucket);
lock(&clear_bucket_);
// Check again in case clear has already run while waiting for lock
if (count_ + 1 > max_size_) {
clear();
}
lock(bucket);
unlock(&clear_bucket_);
}
// Allocate a new entry, set the key and value, then set the next pointer as the current
// first entry in the bucket then make this new entry the first in the bucket.
struct entry *new_entry = (struct entry *)IOMallocAligned(sizeof(struct entry), 2);
new_entry->key = key;
new_entry->value = value;
new_entry->next = (struct entry *)((uintptr_t)bucket->head - 1);
bucket->head = (struct entry *)((uintptr_t)new_entry + 1);
OSIncrementAtomic(&count_);
unlock(bucket);
return zero_;
}
/**
An alias for `set(key, zero_)`
*/
inline void remove(uint64_t key) {
set(key, zero_);
}
/**
Remove all entries and free bucket memory.
*/
void clear() {
for (uint32_t i = 0; i < bucket_count_; ++i) {
struct bucket *bucket = &buckets_[i];
// We grab the lock so nothing can use this bucket while we're erasing it
// and never release it. It'll be 'released' when the bzero call happens
// at the end of this function.
lock(bucket);
// Free the bucket's entries, if there are any.
struct entry *entry = (struct entry *)((uintptr_t)bucket->head - 1);
while (entry != nullptr) {
struct entry *next_entry = entry->next;
IOFreeAligned(entry, sizeof(struct entry));
entry = next_entry;
}
}
// Reset cache count, no atomicity needed as we hold all the bucket locks.
count_ = 0;
// This resets all of the bucket counts and locks. Releasing the locks for
// each bucket isn't really atomic here but each bucket will be zero'd
// before the lock is released as the lock is the last thing in a bucket.
bzero(buckets_, bucket_count_ * sizeof(struct bucket));
}
/**
Return number of entries currently in cache.
*/
inline uint64_t count() const {
return count_;
}
private:
struct entry {
uint64_t key;
T value;
struct entry *next;
};
struct bucket {
// The least significant bit of this pointer is always 0 (due to alignment),
// so we utilize that bit as the lock for the bucket.
struct entry *head;
};
/**
Lock a bucket. Spins until the lock is acquired.
*/
inline void lock(struct bucket *bucket) const {
while (OSTestAndSet(7, (volatile uint8_t *)&bucket->head));
}
/**
Unlock a bucket. Panics if the lock wasn't locked.
*/
inline void unlock(struct bucket *bucket) const {
if (unlikely(OSTestAndClear(7, (volatile uint8_t *)&bucket->head))) {
panic("SantaCache::unlock(): Tried to unlock an unlocked lock");
}
}
uint64_t count_ = 0;
uint64_t max_size_;
uint32_t bucket_count_;
struct bucket *buckets_;
/**
Holder for a 'zero' entry for the current type
*/
const T zero_ = T();
/**
Special bucket used when automatically clearing due to size
to prevent two threads trying to clear at the same time and
getting stuck.
*/
struct bucket clear_bucket_ = {};
/**
Hash a key to determine which bucket it belongs in.
Multiplicative hash using a prime near to the golden ratio, per Knuth.
This seems to have good bucket distribution generally and for the range of
values we expect to see.
*/
inline uint64_t hash(uint64_t input) const {
return (input * 11400714819323198549ul) % bucket_count_;
}
};
#endif // SANTA__SANTA_DRIVER__SANTACACHE_H

View File

@@ -20,104 +20,165 @@ OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
#pragma mark Object Lifecycle
bool SantaDecisionManager::init() {
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", lck_grp_attr_alloc_init());
dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
if (!super::init()) return false;
cached_decisions_ = OSDictionary::withCapacity(1000);
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
sdm_lock_attr_ = lck_attr_alloc_init();
owning_pid_ = 0;
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
return kIOReturnSuccess;
decision_cache_ = new SantaCache<uint64_t>(10000, 2);
vnode_pid_map_ = new SantaCache<uint64_t>(2000, 5);
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
if (!decision_dataqueue_) return kIOReturnNoMemory;
log_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxLogQueueEvents, sizeof(santa_message_t));
if (!log_dataqueue_) return kIOReturnNoMemory;
client_pid_ = 0;
ts_ = { .tv_sec = kRequestLoopSleepMilliseconds / 1000,
.tv_nsec = kRequestLoopSleepMilliseconds % 1000 * 1000000 };
return true;
}
void SantaDecisionManager::free() {
if (cached_decisions_) {
cached_decisions_->release();
cached_decisions_ = NULL;
delete decision_cache_;
delete vnode_pid_map_;
if (decision_dataqueue_lock_) {
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
decision_dataqueue_lock_ = nullptr;
}
if (cached_decisions_lock_) {
lck_rw_free(cached_decisions_lock_, sdm_lock_grp_);
cached_decisions_lock_ = NULL;
if (log_dataqueue_lock_) {
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
log_dataqueue_lock_ = nullptr;
}
if (dataqueue_lock_ ) {
lck_mtx_free(dataqueue_lock_, sdm_lock_grp_);
dataqueue_lock_ = NULL;
if (sdm_lock_attr_) {
lck_attr_free(sdm_lock_attr_);
sdm_lock_attr_ = nullptr;
}
if (sdm_lock_grp_) {
lck_grp_free(sdm_lock_grp_);
sdm_lock_grp_ = NULL;
sdm_lock_grp_ = nullptr;
}
if (sdm_lock_grp_attr_) {
lck_grp_attr_free(sdm_lock_grp_attr_);
sdm_lock_grp_attr_ = nullptr;
}
OSSafeReleaseNULL(decision_dataqueue_);
OSSafeReleaseNULL(log_dataqueue_);
super::free();
}
#pragma mark Client Management
void SantaDecisionManager::ConnectClient(IOSharedDataQueue *queue, pid_t pid) {
void SantaDecisionManager::ConnectClient(pid_t pid) {
if (!pid) return;
if (!queue) return;
client_pid_ = pid;
// Any decisions made while the daemon wasn't
// connected should be cleared
ClearCache();
lck_mtx_lock(dataqueue_lock_);
dataqueue_ = queue;
dataqueue_->retain();
lck_mtx_unlock(dataqueue_lock_);
owning_pid_ = pid;
owning_proc_ = proc_find(pid);
failed_queue_requests_ = 0;
failed_decision_queue_requests_ = 0;
failed_log_queue_requests_ = 0;
}
void SantaDecisionManager::DisconnectClient() {
if (owning_pid_ < 1) return;
owning_pid_ = -1;
void SantaDecisionManager::DisconnectClient(bool itDied) {
if (client_pid_ < 1) return;
client_pid_ = 0;
// Ask santad to shutdown, in case it's running.
santa_message_t message;
message.action = ACTION_REQUEST_SHUTDOWN;
message.userId = 0;
message.pid = 0;
message.ppid = 0;
message.vnode_id = 0;
PostToQueue(message);
if (!itDied) {
auto message = new santa_message_t;
message->action = ACTION_REQUEST_SHUTDOWN;
PostToDecisionQueue(message);
delete message;
decision_dataqueue_->setNotificationPort(nullptr);
} else {
// If the client died, reset the data queues so when it reconnects
// it doesn't get swamped straight away.
lck_mtx_lock(decision_dataqueue_lock_);
decision_dataqueue_->release();
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(decision_dataqueue_lock_);
lck_mtx_lock(dataqueue_lock_);
dataqueue_->release();
dataqueue_ = NULL;
lck_mtx_unlock(dataqueue_lock_);
proc_rele(owning_proc_);
owning_proc_ = NULL;
lck_mtx_lock(log_dataqueue_lock_);
log_dataqueue_->release();
log_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxLogQueueEvents, sizeof(santa_message_t));
lck_mtx_unlock(log_dataqueue_lock_);
}
}
bool SantaDecisionManager::ClientConnected() {
return owning_pid_ > 0;
bool SantaDecisionManager::ClientConnected() const {
if (client_pid_ <= 0) return false;
auto p = proc_find(client_pid_);
auto is_exiting = false;
if (p) {
is_exiting = proc_exiting(p);
proc_rele(p);
}
return (client_pid_ > 0 && !is_exiting);
}
# pragma mark Listener Control
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
lck_mtx_lock(decision_dataqueue_lock_);
decision_dataqueue_->setNotificationPort(port);
lck_mtx_unlock(decision_dataqueue_lock_);
}
void SantaDecisionManager::SetLogPort(mach_port_t port) {
lck_mtx_lock(log_dataqueue_lock_);
log_dataqueue_->setNotificationPort(port);
lck_mtx_unlock(log_dataqueue_lock_);
}
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
return decision_dataqueue_->getMemoryDescriptor();
}
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
return log_dataqueue_->getMemoryDescriptor();
}
#pragma mark Listener Control
kern_return_t SantaDecisionManager::StartListener() {
vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE,
vnode_scope_callback,
reinterpret_cast<void *>(this));
vnode_listener_ = kauth_listen_scope(
KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
if (!vnode_listener_) return kIOReturnInternalError;
LOGD("Vnode listener started.");
fileop_listener_ = kauth_listen_scope(
KAUTH_SCOPE_FILEOP, fileop_scope_callback, reinterpret_cast<void *>(this));
if (!fileop_listener_) return kIOReturnInternalError;
LOGD("Listeners started.");
return kIOReturnSuccess;
}
kern_return_t SantaDecisionManager::StopListener() {
kauth_unlisten_scope(vnode_listener_);
vnode_listener_ = NULL;
vnode_listener_ = nullptr;
kauth_unlisten_scope(fileop_listener_);
fileop_listener_ = nullptr;
// Wait for any active invocations to finish before returning
do {
@@ -127,7 +188,7 @@ kern_return_t SantaDecisionManager::StopListener() {
// Delete any cached decisions
ClearCache();
LOGD("Vnode listener stopped.");
LOGD("Listeners stopped.");
return kIOReturnSuccess;
}
@@ -135,208 +196,182 @@ kern_return_t SantaDecisionManager::StopListener() {
#pragma mark Cache Management
void SantaDecisionManager::AddToCache(
const char *identifier, santa_action_t decision, uint64_t microsecs) {
lck_rw_lock_exclusive(cached_decisions_lock_);
uint64_t identifier, santa_action_t decision, uint64_t microsecs) {
// Decision is stored in upper 8 bits, timestamp in remaining 56.
uint64_t val = ((uint64_t)decision << 56) | (microsecs & 0xFFFFFFFFFFFFFF);
if (cached_decisions_->getCount() > kMaxCacheSize) {
// This could be made a _lot_ smarter, say only removing entries older
// than a certain time period. However, with a kMaxCacheSize set
// sufficiently large and a kMaxAllowCacheTimeMilliseconds set
// sufficiently low, this should only ever occur if someone is purposefully
// trying to make the cache grow.
LOGD("Cache too large, flushing.");
cached_decisions_->flushCollection();
// If a previous entry was not found and the new entry is not `REQUEST_BINARY`, remove the
// existing entry. This is to prevent adding an ALLOW to the cache after a write has occurred.
if (decision_cache_->set(identifier, val) == 0 && decision != ACTION_REQUEST_BINARY) {
decision_cache_->remove(identifier);
}
if (decision == ACTION_REQUEST_CHECKBW) {
SantaMessage *pending = new SantaMessage();
pending->setAction(ACTION_REQUEST_CHECKBW, 0);
cached_decisions_->setObject(identifier, pending);
pending->release(); // it was retained when added to the dictionary
} else {
SantaMessage *pending = OSDynamicCast(
SantaMessage, cached_decisions_->getObject(identifier));
if (pending) {
pending->setAction(decision, microsecs);
}
}
lck_rw_unlock_exclusive(cached_decisions_lock_);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
void SantaDecisionManager::CacheCheck(const char *identifier) {
lck_rw_lock_shared(cached_decisions_lock_);
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != NULL);
if (shouldInvalidate) {
lck_rw_lock_shared_to_exclusive(cached_decisions_lock_);
cached_decisions_->removeObject(identifier);
lck_rw_unlock_exclusive(cached_decisions_lock_);
} else {
lck_rw_unlock_shared(cached_decisions_lock_);
}
void SantaDecisionManager::RemoveFromCache(uint64_t identifier) {
decision_cache_->remove(identifier);
if (unlikely(!identifier)) return;
wakeup((void *)identifier);
}
uint64_t SantaDecisionManager::CacheCount() {
return cached_decisions_->getCount();
uint64_t SantaDecisionManager::CacheCount() const {
return decision_cache_->count();
}
void SantaDecisionManager::ClearCache() {
lck_rw_lock_exclusive(cached_decisions_lock_);
cached_decisions_->flushCollection();
lck_rw_unlock_exclusive(cached_decisions_lock_);
decision_cache_->clear();
}
santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
santa_action_t result = ACTION_UNSET;
#pragma mark Decision Fetching
santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
lck_rw_lock_shared(cached_decisions_lock_);
SantaMessage *cached_decision = OSDynamicCast(
SantaMessage, cached_decisions_->getObject(identifier));
if (cached_decision) {
result = cached_decision->getAction();
decision_time = cached_decision->getMicrosecs();
}
lck_rw_unlock_shared(cached_decisions_lock_);
uint64_t cache_val = decision_cache_->get(identifier);
if (cache_val == 0) return result;
if (CHECKBW_RESPONSE_VALID(result)) {
uint64_t diff_time = GetCurrentUptime();
// Decision is stored in upper 8 bits, timestamp in remaining 56.
result = (santa_action_t)(cache_val >> 56);
decision_time = (cache_val & ~(0xFF00000000000000));
if (result == ACTION_RESPOND_CHECKBW_ALLOW) {
if ((kMaxAllowCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxAllowCacheTimeMilliseconds * 1000);
if (RESPONSE_VALID(result)) {
if (result == ACTION_RESPOND_DENY) {
auto expiry_time = decision_time + (kMaxDenyCacheTimeMilliseconds * 1000);
if (expiry_time < GetCurrentUptime()) {
decision_cache_->remove(identifier);
return ACTION_UNSET;
}
} else if (result == ACTION_RESPOND_CHECKBW_DENY) {
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
}
}
if (decision_time < diff_time) {
lck_rw_lock_exclusive(cached_decisions_lock_);
cached_decisions_->removeObject(identifier);
lck_rw_unlock_exclusive(cached_decisions_lock_);
return ACTION_UNSET;
}
}
return result;
}
# pragma mark Queue Management
santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
auto return_action = ACTION_UNSET;
bool SantaDecisionManager::PostToQueue(santa_message_t message) {
lck_mtx_lock(dataqueue_lock_);
bool kr = false;
if (dataqueue_) {
kr = dataqueue_->enqueue(&message, sizeof(message));
}
lck_mtx_unlock(dataqueue_lock_);
return kr;
}
#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
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t credential,
const vfs_context_t vfs_context,
const vnode_t vnode) {
santa_action_t return_action = ACTION_UNSET;
// Wait for the daemon to respond or die.
do {
// Add pending request to cache, to be replaced by daemon with actual response
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
// Fetch Vnode ID & string
uint64_t vnode_id = GetVnodeIDForVnode(vfs_context, vnode);
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Check to see if item is in cache
return_action = GetFromCache(vnode_id_str);
// If item wasn't in cache, fetch decision from daemon.
if (!CHECKBW_RESPONSE_VALID(return_action)) {
// Add pending request to cache
AddToCache(vnode_id_str, ACTION_REQUEST_CHECKBW, 0);
// Get path
char path[MAX_PATH_LEN];
int name_len = MAX_PATH_LEN;
if (vn_getpath(vnode, path, &name_len) != 0) {
path[0] = '\0';
}
if (!ClientConnected()) {
LOGI("Execution request without daemon running: %s", path);
AddToCache(vnode_id_str,
ACTION_RESPOND_CHECKBW_ALLOW,
GetCurrentUptime());
return ACTION_RESPOND_CHECKBW_ALLOW;
}
// Prepare to send message to daemon
santa_message_t message;
strncpy(message.path, path, MAX_PATH_LEN);
message.userId = kauth_cred_getuid(credential);
message.pid = proc_selfpid();
message.ppid = proc_selfppid();
message.action = ACTION_REQUEST_CHECKBW;
message.vnode_id = vnode_id;
// Wait for the daemon to respond or die.
do {
// Send request to daemon...
if (!PostToQueue(message)) {
OSIncrementAtomic(&failed_queue_requests_);
if (failed_queue_requests_ > kMaxQueueFailures) {
LOGE("Failed to queue more than %d requests, killing daemon", kMaxQueueFailures);
proc_signal(owning_pid_, SIGKILL);
}
LOGE("Failed to queue request for %s.", path);
CacheCheck(vnode_id_str);
return ACTION_ERROR;
}
// ... and wait for it to respond. If after kRequestLoopSleepMilliseconds
// * kMaxRequestLoops it still hasn't responded, send request again.
for (int i = 0; i < kMaxRequestLoops; ++i) {
IOSleep(kRequestLoopSleepMilliseconds);
return_action = GetFromCache(vnode_id_str);
if (CHECKBW_RESPONSE_VALID(return_action)) break;
}
} while (!CHECKBW_RESPONSE_VALID(return_action) &&
proc_exiting(owning_proc_) == 0);
// If response is still not valid, the daemon exited
if (!CHECKBW_RESPONSE_VALID(return_action)) {
LOGE("Daemon process did not respond correctly. Allowing executions "
"until it comes back.");
CacheCheck(vnode_id_str);
// Send request to daemon...
if (!PostToDecisionQueue(message)) {
LOGE("Failed to queue request for %s.", message->path);
RemoveFromCache(identifier);
return ACTION_ERROR;
}
do {
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());
// If response is still not valid, the daemon exited
if (!RESPONSE_VALID(return_action)) {
LOGE("Daemon process did not respond correctly. Allowing executions "
"until it comes back. Executable path: %s", message->path);
RemoveFromCache(identifier);
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;
}
# pragma mark Misc
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id) {
while (true) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
uint64_t SantaDecisionManager::GetVnodeIDForVnode(const vfs_context_t context,
const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, context);
return vap.va_fileid;
// Check to see if item is in cache
auto return_action = GetFromCache(vnode_id);
// If item was in cache with a valid response, return it.
// If item is in cache but hasn't received a response yet, sleep for a bit.
// If item is not in cache, break out of loop to send request to daemon.
if (RESPONSE_VALID(return_action)) {
return return_action;
} else if (return_action == ACTION_REQUEST_BINARY) {
msleep((void *)vnode_id, NULL, 0, "", &ts_);
} else {
break;
}
}
// Get path
char path[MAXPATHLEN];
int name_len = MAXPATHLEN;
if (vn_getpath(vp, path, &name_len) != 0) {
path[0] = '\0';
}
auto message = NewMessage(cred);
strlcpy(message->path, path, sizeof(message->path));
message->action = ACTION_REQUEST_BINARY;
message->vnode_id = vnode_id;
proc_name(message->ppid, message->pname, sizeof(message->pname));
auto return_action = GetFromDaemon(message, vnode_id);
delete message;
return return_action;
}
uint64_t SantaDecisionManager::GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
clock_get_system_microtime(&sec, &usec);
return (uint64_t)((sec * 1000000) + usec);
#pragma mark Misc
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;
}
# pragma mark Invocation Tracking & PID comparison
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 (failed_log_queue_requests_++ == 0) {
LOGW("Dropping log queue messages");
}
// If enqueue failed, pop an item off the queue and try again.
uint32_t dataSize = 0;
log_dataqueue_->dequeue(0, &dataSize);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
} else {
if (failed_log_queue_requests_ > 0) {
failed_log_queue_requests_--;
}
}
lck_mtx_unlock(log_dataqueue_lock_);
return kr;
}
#pragma mark Invocation Tracking & PID comparison
void SantaDecisionManager::IncrementListenerInvocations() {
OSIncrementAtomic(&listener_invocations_);
@@ -346,81 +381,185 @@ void SantaDecisionManager::DecrementListenerInvocations() {
OSDecrementAtomic(&listener_invocations_);
}
#pragma mark Callbacks
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
const vfs_context_t ctx,
const vnode_t vp,
int *errno) {
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
// Fetch decision
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
// closed.
if (vnode_hasdirtyblks(vp)) {
RemoveFromCache(vnode_id);
returnedAction = ACTION_RESPOND_DENY;
}
switch (returnedAction) {
case ACTION_RESPOND_ALLOW: {
auto proc = vfs_context_proc(ctx);
if (proc) {
pid_t pid = proc_pid(proc);
pid_t ppid = proc_ppid(proc);
// pid_t is 32-bit; pid is in upper 32 bits, ppid in lower.
uint64_t val = ((uint64_t)pid << 32) | (ppid & 0xFFFFFFFF);
vnode_pid_map_->set(vnode_id, val);
}
return KAUTH_RESULT_ALLOW;
}
case ACTION_RESPOND_DENY:
*errno = EPERM;
return KAUTH_RESULT_DENY;
default:
// NOTE: Any unknown response or error condition causes us to fail open.
// Whilst from a security perspective this is bad, it's important that
// we don't break user's machines.
return KAUTH_RESULT_DEFER;
}
}
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) {
RemoveFromCache(vnode_id);
} else if (action == KAUTH_FILEOP_EXEC) {
auto message = NewMessage(nullptr);
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
uint64_t val = vnode_pid_map_->get(vnode_id);
if (val) {
// pid_t is 32-bit, so pid is in upper 32 bits, ppid in lower.
message->pid = (val >> 32);
message->ppid = (val & ~0xFFFFFFFF00000000);
}
PostToLogQueue(message);
delete message;
return;
}
}
// Filter out modifications to locations that are definitely
// not useful or made by santad.
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));
proc_name(message->pid, message->pname, sizeof(message->pname));
switch (action) {
case KAUTH_FILEOP_CLOSE:
message->action = ACTION_NOTIFY_WRITE;
break;
case KAUTH_FILEOP_RENAME:
message->action = ACTION_NOTIFY_RENAME;
break;
case KAUTH_FILEOP_LINK:
message->action = ACTION_NOTIFY_LINK;
break;
case KAUTH_FILEOP_EXCHANGE:
message->action = ACTION_NOTIFY_EXCHANGE;
break;
case KAUTH_FILEOP_DELETE:
message->action = ACTION_NOTIFY_DELETE;
break;
default:
delete message;
return;
}
PostToLogQueue(message);
delete message;
}
}
#undef super
#pragma mark Kauth Callback
extern "C" int fileop_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
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;
switch (action) {
case KAUTH_FILEOP_DELETE:
case KAUTH_FILEOP_EXEC:
vp = reinterpret_cast<vnode_t>(arg0);
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
path = reinterpret_cast<char *>(arg1);
break;
case KAUTH_FILEOP_RENAME:
case KAUTH_FILEOP_EXCHANGE:
case KAUTH_FILEOP_LINK:
path = reinterpret_cast<char *>(arg0);
new_path = reinterpret_cast<char *>(arg1);
break;
default:
return KAUTH_RESULT_DEFER;
}
sdm->IncrementListenerInvocations();
sdm->FileOpCallback(action, vp, path, new_path);
sdm->DecrementListenerInvocations();
return KAUTH_RESULT_DEFER;
}
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) {
// The default action is to defer
int returnResult = KAUTH_RESULT_DEFER;
// Cast arguments to correct types
if (idata == NULL) {
LOGE("Vnode callback established without valid decision manager.");
return returnResult;
}
SantaDecisionManager *sdm = OSDynamicCast(
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
vfs_context_t vfs_context = reinterpret_cast<vfs_context_t>(arg0);
vnode_t vnode = reinterpret_cast<vnode_t>(arg1);
// Only operate on regular files (not directories, symlinks, etc.)
vtype vt = vnode_vtype(vnode);
if (vt != VREG) return returnResult;
// Don't operate on ACCESS events, as they're advisory
if (action & KAUTH_VNODE_ACCESS) return returnResult;
// Filter for only writes
if (action & KAUTH_VNODE_WRITE_DATA ||
action & KAUTH_VNODE_APPEND_DATA ||
action & KAUTH_VNODE_DELETE) {
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
sdm->GetVnodeIDForVnode(vfs_context, vnode));
// If an execution request is pending, deny write
if (sdm->GetFromCache(vnode_id_str) == ACTION_REQUEST_CHECKBW) {
LOGD("Denying write due to pending execution: %s", vnode_id_str);
*(reinterpret_cast<int *>(arg3)) = EACCES;
return KAUTH_RESULT_DENY;
}
// Otherwise remove from cache
sdm->CacheCheck(vnode_id_str);
return returnResult;
if (unlikely(sdm == nullptr)) {
LOGE("vnode_scope_callback called with no decision manager");
return KAUTH_RESULT_DEFER;
}
// Filter for only EXECUTE actions
if (action & KAUTH_VNODE_EXECUTE) {
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();
// Fetch decision
santa_action_t returnedAction = sdm->FetchDecision(
credential, vfs_context, vnode);
switch (returnedAction) {
case ACTION_RESPOND_CHECKBW_ALLOW:
returnResult = KAUTH_RESULT_ALLOW;
break;
case ACTION_RESPOND_CHECKBW_DENY:
*(reinterpret_cast<int *>(arg3)) = EACCES;
returnResult = KAUTH_RESULT_DENY;
break;
default:
// NOTE: Any unknown response or error condition causes us to fail open.
// Whilst from a security perspective this is bad, it's important that
// we don't break user's machines.
break;
}
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
vp,
reinterpret_cast<int *>(arg3));
sdm->DecrementListenerInvocations();
return result;
} else if (action & KAUTH_VNODE_WRITE_DATA) {
sdm->IncrementListenerInvocations();
char path[MAXPATHLEN];
int pathlen = MAXPATHLEN;
vn_getpath(vp, path, &pathlen);
sdm->FileOpCallback(KAUTH_FILEOP_CLOSE, vp, path, nullptr);
sdm->DecrementListenerInvocations();
return returnResult;
}
return returnResult;
return KAUTH_RESULT_DEFER;
}

View File

@@ -15,51 +15,19 @@
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
#include <IOKit/IODataQueueShared.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOMemoryDescriptor.h>
#include <IOKit/IOSharedDataQueue.h>
#include <libkern/c++/OSDictionary.h>
#include <sys/kauth.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "SantaMessage.h"
#include "SantaCache.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
///
/// The maximum number of milliseconds a cached deny message should be
/// considered valid.
///
const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
///
/// The maximum number of milliseconds a cached allow message should be
/// considered valid.
///
const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
///
/// While waiting for a response from the daemon, this is the number of
/// milliseconds to sleep for before checking the cache for a response.
///
const int kRequestLoopSleepMilliseconds = 10;
///
/// While waiting for a response from the daemon, this is the maximum number
/// of loops to wait before sending the request again.
///
const int kMaxRequestLoops = 50;
///
/// Maximum number of entries in the in-kernel cache.
///
const int kMaxCacheSize = 10000;
///
/// Maximum number of PostToQueue failures to allow.
///
const int kMaxQueueFailures = 10;
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
/// and responding to the request appropriately.
@@ -71,104 +39,274 @@ class SantaDecisionManager : public OSObject {
OSDeclareDefaultStructors(SantaDecisionManager);
public:
/// Used for initialization after instantiation. Required because
/// constructors cannot throw inside kernel-space.
bool init();
/// Used for initialization after instantiation.
bool init() override;
/// Called automatically when retain count drops to 0.
void free();
/// Called automatically when retain count drops to 0.
void free() override;
/// Called by SantaDriverClient when a client connects, providing the data
/// queue used to pass messages and the pid of the client process.
void ConnectClient(IOSharedDataQueue *queue, pid_t pid);
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the decision queue.
*/
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
/// Called by SantaDriverClient when a client disconnects
void DisconnectClient();
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the logging queue.
*/
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
/// Returns whether a client is currently connected or not.
bool ClientConnected();
/**
Called by SantaDriverClient when a client connects to the decision queue,
providing the pid of the client process.
*/
void ConnectClient(pid_t pid);
/// Starts the kauth listener.
/// Called by SantaDriverClient when a client disconnects
void DisconnectClient(bool itDied = false);
/// Returns whether a client is currently connected or not.
bool ClientConnected() const;
/// Sets the Mach port for notifying the decision queue.
void SetDecisionPort(mach_port_t port);
/// Sets the Mach port for notifying the log queue.
void SetLogPort(mach_port_t port);
/// Starts the kauth listeners.
kern_return_t StartListener();
/// Stops the kauth listener. After stopping new callback requests,
/// waits until all current invocations have finished before clearing the
/// cache and returning.
/**
Stops the kauth listeners. After stopping new callback requests, waits until all
current invocations have finished before clearing the cache and returning.
*/
kern_return_t StopListener();
/// Adds a decision to the cache, with a timestamp.
void AddToCache(const char *identifier,
/// Adds a decision to the cache, with a timestamp.
void AddToCache(uint64_t identifier,
const santa_action_t decision,
const uint64_t microsecs);
const uint64_t microsecs = GetCurrentUptime());
/// Checks to see if a given identifier is in the cache and removes it.
void CacheCheck(const char *identifier);
/// Fetches a response from the cache, first checking to see if the entry has expired.
santa_action_t GetFromCache(uint64_t identifier);
/// Returns the number of entries in the cache.
uint64_t CacheCount();
/// Checks to see if a given identifier is in the cache and removes it.
void RemoveFromCache(uint64_t identifier);
/// Clears the cache.
/// Returns the number of entries in the cache.
uint64_t CacheCount() const;
/// Clears the cache.
void ClearCache();
/// Fetches a response from the cache, first checking to see if the
/// entry has expired.
santa_action_t GetFromCache(const char *identifier);
/// Posts the requested message to the client data queue, if there is one.
/// Uses dataqueue_lock_ to ensure two threads don't try to write to the
/// queue at the same time.
bool PostToQueue(santa_message_t);
/// Fetches an execution decision for a file, first using the cache and then
/// by sending a message to the daemon and waiting until a response arrives.
/// If a daemon isn't connected, will allow execution and cache, logging
/// the path to the executed file.
santa_action_t FetchDecision(const kauth_cred_t credential,
const vfs_context_t vfs_context,
const vnode_t vnode);
/// Fetches the vnode_id for a given vnode.
uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp);
/// Returns the current system uptime in microseconds
uint64_t GetCurrentUptime();
/// Increments the count of active vnode callback's pending.
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
/// Decrements the count of active vnode callback's pending.
/// Decrements the count of active callbacks pending.
void DecrementListenerInvocations();
/**
Vnode Callback
@param cred The kauth credential for this request.
@param ctx The VFS context for this request.
@param vp The Vnode for this request.
@param errno A pointer to return an errno style error.
@return int A valid KAUTH_RESULT_*.
*/
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
const vnode_t vp, int *errno);
/**
FileOp Callback
@param action The performed action
@param vp The Vnode for this request. May be nullptr.
@param path The path being operated on.
@param new_path The target path for moves and links.
*/
void FileOpCallback(kauth_action_t action, const vnode_t vp,
const char *path, const char *new_path);
protected:
/**
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 = 1000;
/// The maximum number of milliseconds a cached deny message should be considered valid.
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
/// Maximum number of entries in the in-kernel cache.
static const uint32_t kMaxCacheSize = 10000;
/// Maximum number of PostToDecisionQueue failures to allow.
static const uint32_t kMaxDecisionQueueFailures = 10;
/// The maximum number of messages can be kept in the decision data queue at any time.
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 = 2048;
/**
Fetches a response from the daemon. Handles both daemon death
and failure to post messages to the daemon.
@param message The message to send to the daemon
@param identifier The vnode ID string for this request
@return santa_action_t The response for this request
*/
santa_action_t GetFromDaemon(santa_message_t *message, uint64_t identifier);
/**
Fetches an execution decision for a file, first using the cache and then
by sending a message to the daemon and waiting until a response arrives.
If a daemon isn't connected, will allow execution and cache, logging
the path to the executed file.
@param cred The credential for this request.
@param vp The Vnode for this request.
@param vnode_id The ID for this vnode.
@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);
/**
Posts the requested message to the decision data queue.
@param message The message to send
@return bool true if sending was successful.
*/
bool PostToDecisionQueue(santa_message_t *message);
/**
Posts the requested message to the logging data queue.
@param message The message to send
@return bool true if sending was successful.
*/
bool PostToLogQueue(santa_message_t *message);
/**
Fetches the vnode_id for a given vnode.
@param ctx The VFS context to use.
@param vp The Vnode to get the ID for
@return uint64_t The Vnode ID as a 64-bit unsigned int.
*/
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
VATTR_WANTED(&vap, va_fileid);
vnode_getattr(vp, &vap, ctx);
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
}
/**
Creates a new santa_message_t with some fields pre-filled.
@param credential The kauth_cred_t for this action, if available.
If nullptr, will get the credential for the current process.
*/
static inline santa_message_t *NewMessage(kauth_cred_t credential) {
bool should_release = false;
if (credential == nullptr) {
credential = kauth_cred_get_with_ref();
should_release = true;
}
auto message = new santa_message_t;
message->uid = kauth_cred_getuid(credential);
message->gid = kauth_cred_getgid(credential);
message->pid = proc_selfpid();
message->ppid = proc_selfppid();
if (should_release) {
kauth_cred_unref(&credential);
}
return message;
}
/**
Returns the current system uptime in microseconds
*/
static inline uint64_t GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
clock_get_system_microtime(&sec, &usec);
return (uint64_t)((sec * 1000000) + usec);
}
private:
SantaCache<uint64_t> *decision_cache_;
SantaCache<uint64_t> *vnode_pid_map_;
lck_grp_t *sdm_lock_grp_;
lck_rw_t *cached_decisions_lock_;
lck_mtx_t *dataqueue_lock_;
lck_grp_attr_t *sdm_lock_grp_attr_;
lck_attr_t *sdm_lock_attr_;
OSDictionary *cached_decisions_;
lck_mtx_t *decision_dataqueue_lock_;
lck_mtx_t *log_dataqueue_lock_;
IOSharedDataQueue *dataqueue_;
SInt32 failed_queue_requests_;
IOSharedDataQueue *decision_dataqueue_;
IOSharedDataQueue *log_dataqueue_;
uint32_t failed_decision_queue_requests_;
uint32_t failed_log_queue_requests_;
SInt32 listener_invocations_;
int32_t listener_invocations_;
pid_t owning_pid_;
proc_t owning_proc_;
pid_t client_pid_;
kauth_listener_t vnode_listener_;
kauth_listener_t fileop_listener_;
struct timespec ts_;
};
///
/// The kauth callback function for the Vnode scope
/// @param actor's credentials
/// @param data that was passed when the listener was registered
/// @param action that was requested
/// @param VFS context
/// @param Vnode being operated on
/// @param Parent Vnode. May be NULL.
/// @param Pointer to an errno-style error.
///
/**
The kauth callback function for the Vnode scope
@param actor's credentials
@param data that was passed when the listener was registered
@param action that was requested
@param VFS context
@param Vnode being operated on
@param Parent Vnode. May be nullptr.
@param Pointer to an errno-style error.
*/
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);
kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3);
/**
The kauth callback function for the FileOp scope
@param actor's credentials
@param data that was passed when the listener was registered
@param action that was requested
@param depends on action, usually the vnode ref.
@param depends on action.
@param depends on action, usually 0.
@param depends on action, usually 0.
*/
extern "C" int fileop_scope_callback(
kauth_cred_t credential,
void *idata,
kauth_action_t action,
uintptr_t arg0,
uintptr_t arg1,
uintptr_t arg2,
uintptr_t arg3);
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H

View File

@@ -24,8 +24,12 @@ bool SantaDriver::start(IOService *provider) {
if (!super::start(provider)) return false;
santaDecisionManager = new SantaDecisionManager;
santaDecisionManager->init();
santaDecisionManager->StartListener();
if (!santaDecisionManager->init() ||
santaDecisionManager->StartListener() != kIOReturnSuccess) {
santaDecisionManager->release();
santaDecisionManager = nullptr;
return false;
}
registerService();
@@ -37,14 +41,14 @@ bool SantaDriver::start(IOService *provider) {
void SantaDriver::stop(IOService *provider) {
santaDecisionManager->StopListener();
santaDecisionManager->release();
santaDecisionManager = NULL;
santaDecisionManager = nullptr;
LOGI("Unloaded.");
super::stop(provider);
}
SantaDecisionManager* SantaDriver::GetDecisionManager() {
SantaDecisionManager *SantaDriver::GetDecisionManager() const {
return santaDecisionManager;
}

View File

@@ -18,8 +18,8 @@
#include <IOKit/IOService.h>
#include <libkern/OSKextLib.h>
#include "SantaDecisionManager.h"
#include "SNTLogging.h"
#include "SantaDecisionManager.h"
///
/// The driver class, which provides the start/stop functions and holds
@@ -31,17 +31,16 @@ class com_google_SantaDriver : public IOService {
public:
/// Called by the kernel when the kext is loaded
bool start(IOService *provider);
bool start(IOService *provider) override;
/// Called by the kernel when the kext is unloaded
void stop(IOService *provider);
void stop(IOService *provider) override;
/// Returns a pointer to the SantaDecisionManager created in start().
SantaDecisionManager* GetDecisionManager();
SantaDecisionManager *GetDecisionManager() const;
private:
SantaDecisionManager *santaDecisionManager;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H

View File

@@ -25,7 +25,7 @@ OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
bool SantaDriverClient::initWithTask(
task_t owningTask, void *securityID, UInt32 type) {
if (clientHasPrivilege(
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
LOGW("Unprivileged client attempted to connect.");
return false;
}
@@ -36,174 +36,148 @@ bool SantaDriverClient::initWithTask(
}
bool SantaDriverClient::start(IOService *provider) {
fProvider = OSDynamicCast(com_google_SantaDriver, provider);
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
if (!fProvider) return false;
if (!myProvider) return false;
if (!super::start(provider)) return false;
fSDM = fProvider->GetDecisionManager();
if (!fSDM) return false;
decisionManager = myProvider->GetDecisionManager();
if (!decisionManager) return false;
return true;
}
void SantaDriverClient::stop(IOService *provider) {
super::stop(provider);
fProvider = NULL;
myProvider = nullptr;
decisionManager = nullptr;
}
IOReturn SantaDriverClient::clientClose() {
terminate(kIOServiceSynchronous);
return kIOReturnSuccess;
decisionManager->DisconnectClient(true);
return terminate(kIOServiceSynchronous) ? kIOReturnSuccess : kIOReturnError;
}
bool SantaDriverClient::terminate(IOOptionBits options) {
fSDM->DisconnectClient();
decisionManager->DisconnectClient();
LOGI("Client disconnected.");
if (fSharedMemory) {
fSharedMemory->release();
fSharedMemory = NULL;
}
if (fDataQueue) {
fDataQueue->release();
fDataQueue = NULL;
}
if (fProvider && fProvider->isOpen(this)) fProvider->close(this);
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
return super::terminate(options);
}
#pragma mark Fetching memory and data queue notifications
IOReturn SantaDriverClient::registerNotificationPort(mach_port_t port,
UInt32 type,
UInt32 ref) {
if ((!fDataQueue) || (port == MACH_PORT_NULL)) return kIOReturnError;
IOReturn SantaDriverClient::registerNotificationPort(
mach_port_t port, UInt32 type, UInt32 ref) {
if (port == MACH_PORT_NULL) return kIOReturnError;
fDataQueue->setNotificationPort(port);
switch (type) {
case QUEUETYPE_DECISION:
decisionManager->SetDecisionPort(port);
break;
case QUEUETYPE_LOG:
decisionManager->SetLogPort(port);
break;
default:
return kIOReturnBadArgument;
}
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clientMemoryForType(UInt32 type,
IOOptionBits *options,
IOMemoryDescriptor **memory) {
*memory = NULL;
*options = 0;
if (type == kIODefaultMemoryType) {
if (!fSharedMemory) return kIOReturnNoMemory;
fSharedMemory->retain(); // client will decrement this ref
*memory = fSharedMemory;
fSDM->ConnectClient(fDataQueue, proc_selfpid());
LOGI("Client connected, PID: %d.", proc_selfpid());
return kIOReturnSuccess;
IOReturn SantaDriverClient::clientMemoryForType(
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) {
switch (type) {
case QUEUETYPE_DECISION:
*options = 0;
*memory = decisionManager->GetDecisionMemoryDescriptor();
decisionManager->ConnectClient(proc_selfpid());
break;
case QUEUETYPE_LOG:
*options = 0;
*memory = decisionManager->GetLogMemoryDescriptor();
break;
default:
return kIOReturnBadArgument;
}
return kIOReturnNoMemory;
(*memory)->retain();
return kIOReturnSuccess;
}
#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 (!fProvider->open(this)) {
if (me->isInactive()) return kIOReturnNotAttached;
if (!me->myProvider->open(me)) {
LOGW("A second client tried to connect.");
return kIOReturnExclusiveAccess;
}
fDataQueue = IOSharedDataQueue::withCapacity((sizeof(santa_message_t) +
DATA_QUEUE_ENTRY_HEADER_SIZE)
* kMaxQueueEvents);
if (!fDataQueue) return kIOReturnNoMemory;
fSharedMemory = fDataQueue->getMemoryDescriptor();
if (!fSharedMemory) {
fDataQueue->release();
fDataQueue = NULL;
return kIOReturnVMError;
}
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;
IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) {
char vnode_id_str[21];
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
fSDM->AddToCache(vnode_id_str,
ACTION_RESPOND_CHECKBW_ALLOW,
fSDM->GetCurrentUptime());
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_allow_binary(
SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->allow_binary(
*(static_cast<const uint64_t *>(arguments->scalarInput)));
}
IOReturn SantaDriverClient::deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
char vnode_id_str[21];
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
fSDM->AddToCache(vnode_id_str,
ACTION_RESPOND_CHECKBW_DENY,
fSDM->GetCurrentUptime());
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
me->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;
return target->deny_binary(
*(static_cast<const uint64_t *>(arguments->scalarInput)));
}
IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
me->decisionManager->ClearCache();
IOReturn SantaDriverClient::clear_cache() {
fSDM->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 = fSDM->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);
return kIOReturnSuccess;
}
#pragma mark Method Resolution
@@ -216,60 +190,23 @@ IOReturn SantaDriverClient::externalMethod(
void *reference) {
/// Array of methods callable by clients. The order of these must match the
/// order of the items in SantaDriverMethods in SNTKernelCommon.h
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
}
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
// 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

@@ -16,20 +16,13 @@
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
#include <IOKit/IOUserClient.h>
#include <IOKit/IOSharedDataQueue.h>
#include <IOKit/IOLib.h>
#include <IOKit/IODataQueueShared.h>
#include <sys/kauth.h>
#include <sys/vnode.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include "SNTKernelCommon.h"
#include "SantaDecisionManager.h"
#include "SantaDriver.h"
#include "SantaMessage.h"
#include "SNTKernelCommon.h"
// The maximum number of messages can be kept in the IODataQueue at any time.
const int kMaxQueueEvents = 256;
///
/// This class is instantiated by IOKit when a new client process attempts to
@@ -39,33 +32,34 @@ const int kMaxQueueEvents = 256;
/// Documentation on how the IOUserClient parts of this code work can be found
/// here:
/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
/// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/WritingDeviceDriver.pdf
///
class com_google_SantaDriverClient : public IOUserClient {
OSDeclareDefaultStructors(com_google_SantaDriverClient);
public:
/// Called as part of IOServiceOpen in clients
bool initWithTask(task_t owningTask, void *securityID, UInt32 type);
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
/// Called after initWithTask as part of IOServiceOpen
bool start(IOService *provider);
bool start(IOService *provider) override;
/// Called when this class is stopping
void stop(IOService *provider);
void stop(IOService *provider) override;
/// Called when a client disconnects
IOReturn clientClose();
IOReturn clientClose() override;
/// Called when the driver is shutting down
bool terminate(IOOptionBits options);
bool terminate(IOOptionBits options) override;
/// Called in clients with IOConnectSetNotificationPort
IOReturn registerNotificationPort(
mach_port_t port, UInt32 type, UInt32 refCon);
mach_port_t port, UInt32 type, UInt32 refCon) override;
/// Called in clients with IOConnectMapMemory
IOReturn clientMemoryForType(
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory);
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
/// to the requested selector using the SantaDriverMethods enum in
@@ -74,54 +68,41 @@ class com_google_SantaDriverClient : public IOUserClient {
UInt32 selector,
IOExternalMethodArguments *arguments,
IOExternalMethodDispatch *dispatch,
OSObject *target, void *reference);
OSObject *target, void *reference) override;
///
/// 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);
/// Called during client connection.
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.
static IOReturn check_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
private:
IOSharedDataQueue *fDataQueue;
IOMemoryDescriptor *fSharedMemory;
com_google_SantaDriver *fProvider;
SantaDecisionManager *fSDM;
com_google_SantaDriver *myProvider;
SantaDecisionManager *decisionManager;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H

View File

@@ -1,44 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#ifndef SANTA__SANTA_DRIVER__SANTAMESSAGE_H
#define SANTA__SANTA_DRIVER__SANTAMESSAGE_H
#include <libkern/c++/OSObject.h>
#include "SNTKernelCommon.h"
///
/// An OSObject wrapper around a @c santa_action_t and a time.
/// Only OSObject subclasses can be inserted into an OSDictionary.
///
class SantaMessage : public OSObject {
OSDeclareDefaultStructors(SantaMessage)
public:
// Returns the time the action was last set.
uint64_t getMicrosecs() const;
// Returns the set action.
santa_action_t getAction() const;
// Sets the acion and receive time.
void setAction(const santa_action_t action, const uint64_t microsecs);
private:
santa_action_t action_;
uint64_t microsecs_;
};
#endif // SANTA__SANTA_DRIVER__SANTAMESSAGE_H

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,313 @@
/// 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 "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTBundleService ()
@property SNTXPCConnection *notifierConnection;
@property SNTXPCConnection *listener;
@end
@implementation SNTBundleService
#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(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self createConnection];
});
}
- (void)hashBundleBinariesForEvent:(SNTStoredEvent *)event
reply:(SNTBundleHashBlock)reply {
NSProgress *progress =
[NSProgress currentProgress] ? [NSProgress progressWithTotalUnitCount:1] : nil;
NSDate *startTime = [NSDate date];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// 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;
NSArray *relatedBinaries = [self findRelatedBinaries:event progress:progress];
NSString *bundleHash = [self calculateBundleHashFromEvents:relatedBinaries];
NSNumber *ms = [NSNumber numberWithDouble:[startTime timeIntervalSinceNow] * -1000.0];
if (bundleHash) LOGD(@"hashed %@ in %@ ms", event.fileBundlePath, ms);
reply(bundleHash, relatedBinaries, 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(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 600 * NSEC_PER_SEC))) {
LOGD(@"hashBundleBinariesForEvent timeout");
[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 underneath
@return An array of SNTStoredEvent's
@note The first stage gathers a set of executables. 60 sec / max thread timeout.
@note The second stage hashes the executables. 300 sec / max thread timeout.
*/
- (NSArray *)findRelatedBinaries:(SNTStoredEvent *)event progress:(NSProgress *)progress {
// For storing the generated events, with a simple lock for writing.
NSMutableArray *relatedEvents = [NSMutableArray array];
// For storing files to be hashed
NSMutableSet<SNTFileInfo *> *fis = [NSMutableSet set];
// Limit the number of threads that can process files at once to keep CPU usage down.
dispatch_semaphore_t sema =
dispatch_semaphore_create([[NSProcessInfo processInfo] processorCount] / 2);
// Group the processing into a single group so we can wait on the whole group after each stage.
dispatch_group_t group = dispatch_group_create();
// Directory enumerator
NSDirectoryEnumerator *dirEnum =
[[NSFileManager defaultManager] enumeratorAtPath:event.fileBundlePath];
// Locks for accessing the enumerator and adding file and events between threads.
__block pthread_mutex_t enumeratorMutex = PTHREAD_MUTEX_INITIALIZER;
__block pthread_mutex_t eventsMutex = PTHREAD_MUTEX_INITIALIZER;
// Counts used as additional progress information in SantaGUI
__block uint64_t binaryCount = 0;
__block uint64_t sentBinaryCount = 0;
__block uint64_t fileCount = 0;
__block BOOL breakDir = NO;
// In the first stage iterate over every file in the tree checking if it is a binary. If so add
// it to the fis set for the second stage. Hashing the file while iterating over the filesystem
// causes performance issues. Do them separately.
while (1) {
@autoreleasepool {
if (breakDir || progress.isCancelled) break;
// Wait for a processing thread to become available. At this stage we are only reading the
// mach_header. If all processing threads are blocking for more than 60 sec bail.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 60 * NSEC_PER_SEC))) {
LOGD(@"isExecutable processing threads timeout");
return nil;
}
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
pthread_mutex_lock(&enumeratorMutex);
NSString *file = [dirEnum nextObject];
fileCount++;
pthread_mutex_unlock(&enumeratorMutex);
if (!file) {
breakDir = YES;
dispatch_semaphore_signal(sema);
return;
}
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) {
dispatch_semaphore_signal(sema);
return;
}
NSString *newFile = [event.fileBundlePath stringByAppendingPathComponent:file];
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:newFile];
if (!fi.isExecutable) {
dispatch_semaphore_signal(sema);
return;
}
pthread_mutex_lock(&eventsMutex);
[fis addObject:fi];
binaryCount++;
pthread_mutex_unlock(&eventsMutex);
dispatch_semaphore_signal(sema);
});
if (progress && ((fileCount % 500) == 0 || binaryCount > sentBinaryCount)) {
sentBinaryCount = binaryCount;
[[self.notifierConnection remoteObjectProxy] updateCountsForEvent:event
binaryCount:binaryCount
fileCount:fileCount];
}
}
}
if (progress.isCancelled) return nil;
// Wait for all the processing threads to finish
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSProgress *p;
if (progress) {
[progress becomeCurrentWithPendingUnitCount:1];
p = [NSProgress progressWithTotalUnitCount:fis.count];
}
// In the second stage perform SHA256 hashing on all of the found binaries.
for (SNTFileInfo *fi in fis) {
@autoreleasepool {
if (progress.isCancelled) break;
// Wait for a processing thread to become available. Here we are hashing the entire file.
// If all processing threads are blocking for more than 5 min bail.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC))) {
LOGD(@"SHA256 processing threads timeout");
return nil;
}
dispatch_group_async(group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
@autoreleasepool {
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.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;
pthread_mutex_lock(&eventsMutex);
[relatedEvents addObject:se];
p.completedUnitCount++;
pthread_mutex_unlock(&eventsMutex);
dispatch_semaphore_signal(sema);
}
});
}
}
// Wait for all the processing threads to finish
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
pthread_mutex_destroy(&enumeratorMutex);
pthread_mutex_destroy(&eventsMutex);
return progress.isCancelled ? nil : relatedEvents;
}
- (NSString *)calculateBundleHashFromEvents:(NSArray<SNTStoredEvent *> *)events {
if (!events) return nil;
NSMutableArray *eventSHA256Hashes = [NSMutableArray arrayWithCapacity:events.count];
for (SNTStoredEvent *event in events) {
if (!event.fileSHA256) return nil;
[eventSHA256Hashes addObject:event.fileSHA256];
}
[eventSHA256Hashes sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
NSString *sha256Hashes = [eventSHA256Hashes 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]];
return sha256;
}
@end

View File

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// 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.
@@ -12,20 +12,16 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SantaMessage.h"
@import Foundation;
OSDefineMetaClassAndStructors(SantaMessage, OSObject);
#import "SNTBundleService.h"
#import "SNTXPCBundleServiceInterface.h"
#import "SNTXPCConnection.h"
uint64_t SantaMessage::getMicrosecs() const {
return microsecs_;
}
santa_action_t SantaMessage::getAction() const {
return action_;
}
void SantaMessage::setAction(const santa_action_t action,
const uint64_t microsecs) {
action_ = action;
microsecs_ = microsecs;
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

@@ -0,0 +1,73 @@
/// 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 "SNTCommandController.h"
#import "SNTLogging.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#include <sys/stat.h>
@interface SNTCommandCheckCache : NSObject<SNTCommand>
@end
@implementation SNTCommandCheckCache
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"checkcache")
#endif
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Prints the status of a file in the kernel cache.";
}
+ (NSString *)longHelpText {
return (@"Checks the in-kernel cache for desired file.\n"
@"Returns 0 if successful, 1 otherwise");
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
uint64_t vnodeID = [self vnodeIDForFile:arguments.firstObject];
[[daemonConn remoteObjectProxy] checkCacheForVnodeID:vnodeID withReply:^(santa_action_t action) {
if (action == ACTION_RESPOND_ALLOW) {
LOGI(@"File exists in [whitelist] kernel cache");
exit(0);
} else if (action == ACTION_RESPOND_DENY) {
LOGI(@"File exists in [blacklist] kernel cache");
exit(0);
} else if (action == ACTION_UNSET) {
LOGE(@"File does not exist in cache");
exit(1);
}
}];
}
+ (uint64_t)vnodeIDForFile:(NSString *)path {
struct stat fstat = {};
stat(path.fileSystemRepresentation, &fstat);
return (((uint64_t)fstat.st_dev << 32) | fstat.st_ino);
}
@end

View File

@@ -0,0 +1,631 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#import <MOLCertificate/MOLCertificate.h>
#import <MOLCodesignChecker/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;
}
+ (BOOL)requiresDaemonConn {
return NO;
}
+ (NSString *)shortHelpText {
return @"Prints information about a file.";
}
+ (NSString *)longHelpText {
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 {
if (!arguments.count) [self printErrorUsageAndExit:@"No arguments"];
NSString *key;
NSNumber *certIndex;
NSArray *filePaths;
[self parseArguments:arguments
forKey:&key
certIndex:&certIndex
jsonOutput:&json
filePaths:&filePaths];
// Only access outputHashes from the outputHashesQueue
__block NSMutableArray *outputHashes = [[NSMutableArray alloc] initWithCapacity:filePaths.count];
dispatch_group_t outputHashesGroup = dispatch_group_create();
dispatch_queue_t outputHashesQueue =
dispatch_queue_create("com.google.santa.outputhashes", DISPATCH_QUEUE_SERIAL);
__block NSOperationQueue *hashQueue = [[NSOperationQueue alloc] init];
hashQueue.maxConcurrentOperationCount = 15;
__block NSUInteger hashed = 0;
[filePaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSBlockOperation *hashOperation = [NSBlockOperation blockOperationWithBlock:^{
if (PrettyOutput()) printf("\rCalculating %lu/%lu", ++hashed, filePaths.count);
SNTCommandFileInfo *fi = [[self alloc] initWithFilePath:obj daemonConnection:daemonConn];
if (!fi.fileInfo) return;
__block NSMutableDictionary *outputHash = [[NSMutableDictionary alloc] init];
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];
});
}
}];
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);
}
#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]);
}
+ (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"
@@ -23,7 +25,9 @@
@implementation SNTCommandFlushCache
REGISTER_COMMAND_NAME(@"flushcache");
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"flushcache")
#endif
+ (BOOL)requiresRoot {
return YES;
@@ -44,13 +48,13 @@ REGISTER_COMMAND_NAME(@"flushcache");
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
[[daemonConn remoteObjectProxy] flushCache:^(BOOL success) {
if (success) {
LOGI(@"Cache flush requested");
exit(0);
} else {
LOGE(@"Cache flush failed");
exit(1);
}
if (success) {
LOGI(@"Cache flush requested");
exit(0);
} else {
LOGE(@"Cache flush failed");
exit(1);
}
}];
}

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