Compare commits

...

228 Commits

Author SHA1 Message Date
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
159 changed files with 7910 additions and 4245 deletions

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,11 +1,14 @@
---
language: objective-c
cache: cocoapods
cache:
- bundler
- cocoapods
sudo: false
osx_image: xcode7
before_install:
- gem install activesupport
- gem install cocoapods xcpretty
- pod setup >/dev/null
script:
- 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

View File

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

View File

@@ -18,7 +18,8 @@ sleep 1
sleep 1
# Create hopefully useful symlink for santactl
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
mkdir -p /usr/local/bin
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin/santactl
user=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "$user" ]] && exit 0

View File

@@ -2,7 +2,5 @@
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
? [= Sender kernel] [S= Message santa-driver:] claim
? [= Sender kernel] [S= Message santa-driver:] file /var/log/santa.log
? [= Sender santad] claim
? [= Sender santad] file /var/log/santa.log
? [= Sender santactl] claim
? [= Sender santactl] file /var/log/santa.log
? [= Facility com.google.santa] claim
? [= Facility com.google.santa] file /var/log/santa.log

View File

@@ -7,6 +7,7 @@
<key>ProgramArguments</key>
<array>
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
@@ -19,5 +20,7 @@
<true />
<key>ProcessType</key>
<string>Interactive</string>
<key>ThrottleInterval</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -7,6 +7,7 @@
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
<string>--syslog</string>
</array>
<key>RunAtLoad</key>
<true/>

View File

@@ -36,6 +36,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Copy new files.
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
mkdir -p /usr/local/bin
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons

26
Conf/uninstall.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# Uninstalls Santa from the boot volume, clearing up everything but logs/configs.
# Unloads the kernel extension, services, and deletes component files.
# If a user is logged in, also unloads the GUI agent.
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
/bin/launchctl remove com.google.santad
sleep 1
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
user=$(/usr/bin/stat -f '%u' /dev/console)
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
# and to clean out the log config, although it won't write after wiping the binary
/usr/bin/killall -HUP syslogd
# delete artifacts on-disk
/bin/rm -rf /Applications/Santa.app
/bin/rm -rf /Library/Extensions/santa-driver.kext
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /usr/local/bin/santactl # just a symlink
#uncomment to remove the config file and all databases, log files
#/bin/rm -rf /var/db/santa
#/bin/rm -f /var/log/santa*
exit 0

43
Podfile
View File

@@ -7,34 +7,43 @@ target :Santa do
pod 'MOLCodesignChecker'
end
target :santactl do
target :santad do
pod 'FMDB'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'FMDB'
end
target :santad do
target :santactl do
pod 'FMDB'
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'FMDB'
post_install do |installer|
installer.pods_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"
end
end
end
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

View File

@@ -1,22 +1,32 @@
PODS:
- FMDB (2.6):
- FMDB/standard (= 2.6)
- FMDB/standard (2.6)
- MOLCertificate (1.3)
- MOLCodesignChecker (1.4):
- 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)
- OCMock (3.2.2)
- MOLFCMClient (1.3):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.4)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient (~> 1.3)
- OCMock
SPEC CHECKSUMS:
FMDB: c1968bab3ab0aed38f66cb778ae1e7fa9a652b6e
MOLCertificate: a776221906b5a46dd1bd749d0682bef3ee68c1f5
MOLCodesignChecker: 34e60cc6beadabfb4762b6e5087e12837774f85f
OCMock: 18c9b7e67d4c2770e95bb77a9cc1ae0c91fe3835
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: 5a5e31eb73248c3e92c79b9a285f031194e8404c
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: 13d8b42db9d750e772f09cc38fc453922fece09f
OCMock: 35ae71d6a8fcc1b59434d561d1520b9dd4f15765
COCOAPODS: 0.39.0
PODFILE CHECKSUM: 1728b86e95e7cdb481dd91ab9c38af38e060a023
COCOAPODS: 1.2.0

102
README.md
View File

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

View File

@@ -1,11 +1,14 @@
require 'openssl'
WORKSPACE = 'Santa.xcworkspace'
DEFAULT_SCHEME = 'All'
OUTPUT_PATH = 'Build'
DIST_PATH = 'Dist'
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
@@ -45,6 +48,13 @@ task :init do
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
$DISABLE_XCPRETTY = true
end
cert_pem = `security find-certificate -p -c '#{DEVTEAM_CERT_CN}'`
cert = OpenSSL::X509::Certificate.new cert_pem
team_id = cert.subject.to_a.find {|f| f[0] == "OU"}[1]
File.open(DEVTEAM_FILE, 'w') { |f|
f.puts("// This file is auto-generated. Do not edit manually")
f.puts("DEVELOPMENT_TEAM = #{team_id}")
}
end
task :remove_existing do
@@ -56,7 +66,7 @@ desc "Clean"
task :clean => :init do
puts "Cleaning"
FileUtils.rm_rf(OUTPUT_PATH)
FileUtils.rm_rf(DIST_PATH)
xcodebuild("-scheme All clean")
end
# Build
@@ -111,24 +121,26 @@ task :dist do
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("#{xcodebuilddir}/Release/#{x}", "#{DIST_PATH}/binaries")
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/binaries")
end
DSYMS.each do |x|
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{DIST_PATH}/dsym")
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/dsym")
end
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{DIST_PATH}/conf")}
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{dist_path}/conf")}
puts "Distribution folder created"
puts "Distribution folder #{dist_path} created"
end
# Tests

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 = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,7 +26,9 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES"
enableAddressSanitizer = "YES">
<Testables>
<TestableReference
skipped = "NO">

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

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

View File

@@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8191" systemVersion="15C50" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
<development version="6300" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
@@ -14,64 +15,95 @@
</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="497" height="381"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<rect key="contentRect" x="167" y="107" width="497" height="439"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" id="Iwq-Lx-rLv">
<rect key="frame" x="0.0" y="0.0" width="497" height="381"/>
<rect key="frame" x="0.0" y="0.0" width="497" height="439"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="-6" y="411" width="37" height="32"/>
<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="207" y="311" width="83" height="40"/>
<animations/>
<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="206" y="368" 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="22" y="264" width="454" height="17"/>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
<rect key="frame" x="22" y="329" width="454" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" 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="165" y="192" width="294" height="17"/>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="132" width="1" height="167"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<animations/>
<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"/>
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<font key="titleFont" metaFont="system"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
<rect key="frame" x="8" y="282" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
</userDefinedRuntimeAttributes>
</textFieldCell>
<connections>
<binding destination="-2" name="value" keyPath="self.event.filePath" id="qfp-sR-Nmu"/>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
</dictionary>
</binding>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr">
<rect key="frame" x="165" y="217" width="294" height="17"/>
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="282" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
</constraints>
<animations/>
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
<font key="font" metaFont="systemBold"/>
<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.event.fileBundleName" id="enC-Cl-UWt">
@@ -81,31 +113,68 @@
</binding>
</connections>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="142" width="219" height="17"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
<rect key="frame" x="8" y="257" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<animations/>
<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" 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="232" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" 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 Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="257" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" 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.fileSHA256" id="9KB-0b-qLV"/>
<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="C3G-wL-u7w">
<rect key="frame" x="165" y="167" width="294" height="17"/>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
<rect key="frame" x="8" y="207" width="120" height="17"/>
<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>
</textField>
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="207" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
</constraints>
<animations/>
<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">
@@ -115,93 +184,18 @@
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
<rect key="frame" x="8" y="92" width="120" height="17"/>
<animations/>
<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="eQb-0a-76J">
<rect key="frame" x="8" y="117" width="120" height="17"/>
<animations/>
<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"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
<rect key="frame" x="8" y="167" width="120" height="17"/>
<animations/>
<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="8" y="192" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
</constraints>
<animations/>
<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="8" y="142" width="120" height="17"/>
<animations/>
<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"/>
</textFieldCell>
</textField>
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0">
<rect key="frame" x="165" y="92" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<animations/>
<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" 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="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
<rect key="frame" x="146" y="92" width="1" height="142"/>
<constraints>
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
</constraints>
<animations/>
<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>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs">
<rect key="frame" x="40" y="168" width="15" height="15"/>
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
<rect key="frame" x="40" y="208" width="15" height="15"/>
<constraints>
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
</constraints>
<animations/>
<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"/>
@@ -212,55 +206,54 @@
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
<rect key="frame" x="256" 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="182" width="120" 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 verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
<rect key="frame" x="165" y="182" width="219" height="17"/>
<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>
<animations/>
<buttonCell key="cell" type="roundTextured" title="Dismiss" 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>
<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"/>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
</connections>
</button>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="132" y="33" width="112" height="25"/>
<constraints>
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
</constraints>
<animations/>
<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">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</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 horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o">
<rect key="frame" x="165" y="117" width="294" height="17"/>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
<rect key="frame" x="8" y="157" width="120" height="17"/>
<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="165" y="157" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
</constraints>
<animations/>
<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" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<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="displayPatternValue1" keyPath="self.event.parentName" id="Lce-TO-q9V">
@@ -275,96 +268,163 @@ DQ
</binding>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC">
<rect key="frame" x="8" y="217" width="120" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
</constraints>
<animations/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
<rect key="frame" x="8" y="132" width="120" height="17"/>
<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>
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="132" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
<font key="font" metaFont="system"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
<dictionary key="options">
<string key="NSValueTransformerName">NSIsNil</string>
<string key="NSNullPlaceholder">Unknown</string>
</dictionary>
</binding>
</connections>
</textField>
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
<rect key="frame" x="-6" y="353" width="37" height="32"/>
<animations/>
<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">
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
<rect key="frame" x="132" 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" 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">
DQ
</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
<rect key="frame" x="256" y="33" width="110" height="25"/>
<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="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
<rect key="frame" x="165" y="232" width="294" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="290" 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="91" 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>
</subviews>
<constraints>
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="0AD-PS-5V1"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="centerY" secondItem="qgf-Jf-cJr" secondAttribute="centerY" id="AKX-pe-hEX"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="GT2-tO-2td"/>
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pDa-fA-vnC" secondAttribute="top" id="Gd4-Nr-n5G"/>
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
<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="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" priority="750" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="centerX" secondItem="Iwq-Lx-rLv" secondAttribute="centerX" id="LkH-F4-Ncm"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="30" id="Nsl-zf-poH"/>
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
<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="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" id="asc-Ga-WHD"/>
<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="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
<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="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="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="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
<constraint firstItem="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="top" secondItem="KEB-eH-x2Y" secondAttribute="bottom" constant="8" id="m2z-1O-ifB"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
<constraint firstItem="5D8-GP-a4l" firstAttribute="top" secondItem="h6f-PY-cc0" secondAttribute="bottom" constant="25" id="lYd-VZ-lBs"/>
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="pCX-eX-erN"/>
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="u1y-6V-moc"/>
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
<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>
<animations/>
</view>
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="112.5" y="307.5"/>
<point key="canvasLocation" x="302.5" y="304.5"/>
</window>
</objects>
<resources>

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

@@ -1,4 +1,4 @@
/// Copyright 2015 Google Inc. All rights reserved.
/// 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.
@@ -12,20 +12,28 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SantaCachedDecision.h"
#import "SNTAccessibleTextField.h"
OSDefineMetaClassAndStructors(SantaCachedDecision, OSObject);
@implementation SNTAccessibleTextField
uint64_t SantaCachedDecision::getMicrosecs() const {
return microsecs_;
- (BOOL)accessibilityIsIgnored {
return NO;
}
santa_action_t SantaCachedDecision::getAction() const {
return action_;
- (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;
}
}
void SantaCachedDecision::setAction(
const santa_action_t action, const uint64_t microsecs) {
action_ = action;
microsecs_ = microsecs;
- (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,6 +18,7 @@
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -36,8 +37,8 @@
[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.notificationManager = [[SNTNotificationManager alloc] init];
@@ -71,28 +72,37 @@
#pragma mark Connection handling
- (void)createConnection {
dispatch_async(dispatch_get_main_queue(), ^{
__weak __typeof(self) weakSelf = self;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.invalidationHandler = ^{
[weakSelf attemptReconnection];
};
[self.listener resume];
WEAKIFY(self);
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
[daemonConn resume];
[[daemonConn remoteObjectProxy] setNotificationListener:listener.endpoint];
});
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.acceptedHandler = ^{
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
STRONGIFY(self);
[self attemptReconnection];
};
[self.listener 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 attemptReconnection];
}
}
- (void)attemptReconnection {
// TODO(rah): Make this smarter.
sleep(5);
[self createConnection];
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
}
#pragma mark Menu Management

View File

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

View File

@@ -12,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,41 +31,9 @@
- (IBAction)closeWindow:(id)sender;
- (IBAction)showCertInfo:(id)sender;
///
/// The execution event that this window is for
///
@property SNTStoredEvent *event;
///
/// The custom message to display for this event
///
@property(copy) NSString *customMessage;
///
/// The delegate to inform when the notification is dismissed
///
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
///
/// 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 "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
///
@property IBOutlet NSButton *openEventButton;
///
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
///
@property IBOutlet NSTextField *applicationNameLabel;
@end

View File

@@ -14,21 +14,47 @@
#import "SNTMessageWindowController.h"
#import <SecurityInterface/SFCertificatePanel.h>
@import SecurityInterface.SFCertificatePanel;
#import "MOLCertificate.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTFileInfo.h"
#import "SNTMessageWindow.h"
#import "SNTStoredEvent.h"
@interface SNTMessageWindowController ()
/// The execution event that this window is for
@property SNTStoredEvent *event;
/// The custom message to display for this event
@property(copy) NSString *customMessage;
/// 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 "Open Event" button in the XIB. Used to either remove the button
/// if it isn't needed or set its title if it is.
@property IBOutlet NSButton *openEventButton;
/// Reference to the "Application Name" label in the XIB. Used to remove if application
/// doesn't have a CFBundleName.
@property IBOutlet NSTextField *applicationNameLabel;
/// 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;
}
return self;
}
@@ -61,7 +87,13 @@
}
- (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 {
@@ -80,18 +112,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
@@ -100,7 +123,7 @@
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return nil;
return [NSSet set];
}
}
@@ -119,39 +142,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) {
message = self.customMessage;
} else if (self.event.decision == EVENTSTATE_BLOCK_UNKNOWN) {
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];
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,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Cocoa;
#import "SNTMessageWindowController.h"
#import "SNTXPCNotifierInterface.h"

View File

@@ -14,22 +14,23 @@
#import "SNTNotificationManager.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@interface SNTNotificationManager ()
///
/// The currently displayed notification
///
@property SNTMessageWindowController *currentWindowController;
///
/// The queue of pending notifications
///
@property(readonly) NSMutableArray *pendingNotifications;
@end
@implementation SNTNotificationManager
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
- (instancetype)init {
self = [super init];
if (self) {
@@ -38,7 +39,9 @@
return self;
}
- (void)windowDidClose {
- (void)windowDidCloseSilenceHash:(NSString *)hash {
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
[self.pendingNotifications removeObject:self.currentWindowController];
self.currentWindowController = nil;
@@ -50,7 +53,43 @@
}
}
#pragma mark SNTNotifierXPC protocol methods
- (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 method
- (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.
@@ -58,40 +97,50 @@
[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];
}
});
}
- (void)postBlockNotificationMainThread:(NSDictionary *)dict {
SNTStoredEvent *event = dict[@"event"];
NSString *msg = dict[@"custommsg"];
// Create message window
SNTMessageWindowController *pendingMsg = [[SNTMessageWindowController alloc] initWithEvent:event
andMessage:msg];
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;
[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)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];
}
@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,43 @@
/// 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;
@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,127 @@
/// 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.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

@@ -12,63 +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,
EVENTSTATE_BLOCK_UNKNOWN = 5,
EVENTSTATE_BLOCK_BINARY = 6,
EVENTSTATE_BLOCK_CERTIFICATE = 7,
EVENTSTATE_BLOCK_SCOPE = 8,
EVENTSTATE_RELATED_BINARY = 9,
EVENTSTATE_MAX
} santa_eventstate_t;
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
SNTRuleTableErrorEmptyRuleArray,
SNTRuleTableErrorInsertOrReplaceFailed,
SNTRuleTableErrorInvalidRule,
SNTRuleTableErrorMissingRequiredRule,
SNTRuleTableErrorRemoveFailed
};
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";
#endif // SANTA__COMMON__COMMONENUMS_H

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
@@ -28,7 +30,7 @@ extern NSString *const kDefaultConfigFilePath;
///
/// The operating mode.
///
@property(nonatomic) santa_clientmode_t clientMode;
@property(nonatomic) SNTClientMode clientMode;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
@@ -75,18 +77,24 @@ extern NSString *const kDefaultConfigFilePath;
///
/// 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, nonatomic) NSString *eventDetailURL;
@property(readonly, nonatomic) NSString *eventDetailBundleURL;
///
/// Related to the above property, this string represents the text to show on the button.
@@ -106,6 +114,18 @@ extern NSString *const kDefaultConfigFilePath;
///
@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
///
@@ -125,9 +145,14 @@ extern NSString *const kDefaultConfigFilePath;
@property(readonly, nonatomic) NSString *machineOwner;
///
/// The last date of successful sync.
/// The last date of a successful full sync.
///
@property(nonatomic) NSDate *syncLastSuccess;
@property(nonatomic) NSDate *fullSyncLastSuccess;
///
/// The last date of a successful rule sync.
///
@property(nonatomic) NSDate *ruleSyncLastSuccess;
///
/// If YES a clean sync is required.
@@ -139,6 +164,24 @@ extern NSString *const kDefaultConfigFilePath;
///
@property(readonly, nonatomic) NSString *machineID;
///
/// The number of seconds in-between full syncs while connected to FCM
///
/// @note The default value is 14400.
/// @note The minimum value is 600.
///
@property(readonly, nonatomic) NSInteger FCMFullSyncInterval;
///
/// The maximum number of seconds to wait before a rule sync when
/// receiving a global rule sync message. Each client will choose a random
/// number of seconds between 0 and FCMGlobalRuleLeeway.
///
/// @note The default value is 600.
/// @note The minimum value is 60.
///
@property(readonly, nonatomic) NSInteger FCMGlobalRuleLeeway;
#pragma mark Server Auth Settings
///

View File

@@ -44,12 +44,16 @@ static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection
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 kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
static NSString *const kRuleSyncLastSuccess = @"RuleSyncLastSuccess";
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
@@ -67,6 +71,9 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kFCMFullSyncInterval = @"FCMFullSyncInterval";
static NSString *const kFCMGlobalRuleLeeway = @"FCMGlobalRuleLeeway";
- (instancetype)initWithFilePath:(NSString *)filePath {
self = [super init];
if (self) {
@@ -96,20 +103,29 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
#pragma mark Public Interface
- (santa_clientmode_t)clientMode {
int cm = [self.configData[kClientModeKey] intValue];
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
return (santa_clientmode_t)cm;
- (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);
}
}
@@ -189,6 +205,10 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kEventDetailURLKey];
}
- (NSString *)eventDetailBundleURL {
return self.configData[kEventDetailBundleURLKey];
}
- (NSString *)eventDetailText {
return self.configData[kEventDetailTextKey];
}
@@ -201,6 +221,14 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configData[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configData[kModeNotificationLockdown];
}
- (NSURL *)syncBaseURL {
NSString *urlStr = self.configData[kSyncBaseURLKey];
if (urlStr) {
@@ -235,12 +263,22 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kServerAuthRootsFileKey];
}
- (NSDate *)syncLastSuccess {
return self.configData[kSyncLastSuccess];
- (NSDate *)fullSyncLastSuccess {
return self.configData[kFullSyncLastSuccess];
}
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
self.configData[kSyncLastSuccess] = syncLastSuccess;
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self saveConfigToDisk];
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
- (NSDate *)ruleSyncLastSuccess {
return self.configData[kRuleSyncLastSuccess];
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveConfigToDisk];
}
@@ -291,16 +329,34 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return machineId;
}
- (NSInteger)FCMFullSyncInterval {
NSInteger interval = [self.configData[kFCMFullSyncInterval] integerValue];
return (interval < 600) ? 1440 : interval;
}
- (NSInteger)FCMGlobalRuleLeeway {
NSInteger leeway = [self.configData[kFCMGlobalRuleLeeway] integerValue];
return (leeway < 60) ? 600 : leeway;
}
- (void)reloadConfigData {
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:self.configFilePath]) return;
if (![fm fileExistsAtPath:self.configFilePath]) {
// As soon as saveConfigToDisk is called, reloadConfigData will be called again because
// of the SNTFileWatchers on the config path. No need to use dictionaryWithCapacity: here.
self.configData = [NSMutableDictionary dictionary];
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
[self saveConfigToDisk];
return;
};
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath
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;
}
@@ -310,7 +366,8 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
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;
}

View File

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

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
///
/// Represents a binary on disk, providing access to details about that binary
/// such as the SHA-1, SHA-256, Info.plist and the Mach-O data.
@@ -84,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.
///

View File

@@ -76,7 +76,17 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return nil;
}
_fileHandle = [NSFileHandle fileHandleForReadingAtPath:_path];
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);
@@ -102,7 +112,9 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
#pragma mark Hashing
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
const int chunkSize = 4096;
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;
@@ -110,46 +122,59 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
if (sha1) CC_SHA1_Init(&c1);
if (sha256) CC_SHA256_Init(&c256);
for (uint64_t offset = 0; offset < self.fileSize; offset += chunkSize) {
@autoreleasepool {
int readSize = 0;
if (offset + chunkSize > self.fileSize) {
readSize = (int)(self.fileSize - offset);
} else {
readSize = chunkSize;
}
int fd = self.fileHandle.fileDescriptor;
NSData *chunk = [self safeSubdataWithRange:NSMakeRange(offset, readSize)];
if (!chunk) {
if (sha1) CC_SHA1_Final(NULL, &c1);
if (sha256) CC_SHA256_Final(NULL, &c256);
return;
}
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;
if (sha1) CC_SHA1_Update(&c1, chunk.bytes, readSize);
if (sha256) CC_SHA256_Update(&c256, chunk.bytes, readSize);
for (uint64_t offset = 0; offset < _fileSize;) {
bytesRead = pread(fd, chunk, chunkSize, offset);
if (bytesRead > 0) {
if (sha1) CC_SHA1_Update(&c1, chunk, (CC_LONG)bytesRead);
if (sha256) CC_SHA256_Update(&c256, chunk, (CC_LONG)bytesRead);
offset += bytesRead;
} else if (bytesRead == -1 && errno == EINTR) {
continue;
} else {
return;
}
}
// We turn off Read Ahead that we turned on
fcntl(fd, F_RDAHEAD, 0);
if (sha1) {
unsigned char dgst[CC_SHA1_DIGEST_LENGTH];
CC_SHA1_Final(dgst, &c1);
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)dgst[i]];
}
*sha1 = [buf copy];
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 dgst[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(dgst, &c256);
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";
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)dgst[i]];
}
*sha256 = [buf copy];
*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]];
}
}
@@ -171,22 +196,26 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
return [self.machHeaders allKeys];
}
- (BOOL)isExecutable {
- (uint32_t)machFileType {
struct mach_header *mach_header = [self firstMachHeader];
if (mach_header && mach_header->filetype == MH_EXECUTE) return YES;
return NO;
if (mach_header) return mach_header->filetype;
return -1;
}
- (BOOL)isExecutable {
return [self machFileType] == MH_EXECUTE;
}
- (BOOL)isDylib {
struct mach_header *mach_header = [self firstMachHeader];
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
return NO;
return [self machFileType] == MH_DYLIB;
}
- (BOOL)isBundle {
return [self machFileType] == MH_BUNDLE;
}
- (BOOL)isKext {
struct mach_header *mach_header = [self firstMachHeader];
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
return NO;
return [self machFileType] == MH_KEXT_BUNDLE;
}
- (BOOL)isMachO {
@@ -199,18 +228,18 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
- (BOOL)isScript {
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 2)] bytes];
return (strncmp("#!", magic, 2) == 0);
return (magic && memcmp("#!", magic, 2) == 0);
}
- (BOOL)isXARArchive {
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 4)] bytes];
return (strncmp("xar!", magic, 4) == 0);
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 (strncmp("koly", magic, 4) == 0);
return (magic && memcmp("koly", magic, 4) == 0);
}
#pragma mark Page Zero
@@ -230,7 +259,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
if (!lcData) return NO;
// This code assumes the __PAGEZERO is always the first load-command in the file.
// Given that the OS X ABI says "the static linker creates a __PAGEZERO segment
// 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) {
@@ -273,9 +302,10 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
// Check that the full path is at least 4-levels deep:
// e.g: /Calendar.app/Contents/MacOS/Calendar
NSArray *pathComponents = [self.path pathComponents];
if ([pathComponents count] < 4) return nil;
NSUInteger pathComponentsCount = pathComponents.count;
if (pathComponentsCount < 4) return nil;
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, pathComponentsCount - 3)];
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
}
@@ -310,7 +340,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
}
- (NSString *)bundleName {
return [[self.infoPlist objectForKey:@"CFBundleName"] description];
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
[[self.infoPlist objectForKey:@"CFBundleName"] description];
}
- (NSString *)bundleVersion {
@@ -370,7 +401,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
NSData *fatHeader = [self safeSubdataWithRange:range];
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
if (fatHeader && (fh->magic == FAT_CIGAM || fh->magic == FAT_MAGIC)) {
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
@@ -444,7 +475,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
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 (strncmp(lc->segname, "__TEXT", 6) == 0) {
if (memcmp(lc->segname, "__TEXT", 6) == 0) {
nsects = lc->nsects;
offset += sz_segment;
break;
@@ -458,7 +489,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
if (!sectData) return nil;
struct section_64 *sect = (struct section_64 *)[sectData bytes];
if (sect && strncmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
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;
@@ -503,7 +534,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
/// is not the one who downloaded the file.
///
- (NSDictionary *)quarantineData {
if (!self.quarantineDict && self.fileOwnerHomeDir) {
if (!self.quarantineDict && self.fileOwnerHomeDir && NSURLQuarantinePropertiesKey) {
self.quarantineDict = (NSDictionary *)[NSNull null];
NSURL *url = [NSURL fileURLWithPath:self.path];

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,57 +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);
self.internalCancelHandler = ^{
int fd;
WEAKIFY(self);
if (weakSelf.monitoringSource) {
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
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);
});
dispatch_source_set_registration_handler(self.source, ^{
STRONGIFY(self);
self.handler(0);
});
dispatch_source_set_cancel_handler(self.source, ^{
close(fd);
}
});
const char *filePathCString = [weakSelf.filePath fileSystemRepresentation];
while ((fd = open(filePathCString, 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

@@ -28,6 +28,10 @@
#define USERCLIENT_CLASS "com_google_SantaDriver"
#define USERCLIENT_ID "com.google.santa-driver"
// Branch prediction
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
// List of methods supported by the driver.
enum SantaDriverMethods {
kSantaUserClientOpen,
@@ -35,6 +39,7 @@ 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.

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,6 +34,8 @@
#else // KERNEL
@import Foundation;
typedef enum : NSUInteger {
LOG_LEVEL_ERROR,
LOG_LEVEL_WARN,

View File

@@ -42,10 +42,8 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
}
// If requested, redirect output to syslog.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
strcmp(binaryName, "santad") == 0) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
useSyslog = YES;
pthread_key_create(&syslogKey, syslogClientDestructor);
}
});
@@ -54,7 +52,7 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
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);
if (useSyslog) {
@@ -88,6 +86,8 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
} else {
fprintf(destination, "%s\n", [s UTF8String]);
[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.
@@ -27,12 +29,12 @@
///
/// 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
@@ -43,8 +45,8 @@
/// 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,8 +17,8 @@
@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) {
@@ -77,7 +77,7 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %d, Type: %d",
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
self.shasum, self.state, self.type];
}

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,16 @@
@property NSString *filePath;
///
/// If the executed file was part of the bundle, this is the CFBundleName.
/// 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.
///
@@ -73,7 +81,7 @@
///
/// The decision santad returned.
///
@property santa_eventstate_t decision;
@property SNTEventState decision;
///
/// NSArray of logged in users when the decision was made.

View File

@@ -34,6 +34,7 @@
ENCODE(self.filePath, @"filePath");
ENCODE(self.fileBundleName, @"fileBundleName");
ENCODE(self.fileBundlePath, @"fileBundlePath");
ENCODE(self.fileBundleID, @"fileBundleID");
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
@@ -56,6 +57,14 @@
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
}
- (instancetype)init {
self = [super init];
if (self) {
_idx = @(arc4random());
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
@@ -64,6 +73,7 @@
_filePath = DECODE(NSString, @"filePath");
_fileBundleName = DECODE(NSString, @"fileBundleName");
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
_fileBundleID = DECODE(NSString, @"fileBundleID");
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
@@ -72,7 +82,7 @@
_executingUser = DECODE(NSString, @"executingUser");
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
_decision = (santa_eventstate_t)[DECODE(NSNumber, @"decision") intValue];
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
_pid = DECODE(NSNumber, @"pid");
_ppid = DECODE(NSNumber, @"ppid");
_parentName = DECODE(NSString, @"parentName");

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

@@ -12,9 +12,11 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
/**
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
validation of connecting clients and a simpler interface.
validation of connecting clients and forced connection establishment.
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
@@ -40,7 +42,12 @@
@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>
@@ -48,14 +55,14 @@
/**
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
*/
- (instancetype)initServerWithListener:(NSXPCListener *)listener;
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
/**
Initializer for the 'server' side of the connection, started by launchd.
@param name MachService name, must match the MachServices key in the launchd.plist
*/
- (instancetype)initServerWithName:(NSString *)name;
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
/**
Initializer a new client to a service exported by a LaunchDaemon.
@@ -63,17 +70,20 @@
@param name MachService name
@param privileged Use YES if the server is running as root.
*/
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged;
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
/**
Initialize a new client with a listener endpoint sent from another process.
@param listener An NSXPCListenerEndpoint to connect to.
*/
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener;
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
/**
Call when the properties of the object have been set-up and you're ready for connections.
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;
@@ -84,27 +94,35 @@
/**
The interface the remote object should conform to. (client)
*/
@property(retain) NSXPCInterface *remoteInterface;
*/
@property(retain, nullable) NSXPCInterface *remoteInterface;
/**
A proxy to the object at the other end of the connection. (client)
*/
@property(readonly, nonatomic) id remoteObjectProxy;
@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) NSXPCInterface *exportedInterface;
*/
@property(retain, nullable) NSXPCInterface *exportedInterface;
/**
The object that responds to messages from the other end. (server)
*/
@property(retain) id exportedObject;
*/
@property(retain, nullable) id exportedObject;
/**
A block to run when a/the connection is invalidated/interrupted.
*/
@property(copy) void (^invalidationHandler)(void);
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

@@ -16,11 +16,39 @@
#import "MOLCodesignChecker.h"
#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 (server only).
@property NSXPCListener *listenerObject;
/// Array of accepted connections (server only).
@property NSMutableArray *acceptedConnections;
/// The current connection object (client only).
@property NSXPCConnection *currentConnection;
@@ -34,8 +62,8 @@
self = [super init];
if (self) {
_listenerObject = listener;
if (!_listenerObject) return nil;
_acceptedConnections = [NSMutableArray array];
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
@@ -49,6 +77,8 @@
if (self) {
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
@@ -59,6 +89,8 @@
NSXPCConnectionOptions options = (privileged ? NSXPCConnectionPrivileged : 0);
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
@@ -75,10 +107,31 @@
self.listenerObject.delegate = self;
[self.listenerObject resume];
} else {
self.currentConnection.remoteObjectInterface = self.remoteInterface;
self.currentConnection.interruptionHandler = self.invalidationHandler;
self.currentConnection.invalidationHandler = self.invalidationHandler;
WEAKIFY(self);
// 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();
};
[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();
}];
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];
}
}
}
@@ -89,23 +142,36 @@
return NO;
}
[self.acceptedConnections addObject:connection];
// 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];
__weak __typeof(connection) weakConnection = connection;
connection.interruptionHandler = connection.invalidationHandler = ^{
[self.acceptedConnections removeObject:weakConnection];
if (self.invalidationHandler) self.invalidationHandler();
// The connection is now established.
if (self.acceptedHandler) self.acceptedHandler();
};
connection.exportedInterface = self.exportedInterface;
connection.exportedObject = self.exportedObject;
connection.exportedInterface = self.validationInterface;
connection.exportedObject = ci;
[connection resume];
return YES;
}
- (id)remoteObjectProxy {
if (self.currentConnection.remoteObjectInterface) {
if (self.currentConnection.remoteObjectInterface &&
self.currentConnection.remoteObjectInterface != self.validationInterface) {
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
[self.currentConnection invalidate];
}];
@@ -119,11 +185,8 @@
if (self.currentConnection) {
[self.currentConnection invalidate];
self.currentConnection = nil;
} else if (self.acceptedConnections.count) {
for (NSXPCConnection *conn in self.acceptedConnections) {
[conn invalidate];
}
[self.acceptedConnections removeAllObjects];
} else if (self.listenerObject) {
[self.listenerObject invalidate];
}
}

View File

@@ -12,7 +12,13 @@
/// 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"
@class SNTRule;
@class SNTStoredEvent;
@@ -28,31 +34,48 @@
///
- (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 (^)(int64_t binary, int64_t certificate))reply;
- (void)databaseRuleAddRule:(SNTRule *)rule
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(BOOL success))reply;
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(BOOL success))reply;
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
reply:(void (^)(SNTRule *))reply;
///
/// Decision ops
///
///
/// @param filePath A Path to the file, can be nil.
/// @param fileSHA256 The pre-calculated SHA256 hash for the file, can be nil. If nil the hash will
/// be calculated by this method from the filePath.
/// @param signingCertificate A MOLCertificate object, can be nil.
/// @note If fileInfo and signingCertificate are both passed in, the most specific rule will be
/// returned. Binary rules take precedence over cert rules.
///
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
signingCertificate:(MOLCertificate *)signingCertificate
reply:(void (^)(SNTEventState))reply;
///
/// Config ops
///
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())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;
@@ -62,6 +85,14 @@
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)pushNotifications:(void (^)(BOOL))reply;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply;
@end
@interface SNTXPCControlInterface : NSObject

View File

@@ -12,11 +12,17 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommonEnums.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
@interface SNTXPCNotifierInterface : NSObject

View File

@@ -0,0 +1,36 @@
/// 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)rescheduleSyncSecondsFromNow:(uint64_t)seconds;
- (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,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 "SNTXPCSyncdInterface.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
}
@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(0);
/**
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

@@ -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__SANTACACHEDDECISION_H
#define SANTA__SANTA_DRIVER__SANTACACHEDDECISION_H
#include <libkern/c++/OSObject.h>
#include "SNTKernelCommon.h"
///
/// An OSObject subclass to store a @c santa_action_t and a timestamp.
/// Only OSObject subclasses can be inserted into an OSDictionary.
///
class SantaCachedDecision : public OSObject {
OSDeclareDefaultStructors(SantaCachedDecision)
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__SANTACACHEDDECISIONWRAPPER_H

View File

@@ -24,40 +24,33 @@ bool SantaDecisionManager::init() {
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();
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_);
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
vnode_pid_map_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
cached_decisions_ = OSDictionary::withCapacity(1000);
vnode_pid_map_ = OSDictionary::withCapacity(1000);
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));
decision_dataqueue_ = IOSharedDataQueue::withEntries(
kMaxDecisionQueueEvents, sizeof(santa_message_t));
if (!decision_dataqueue_) return kIOReturnNoMemory;
log_dataqueue_ = IOSharedDataQueue::withEntries(kMaxLogQueueEvents,
sizeof(santa_message_t));
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_lock_) {
lck_rw_free(cached_decisions_lock_, sdm_lock_grp_);
cached_decisions_lock_ = nullptr;
}
if (vnode_pid_map_lock_) {
lck_rw_free(vnode_pid_map_lock_, sdm_lock_grp_);
vnode_pid_map_lock_ = nullptr;
}
delete decision_cache_;
delete vnode_pid_map_;
if (decision_dataqueue_lock_) {
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
@@ -86,8 +79,6 @@ void SantaDecisionManager::free() {
OSSafeReleaseNULL(decision_dataqueue_);
OSSafeReleaseNULL(log_dataqueue_);
OSSafeReleaseNULL(cached_decisions_);
OSSafeReleaseNULL(vnode_pid_map_);
super::free();
}
@@ -97,12 +88,12 @@ void SantaDecisionManager::free() {
void SantaDecisionManager::ConnectClient(pid_t pid) {
if (!pid) return;
client_pid_ = pid;
// Any decisions made while the daemon wasn't
// connected should be cleared
ClearCache();
client_pid_ = pid;
failed_decision_queue_requests_ = 0;
failed_log_queue_requests_ = 0;
}
@@ -113,7 +104,7 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
// Ask santad to shutdown, in case it's running.
if (!itDied) {
santa_message_t *message = new santa_message_t;
auto message = new santa_message_t;
message->action = ACTION_REQUEST_SHUTDOWN;
PostToDecisionQueue(message);
delete message;
@@ -136,8 +127,9 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
}
bool SantaDecisionManager::ClientConnected() const {
proc_t p = proc_find(client_pid_);
bool is_exiting = false;
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);
@@ -168,14 +160,12 @@ IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
#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;
fileop_listener_ = kauth_listen_scope(KAUTH_SCOPE_FILEOP,
fileop_scope_callback,
reinterpret_cast<void *>(this));
fileop_listener_ = kauth_listen_scope(
KAUTH_SCOPE_FILEOP, fileop_scope_callback, reinterpret_cast<void *>(this));
if (!fileop_listener_) return kIOReturnInternalError;
LOGD("Listeners started.");
@@ -206,131 +196,88 @@ kern_return_t SantaDecisionManager::StopListener() {
#pragma mark Cache Management
void SantaDecisionManager::AddToCache(
const char *identifier, santa_action_t decision, uint64_t microsecs) {
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.
LOGI("Cache too large, flushing.");
ClearCache();
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 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_BINARY) {
SantaCachedDecision *pending = new SantaCachedDecision();
pending->setAction(ACTION_REQUEST_BINARY, 0);
lck_rw_lock_exclusive(cached_decisions_lock_);
cached_decisions_->setObject(identifier, pending);
lck_rw_unlock_exclusive(cached_decisions_lock_);
pending->release(); // it was retained when added to the dictionary
} else {
lck_rw_lock_exclusive(cached_decisions_lock_);
SantaCachedDecision *pending = OSDynamicCast(
SantaCachedDecision, cached_decisions_->getObject(identifier));
if (pending) {
pending->setAction(decision, microsecs);
}
lck_rw_unlock_exclusive(cached_decisions_lock_);
}
wakeup((void *)identifier);
}
void SantaDecisionManager::CacheCheck(const char *identifier) {
lck_rw_lock_shared(cached_decisions_lock_);
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != nullptr);
if (shouldInvalidate) {
if (!lck_rw_lock_shared_to_exclusive(cached_decisions_lock_)) {
// shared_to_exclusive will return false if a previous reader upgraded
// and if that happens the lock will have been unlocked. If that happens,
// which is rare, relock exclusively.
lck_rw_lock_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);
wakeup((void *)identifier);
}
uint64_t SantaDecisionManager::CacheCount() const {
return cached_decisions_->getCount();
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();
}
#pragma mark Decision Fetching
santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
santa_action_t result = ACTION_UNSET;
santa_action_t SantaDecisionManager::GetFromCache(uint64_t identifier) {
auto result = ACTION_UNSET;
uint64_t decision_time = 0;
lck_rw_lock_shared(cached_decisions_lock_);
SantaCachedDecision *cached_decision = OSDynamicCast(
SantaCachedDecision, 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;
// Decision is stored in upper 8 bits, timestamp in remaining 56.
result = (santa_action_t)(cache_val >> 56);
decision_time = (cache_val & ~(0xFF00000000000000));
if (RESPONSE_VALID(result)) {
uint64_t diff_time = GetCurrentUptime();
if (result == ACTION_RESPOND_ALLOW) {
if ((kMaxAllowCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxAllowCacheTimeMilliseconds * 1000);
}
} else if (result == ACTION_RESPOND_DENY) {
if (result == ACTION_RESPOND_DENY) {
auto diff_time = GetCurrentUptime();
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
diff_time = 0;
} else {
diff_time -= (kMaxDenyCacheTimeMilliseconds * 1000);
}
}
if (decision_time < diff_time) {
lck_rw_lock_exclusive(cached_decisions_lock_);
cached_decisions_->removeObject(identifier);
lck_rw_unlock_exclusive(cached_decisions_lock_);
return ACTION_UNSET;
if (decision_time < diff_time) {
decision_cache_->remove(identifier);
return ACTION_UNSET;
}
}
}
return result;
}
santa_action_t SantaDecisionManager::GetFromDaemon(
santa_message_t *message, const char *vnode_id_str) {
santa_action_t return_action = ACTION_UNSET;
santa_action_t SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
auto return_action = ACTION_UNSET;
#ifdef DEBUG
clock_sec_t secs = 0;
clock_usec_t microsecs = 0;
clock_get_system_microtime(&secs, &microsecs);
uint64_t uptime = (secs * 1000000) + microsecs;
#endif
// Wait for the daemon to respond or die.
do {
// Add pending request to cache.
AddToCache(vnode_id_str, ACTION_REQUEST_BINARY, 0);
// Add pending request to cache, to be replaced by daemon with actual response
AddToCache(identifier, ACTION_REQUEST_BINARY, 0);
// Send request to daemon...
if (!PostToDecisionQueue(message)) {
OSIncrementAtomic(&failed_decision_queue_requests_);
if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
LOGE("Failed to queue more than %d requests, killing daemon",
kMaxDecisionQueueFailures);
proc_signal(client_pid_, SIGKILL);
client_pid_ = 0;
}
LOGE("Failed to queue request for %s.", message->path);
CacheCheck(vnode_id_str);
RemoveFromCache(identifier);
return ACTION_ERROR;
}
do {
IOSleep(kRequestLoopSleepMilliseconds);
return_action = GetFromCache(vnode_id_str);
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());
@@ -338,26 +285,40 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
if (!RESPONSE_VALID(return_action)) {
LOGE("Daemon process did not respond correctly. Allowing executions "
"until it comes back. Executable path: %s", message->path);
CacheCheck(vnode_id_str);
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;
}
santa_action_t SantaDecisionManager::FetchDecision(
const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id,
const char *vnode_id_str) {
santa_action_t return_action = ACTION_UNSET;
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
const uint64_t vnode_id) {
while (true) {
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
// Check to see if item is in cache
return_action = GetFromCache(vnode_id_str);
// Check to see if item is in cache
auto return_action = GetFromCache(vnode_id);
// If item was in cache return it.
if (RESPONSE_VALID(return_action)) return return_action;
// If item was in cache with a valid response, return it.
// If item is in cache but hasn't received a response yet, sleep for a bit.
// If item is not in cache, break out of loop to send request to daemon.
if (RESPONSE_VALID(return_action)) {
return return_action;
} else if (return_action == ACTION_REQUEST_BINARY) {
msleep((void *)vnode_id, NULL, 0, "", &ts_);
} else {
break;
}
}
// Get path
char path[MAXPATHLEN];
@@ -366,71 +327,53 @@ santa_action_t SantaDecisionManager::FetchDecision(
path[0] = '\0';
}
santa_message_t *message = NewMessage();
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));
santa_action_t ret = GetFromDaemon(message, vnode_id_str);
auto return_action = GetFromDaemon(message, vnode_id);
delete message;
return ret;
return return_action;
}
#pragma mark Misc
santa_message_t *SantaDecisionManager::NewMessage() const {
santa_message_t *message = new santa_message_t;
message->uid = kauth_getuid();
message->gid = kauth_getgid();
message->pid = proc_selfpid();
message->ppid = proc_selfppid();
return message;
}
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
bool kr = false;
lck_mtx_lock(decision_dataqueue_lock_);
kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
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;
}
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
bool kr = false;
lck_mtx_lock(log_dataqueue_lock_);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
if (!kr) {
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
if (failed_log_queue_requests_++ == 0) {
LOGW("Dropping log queue messages");
}
// If enqueue failed, pop an item off the queue and try again.
uint32_t dataSize = sizeof(santa_message_t);
uint32_t dataSize = 0;
log_dataqueue_->dequeue(0, &dataSize);
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
} else {
OSCompareAndSwap(1, 0, &failed_log_queue_requests_);
if (failed_log_queue_requests_ > 0) {
failed_log_queue_requests_--;
}
}
lck_mtx_unlock(log_dataqueue_lock_);
return kr;
}
uint64_t SantaDecisionManager::GetVnodeIDForVnode(
const vfs_context_t ctx, const vnode_t vp) const {
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);
}
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 Invocation Tracking & PID comparison
void SantaDecisionManager::IncrementListenerInvocations() {
@@ -447,36 +390,29 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
const vfs_context_t ctx,
const vnode_t vp,
int *errno) {
// Only operate on regular files (not directories, symlinks, etc.).
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
// Get ID for the vnode and convert it to a string.
uint64_t vnode_id = GetVnodeIDForVnode(ctx, vp);
char vnode_str[MAX_VNODE_ID_STR];
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
// Get ID for the vnode
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
// Fetch decision
santa_action_t returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
auto returnedAction = FetchDecision(cred, vp, vnode_id);
// If file has dirty blocks, remove from cache and deny. This would usually
// be the case if a file has been written to and flushed but not yet
// closed.
if (vnode_hasdirtyblks(vp)) {
CacheCheck(vnode_str);
RemoveFromCache(vnode_id);
returnedAction = ACTION_RESPOND_DENY;
}
switch (returnedAction) {
case ACTION_RESPOND_ALLOW: {
proc_t proc = vfs_context_proc(ctx);
auto proc = vfs_context_proc(ctx);
if (proc) {
SantaPIDAndPPID *pidWrapper = new SantaPIDAndPPID;
pidWrapper->pid = proc_pid(proc);
pidWrapper->ppid = proc_ppid(proc);
lck_rw_lock_exclusive(vnode_pid_map_lock_);
vnode_pid_map_->setObject(vnode_str, pidWrapper);
lck_rw_unlock_exclusive(vnode_pid_map_lock_);
pidWrapper->release();
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;
}
@@ -494,42 +430,36 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
void SantaDecisionManager::FileOpCallback(
const kauth_action_t action, const vnode_t vp,
const char *path, const char *new_path) {
if (!ClientConnected() || proc_selfpid() == client_pid_) return;
if (vp) {
vfs_context_t context = vfs_context_create(nullptr);
uint64_t vnode_id = GetVnodeIDForVnode(context, vp);
auto context = vfs_context_create(nullptr);
auto vnode_id = GetVnodeIDForVnode(context, vp);
vfs_context_rele(context);
if (action == KAUTH_FILEOP_CLOSE) {
char vnode_id_str[MAX_VNODE_ID_STR];
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
CacheCheck(vnode_id_str);
RemoveFromCache(vnode_id);
} else if (action == KAUTH_FILEOP_EXEC) {
santa_message_t *message = NewMessage();
auto message = NewMessage(nullptr);
message->vnode_id = vnode_id;
message->action = ACTION_NOTIFY_EXEC;
strlcpy(message->path, path, sizeof(message->path));
char vnode_str[MAX_VNODE_ID_STR];
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
lck_rw_lock_shared(vnode_pid_map_lock_);
SantaPIDAndPPID *pidWrapper = OSDynamicCast(
SantaPIDAndPPID, vnode_pid_map_->getObject(vnode_str));
if (pidWrapper) {
message->pid = pidWrapper->pid;
message->ppid = pidWrapper->ppid;
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);
}
lck_rw_unlock_shared(vnode_pid_map_lock_);
PostToLogQueue(message);
delete message;
return;
}
}
// Filter out modifications to locations that are definitely not useful.
if (ClientConnected() && !strprefix(path, "/.") && !strprefix(path, "/dev")) {
santa_message_t *message = NewMessage();
// 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));
@@ -550,7 +480,9 @@ void SantaDecisionManager::FileOpCallback(
case KAUTH_FILEOP_DELETE:
message->action = ACTION_NOTIFY_DELETE;
break;
default: delete message; return;
default:
delete message;
return;
}
PostToLogQueue(message);
@@ -563,17 +495,19 @@ void SantaDecisionManager::FileOpCallback(
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) {
SantaDecisionManager *sdm = OSDynamicCast(
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_CLOSE:
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED)) return KAUTH_RESULT_DEFER;
// Intentional fall-through
case KAUTH_FILEOP_DELETE:
case KAUTH_FILEOP_EXEC:
vp = reinterpret_cast<vnode_t>(arg0);
@@ -600,20 +534,35 @@ extern "C" int fileop_scope_callback(
extern "C" int vnode_scope_callback(
kauth_cred_t credential, void *idata, kauth_action_t action,
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
if (action & KAUTH_VNODE_ACCESS ||
!(action & KAUTH_VNODE_EXECUTE) ||
idata == nullptr) {
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
if (unlikely(sdm == nullptr)) {
LOGE("vnode_scope_callback called with no decision manager");
return KAUTH_RESULT_DEFER;
}
SantaDecisionManager *sdm =
OSDynamicCast(SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
reinterpret_cast<vnode_t>(arg1),
reinterpret_cast<int *>(arg3));
sdm->DecrementListenerInvocations();
return result;
// We only care about regular files.
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
if ((action & KAUTH_VNODE_EXECUTE) && !(action & KAUTH_VNODE_ACCESS)) {
sdm->IncrementListenerInvocations();
int result = sdm->VnodeCallback(credential,
reinterpret_cast<vfs_context_t>(arg0),
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 KAUTH_RESULT_DEFER;
}

View File

@@ -24,10 +24,9 @@
#include <sys/proc.h>
#include <sys/vnode.h>
#include "SantaCache.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
#include "SantaCachedDecision.h"
#include "SantaPIDAndPPID.h"
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
@@ -40,240 +39,274 @@ class SantaDecisionManager : public OSObject {
OSDeclareDefaultStructors(SantaDecisionManager);
public:
/// Used for initialization after instantiation. Required because
/// constructors cannot throw inside kernel-space.
/// Used for initialization after instantiation.
bool init() override;
/// Called automatically when retain count drops to 0.
/// Called automatically when retain count drops to 0.
void free() override;
/// Called by SantaDriverClient during connection to provide the shared
/// dataqueue memory to the client.
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the decision queue.
*/
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
/**
Called by SantaDriverClient during connection to provide the shared
dataqueue memory to the client for the logging queue.
*/
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
/// Called by SantaDriverClient when a client connects to the decision queue,
/// providing the pid of the client process.
/**
Called by SantaDriverClient when a client connects to the decision queue,
providing the pid of the client process.
*/
void ConnectClient(pid_t pid);
/// Called by SantaDriverClient when a client disconnects
/// Called by SantaDriverClient when a client disconnects
void DisconnectClient(bool itDied = false);
/// Returns whether a client is currently connected or not.
/// Returns whether a client is currently connected or not.
bool ClientConnected() const;
/// Sets the Mach port for notifying the decision queue.
/// Sets the Mach port for notifying the decision queue.
void SetDecisionPort(mach_port_t port);
/// Sets the Mach port for notifying the log queue.
/// Sets the Mach port for notifying the log queue.
void SetLogPort(mach_port_t port);
/// Starts the kauth listeners.
/// Starts the kauth listeners.
kern_return_t StartListener();
/// Stops the kauth listeners. 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 = 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.
/// Checks to see if a given identifier is in the cache and removes it.
void RemoveFromCache(uint64_t identifier);
/// Returns the number of entries in the cache.
uint64_t CacheCount() const;
/// Clears the cache.
/// Clears the cache.
void ClearCache();
/// Increments the count of active callbacks pending.
/// Increments the count of active callbacks pending.
void IncrementListenerInvocations();
/// Decrements the count of active callbacks 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_*.
///
/**
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.
///
/**
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:
///
/// The maximum number of milliseconds a cached deny message should be
/// considered valid.
///
const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
/**
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 allow message should be
/// considered valid.
///
const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
/// The maximum number of milliseconds a cached deny message should be considered valid.
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
///
/// 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;
/// Maximum number of entries in the in-kernel cache.
static const uint32_t kMaxCacheSize = 10000;
///
/// Maximum number of entries in the in-kernel cache.
///
const int kMaxCacheSize = 10000;
/// Maximum number of PostToDecisionQueue failures to allow.
static const uint32_t kMaxDecisionQueueFailures = 10;
///
/// Maximum number of PostToDecisionQueue failures to allow.
///
const int 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 decision data queue at any time.
///
const int kMaxDecisionQueueEvents = 512;
/// The maximum number of messages can be kept in the logging data queue at any time.
static const uint32_t kMaxLogQueueEvents = 2048;
///
/// The maximum number of messages can be kept
/// in the logging data queue at any time.
///
const int kMaxLogQueueEvents = 1024;
/**
Fetches a response from the daemon. Handles both daemon death
and failure to post messages to the daemon.
/// Fetches a response from the cache, first checking to see if the
/// entry has expired.
santa_action_t GetFromCache(const char *identifier);
@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 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,
const char *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.
///
/// 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.
/// @param vnode_id_str A string representation of the above ID.
///
santa_action_t FetchDecision(const kauth_cred_t cred,
const vnode_t vp,
const uint64_t vnode_id,
const char *vnode_id_str);
@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.
///
/**
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.
///
/**
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.
///
uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) const;
/**
Fetches the vnode_id for a given vnode.
///
/// Creates a new santa_message_t with some fields pre-filled.
///
santa_message_t *NewMessage() const;
@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);
}
///
/// Returns the current system uptime in microseconds
///
static uint64_t GetCurrentUptime();
/**
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_grp_attr_t *sdm_lock_grp_attr_;
lck_attr_t *sdm_lock_attr_;
lck_rw_t *cached_decisions_lock_;
lck_mtx_t *decision_dataqueue_lock_;
lck_mtx_t *log_dataqueue_lock_;
lck_rw_t *vnode_pid_map_lock_;
OSDictionary *cached_decisions_;
OSDictionary *vnode_pid_map_;
IOSharedDataQueue *decision_dataqueue_;
IOSharedDataQueue *log_dataqueue_;
SInt32 failed_decision_queue_requests_;
SInt32 failed_log_queue_requests_;
uint32_t failed_decision_queue_requests_;
uint32_t failed_log_queue_requests_;
SInt32 listener_invocations_;
int32_t listener_invocations_;
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 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);
/**
The kauth callback function for the Vnode scope
///
/// 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.
///
@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);
/**
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);
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

@@ -110,87 +110,74 @@ IOReturn SantaDriverClient::clientMemoryForType(
#pragma mark Callable Methods
IOReturn SantaDriverClient::open() {
if (isInactive()) return kIOReturnNotAttached;
IOReturn SantaDriverClient::open(
OSObject *target,
void *reference,
IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
if (!myProvider->open(this)) {
if (me->isInactive()) return kIOReturnNotAttached;
if (!me->myProvider->open(me)) {
LOGW("A second client tried to connect.");
return kIOReturnExclusiveAccess;
}
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_open(
SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->open();
}
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);
decisionManager->AddToCache(vnode_id_str, ACTION_RESPOND_ALLOW);
LOGI("Client connected.");
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_allow_binary(
SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
IOReturn SantaDriverClient::allow_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
return target->allow_binary(
static_cast<const uint64_t>(*arguments->scalarInput));
}
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);
decisionManager->AddToCache(vnode_id_str, ACTION_RESPOND_DENY);
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_deny_binary(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
IOReturn SantaDriverClient::deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
return target->deny_binary(
static_cast<const uint64_t>(*arguments->scalarInput));
}
const uint64_t vnode_id = static_cast<const uint64_t>(*arguments->scalarInput);
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
IOReturn SantaDriverClient::clear_cache() {
decisionManager->ClearCache();
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_clear_cache(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->clear_cache();
}
IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
me->decisionManager->ClearCache();
IOReturn SantaDriverClient::cache_count(uint64_t *output) {
*output = 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::cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
arguments->scalarOutput[0] = me->decisionManager->CacheCount();
return kIOReturnSuccess;
}
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
@@ -203,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

@@ -72,44 +72,33 @@ class com_google_SantaDriverClient : public IOUserClient {
///
/// The userpsace callable methods are below. Each method corresponds
/// to an entry in SantaDriverMethods. Each method has a static version
/// which just calls the method on the provided target.
/// to an entry in SantaDriverMethods.
///
/// Called during client connection.
IOReturn open();
static IOReturn static_open(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
static IOReturn open(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to allow a binary.
IOReturn allow_binary(uint64_t vnode_id);
static IOReturn static_allow_binary(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
static IOReturn allow_binary(
OSObject *target, void *reference,IOExternalMethodArguments *arguments);
/// The daemon calls this to deny a binary.
IOReturn deny_binary(uint64_t vnode_id);
static IOReturn static_deny_binary(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
static IOReturn deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to empty the cache.
IOReturn clear_cache();
static IOReturn static_clear_cache(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
static IOReturn clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to find out how many items are in the cache
IOReturn cache_count(uint64_t *output);
static IOReturn static_cache_count(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
static IOReturn cache_count(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to find out the status of a vnode_id in the cache.
/// Output will be a santa_action_t.
static IOReturn check_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
private:
com_google_SantaDriver *myProvider;

View File

@@ -1,32 +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__SANTAPIDANDPPID_H
#define SANTA__SANTA_DRIVER__SANTAPIDANDPPID_H
#include <libkern/c++/OSObject.h>
///
/// An OSObject wrapper around a PID and PPID.
/// Only OSObject subclasses can be inserted into an OSDictionary.
///
class SantaPIDAndPPID : public OSObject {
OSDeclareDefaultStructors(SantaPIDAndPPID)
public:
pid_t pid;
pid_t ppid;
};
#endif // SANTA__SANTA_DRIVER__SANTAPIDANDPPID_H

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)
signingCertificate:fi.csc.leafCertificate
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
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"flushcache")
#endif
+ (BOOL)requiresRoot {
return YES;

View File

@@ -12,6 +12,8 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTCommandController.h"
#include "SNTLogging.h"
@@ -77,8 +79,8 @@ REGISTER_COMMAND_NAME(@"rule")
}
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = RULESTATE_UNKNOWN;
newRule.type = RULETYPE_BINARY;
newRule.state = SNTRuleStateUnknown;
newRule.type = SNTRuleTypeBinary;
NSString *path;
@@ -87,15 +89,15 @@ REGISTER_COMMAND_NAME(@"rule")
NSString *arg = arguments[i];
if ([arg caseInsensitiveCompare:@"--whitelist"] == NSOrderedSame) {
newRule.state = RULESTATE_WHITELIST;
newRule.state = SNTRuleStateWhitelist;
} else if ([arg caseInsensitiveCompare:@"--blacklist"] == NSOrderedSame) {
newRule.state = RULESTATE_BLACKLIST;
newRule.state = SNTRuleStateBlacklist;
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
newRule.state = RULESTATE_SILENT_BLACKLIST;
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
newRule.state = RULESTATE_REMOVE;
newRule.state = SNTRuleStateRemove;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = RULETYPE_CERT;
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--path requires an argument"];
@@ -121,26 +123,33 @@ REGISTER_COMMAND_NAME(@"rule")
if (path) {
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
if (newRule.type == RULETYPE_BINARY) {
if (!fi.path) {
[self printErrorUsageAndExit:@"Provided path was not a plain file"];
}
if (newRule.type == SNTRuleTypeBinary) {
newRule.shasum = fi.SHA256;
} else if (newRule.type == RULETYPE_CERT) {
} else if (newRule.type == SNTRuleTypeCertificate) {
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path];
newRule.shasum = cs.leafCertificate.SHA256;
}
}
if (newRule.state == RULESTATE_UNKNOWN) {
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.shasum) {
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
}
[[daemonConn remoteObjectProxy] databaseRuleAddRule:newRule cleanSlate:NO reply:^(BOOL success) {
if (!success) {
printf("Failed to modify rules.");
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
cleanSlate:NO
reply:^(NSError *error) {
if (error) {
printf("Failed to modify rules: %s", [error.localizedDescription UTF8String]);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
exit(1);
} else {
if (newRule.state == RULESTATE_REMOVE) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,112 @@
/// 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 "SNTCommandSyncManager.h"
#import "SNTConfigurator.h"
#import "SNTDropRootPrivs.h"
#import "SNTLogging.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandSync : NSObject<SNTCommand>
@property SNTXPCConnection *listener;
@property SNTCommandSyncManager *syncManager;
@end
@implementation SNTCommandSync
REGISTER_COMMAND_NAME(@"sync")
#pragma mark SNTCommand protocol methods
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return NO;
}
+ (NSString *)shortHelpText {
return @"Synchronizes Santa with a configured server.";
}
+ (NSString *)longHelpText {
return (@"If Santa is configured to synchronize with a a server, "
@"this is the command used for syncing.\n\n"
@"Options:\n"
@" --clean: Perform a clean sync, erasing all existing rules and requesting a"
@" clean sync from the server.");
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
exit(1);
}
SNTCommandSync *s = [[self alloc] init];
[daemonConn resume];
BOOL daemon = [arguments containsObject:@"--daemon"];
s.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:daemonConn
isDaemon:daemon];
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0
diskCapacity:0
diskPath:nil]];
if (!s.syncManager.daemon) return [s.syncManager fullSync];
[s syncdWithDaemonConnection:daemonConn];
}
#pragma mark daemon methods
- (void)syncdWithDaemonConnection:(SNTXPCConnection *)daemonConn {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCSyncdInterface syncdInterface];
self.listener.exportedObject = self.syncManager;
self.listener.acceptedHandler = ^{
LOGD(@"santad <--> santactl connections established");
dispatch_semaphore_signal(sema);
};
self.listener.invalidationHandler = ^{
// If santad is unloaded kill santactl
LOGD(@"exiting");
exit(0);
};
[self.listener resume];
// Tell daemon to connect back to the above listener.
[[daemonConn remoteObjectProxy] setSyncdListener: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 performSelectorInBackground:@selector(syncdWithDaemonConnection:) withObject:daemonConn];
}
[self.syncManager fullSyncSecondsFromNow:15];
}
@end

View File

@@ -12,10 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
extern NSString *const kURLPreflight;
extern NSString *const kURLEventUpload;
extern NSString *const kURLRuleDownload;
extern NSString *const kURLPostflight;
@import Foundation;
extern NSString *const kXSRFToken;
extern NSString *const kSerialNumber;
extern NSString *const kHostname;
@@ -32,6 +31,9 @@ extern NSString *const kClientModeLockdown;
extern NSString *const kCleanSync;
extern NSString *const kWhitelistRegex;
extern NSString *const kBlacklistRegex;
extern NSString *const kBinaryRuleCount;
extern NSString *const kCertificateRuleCount;
extern NSString *const kFCMToken;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -49,10 +51,11 @@ extern NSString *const kDecisionBlockBinary;
extern NSString *const kDecisionBlockCertificate;
extern NSString *const kDecisionBlockScope;
extern NSString *const kDecisionUnknown;
extern NSString *const kDecisionRelatedBinary;
extern NSString *const kDecisionBundleBinary;
extern NSString *const kLoggedInUsers;
extern NSString *const kCurrentSessions;
extern NSString *const kFileBundleID;
extern NSString *const kFileBundlePath;
extern NSString *const kFileBundleName;
extern NSString *const kFileBundleVersion;
extern NSString *const kFileBundleShortVersionString;
@@ -70,6 +73,7 @@ extern NSString *const kQuarantineDataURL;
extern NSString *const kQuarantineRefererURL;
extern NSString *const kQuarantineTimestamp;
extern NSString *const kQuarantineAgentBundleID;
extern NSString *const kEventUploadBundleBinaries;
extern NSString *const kLogUploadField;
@@ -87,3 +91,8 @@ extern NSString *const kRuleCustomMsg;
extern NSString *const kCursor;
extern NSString *const kBackoffInterval;
extern NSString *const kFullSync;
extern NSString *const kRuleSync;
extern NSString *const kConfigSync;
extern NSString *const kLogSync;

View File

@@ -14,10 +14,7 @@
#import "SNTCommandSyncConstants.h"
NSString *const kURLPreflight = @"preflight/";
NSString *const kURLEventUpload = @"eventupload/";
NSString *const kURLRuleDownload = @"ruledownload/";
NSString *const kURLPostflight = @"postflight/";
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
NSString *const kSerialNumber = @"serial_num";
NSString *const kHostname = @"hostname";
@@ -34,6 +31,9 @@ NSString *const kClientModeLockdown = @"LOCKDOWN";
NSString *const kCleanSync = @"clean_sync";
NSString *const kWhitelistRegex = @"whitelist_regex";
NSString *const kBlacklistRegex = @"blacklist_regex";
NSString *const kBinaryRuleCount = @"binary_rule_count";
NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kFCMToken = @"fcm_token";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -51,10 +51,11 @@ NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
NSString *const kDecisionUnknown = @"UNKNOWN";
NSString *const kDecisionRelatedBinary = @"RELATED_BINARY";
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
NSString *const kLoggedInUsers = @"logged_in_users";
NSString *const kCurrentSessions = @"current_sessions";
NSString *const kFileBundleID = @"file_bundle_id";
NSString *const kFileBundlePath = @"file_bundle_path";
NSString *const kFileBundleName = @"file_bundle_name";
NSString *const kFileBundleVersion = @"file_bundle_version";
NSString *const kFileBundleShortVersionString = @"file_bundle_version_string";
@@ -72,6 +73,7 @@ NSString *const kQuarantineDataURL = @"quarantine_data_url";
NSString *const kQuarantineRefererURL = @"quarantine_referer_url";
NSString *const kQuarantineTimestamp = @"quarantine_timestamp";
NSString *const kQuarantineAgentBundleID = @"quarantine_agent_bundle_id";
NSString *const kEventUploadBundleBinaries = @"event_upload_bundle_binaries";
NSString *const kLogUploadField = @"files";
@@ -89,3 +91,8 @@ NSString *const kRuleCustomMsg = @"custom_msg";
NSString *const kCursor = @"cursor";
NSString *const kBackoffInterval = @"backoff";
NSString *const kFullSync = @"full_sync";
NSString *const kRuleSync = @"rule_sync";
NSString *const kConfigSync = @"config_sync";
NSString *const kLogSync = @"log_sync";

View File

@@ -12,14 +12,12 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@import Foundation;
@interface SNTCommandSyncPreflight : NSObject
#import "SNTCommandSyncStage.h"
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
- (BOOL)uploadEvents:(NSArray *)events;
@end

View File

@@ -0,0 +1,139 @@
/// 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 "SNTCommandSyncEventUpload.h"
#include "SNTLogging.h"
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import "NSData+Zlib.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTFileInfo.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncEventUpload
- (NSURL *)stageURL {
NSString *stageName = [@"eventupload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
}
- (BOOL)sync {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
if (events.count) {
[self uploadEvents:events];
}
dispatch_semaphore_signal(sema);
}];
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)uploadEvents:(NSArray *)events {
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:events.count];
for (SNTStoredEvent *event in events) {
[uploadEvents addObject:[self dictionaryForEvent:event]];
if (event.idx) [eventIds addObject:event.idx];
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
}
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
if (!r) return NO;
LOGI(@"Uploaded %lu events", uploadEvents.count);
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
// See if there are any events remaining to upload
if (uploadEvents.count < events.count) {
NSRange nextEventsRange = NSMakeRange(uploadEvents.count, events.count - uploadEvents.count);
NSArray *nextEvents = [events subarrayWithRange:nextEventsRange];
return [self uploadEvents:nextEvents];
}
return YES;
}
- (NSDictionary *)dictionaryForEvent:(SNTStoredEvent *)event {
#define ADDKEY(dict, key, value) if (value) dict[key] = value
NSMutableDictionary *newEvent = [NSMutableDictionary dictionary];
ADDKEY(newEvent, kFileSHA256, event.fileSHA256);
ADDKEY(newEvent, kFilePath, [event.filePath stringByDeletingLastPathComponent]);
ADDKEY(newEvent, kFileName, [event.filePath lastPathComponent]);
ADDKEY(newEvent, kExecutingUser, event.executingUser);
ADDKEY(newEvent, kExecutionTime, @([event.occurrenceDate timeIntervalSince1970]));
ADDKEY(newEvent, kLoggedInUsers, event.loggedInUsers);
ADDKEY(newEvent, kCurrentSessions, event.currentSessions);
switch (event.decision) {
case SNTEventStateAllowUnknown: ADDKEY(newEvent, kDecision, kDecisionAllowUnknown); break;
case SNTEventStateAllowBinary: ADDKEY(newEvent, kDecision, kDecisionAllowBinary); break;
case SNTEventStateAllowCertificate:
ADDKEY(newEvent, kDecision, kDecisionAllowCertificate);
break;
case SNTEventStateAllowScope: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
case SNTEventStateBlockUnknown: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
case SNTEventStateBlockBinary: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
case SNTEventStateBlockCertificate:
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateBundleBinary: ADDKEY(newEvent, kDecision, kDecisionBundleBinary); break;
default: ADDKEY(newEvent, kDecision, kDecisionUnknown);
}
ADDKEY(newEvent, kFileBundleID, event.fileBundleID);
ADDKEY(newEvent, kFileBundlePath, event.fileBundlePath);
ADDKEY(newEvent, kFileBundleName, event.fileBundleName);
ADDKEY(newEvent, kFileBundleVersion, event.fileBundleVersion);
ADDKEY(newEvent, kFileBundleShortVersionString, event.fileBundleVersionString);
ADDKEY(newEvent, kPID, event.pid);
ADDKEY(newEvent, kPPID, event.ppid);
ADDKEY(newEvent, kParentName, event.parentName);
ADDKEY(newEvent, kQuarantineDataURL, event.quarantineDataURL);
ADDKEY(newEvent, kQuarantineRefererURL, event.quarantineRefererURL);
ADDKEY(newEvent, kQuarantineTimestamp, @([event.quarantineTimestamp timeIntervalSince1970]));
ADDKEY(newEvent, kQuarantineAgentBundleID, event.quarantineAgentBundleID);
NSMutableArray *signingChain = [NSMutableArray arrayWithCapacity:event.signingChain.count];
for (NSUInteger i = 0; i < event.signingChain.count; ++i) {
MOLCertificate *cert = [event.signingChain objectAtIndex:i];
NSMutableDictionary *certDict = [NSMutableDictionary dictionary];
ADDKEY(certDict, kCertSHA256, cert.SHA256);
ADDKEY(certDict, kCertCN, cert.commonName);
ADDKEY(certDict, kCertOrg, cert.orgName);
ADDKEY(certDict, kCertOU, cert.orgUnit);
ADDKEY(certDict, kCertValidFrom, @([cert.validFrom timeIntervalSince1970]));
ADDKEY(certDict, kCertValidUntil, @([cert.validUntil timeIntervalSince1970]));
[signingChain addObject:certDict];
}
newEvent[kSigningChain] = signingChain;
return newEvent;
#undef ADDKEY
}
@end

View File

@@ -12,6 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
#include "SantaPIDAndPPID.h"
@import Foundation;
OSDefineMetaClassAndStructors(SantaPIDAndPPID, OSObject);
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncLogUpload : SNTCommandSyncStage
@end

View File

@@ -15,49 +15,40 @@
#import "SNTCommandSyncLogUpload.h"
#import "NSData+Zlib.h"
#include "SNTCommonEnums.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTCommonEnums.h"
#import "SNTLogging.h"
@implementation SNTCommandSyncLogUpload
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = syncState.uploadLogURL;
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
NSString *boundary = @"----santa-sync-upload-boundary";
- (NSString *)stageName {
return @"logupload";
}
- (NSURL *)stageURL {
return self.syncState.uploadLogURL;
}
- (BOOL)sync {
NSMutableURLRequest *req = [self requestWithDictionary:nil];
NSString *boundary = @"----santa-sync-upload-boundary";
NSString *contentType =
[NSString stringWithFormat:@"multipart/form-data; charset=UTF-8; boundary=%@", boundary];
[req setValue:contentType forHTTPHeaderField:@"Content-Type"];
NSArray *logsToUpload = [self logsToUpload];
[req setHTTPBody:[self requestBodyWithLogs:logsToUpload andBoundary:boundary]];
// Upload the logs
[[session uploadTaskWithRequest:req
fromData:[self requestBodyWithLogs:logsToUpload andBoundary:boundary]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %ld %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
LOGD(@"%@", error);
handler(NO);
} else {
LOGI(@"Uploaded %lu logs", [logsToUpload count]);
handler(YES);
}
}] resume];
NSDictionary *d = [self performRequest:req];
if (!d) return NO;
LOGI(@"Uploaded %lu logs", logsToUpload.count);
return YES;
}
+ (NSData *)requestBodyWithLogs:(NSArray *)logsToUpload andBoundary:(NSString *)boundary {
- (NSData *)requestBodyWithLogs:(NSArray *)logsToUpload andBoundary:(NSString *)boundary {
// Prepare the body of the request, encoded as a multipart/form-data.
// Along the way, gzip the individual log files and append .gz to their filenames.
NSMutableData *reqBody = [[NSMutableData alloc] init];
@@ -80,7 +71,7 @@
return reqBody;
}
+ (NSArray *)logsToUpload {
- (NSArray *)logsToUpload {
// General logs
NSMutableArray *logsToUpload = [@[ @"/var/log/santa.log",
@"/var/log/system.log" ] mutableCopy];

View File

@@ -0,0 +1,55 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@import Foundation;
#import "SNTXPCSyncdInterface.h"
@class SNTXPCConnection;
///
/// Handles push notifications and periodic syncing with a sync server.
///
@interface SNTCommandSyncManager : NSObject<SNTSyncdXPC>
@property(readonly, nonatomic) BOOL daemon;
///
/// Use the designated initializer initWithDaemonConnection:isDaemon:
///
- (instancetype)init NS_UNAVAILABLE;
///
/// Designated initializer.
///
/// @param daemonConn A connection to santad.
/// @param daemon Set to YES if periodic syncing should occur.
/// Set to NO if a single sync should be performed. NO is default.
///
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn
isDaemon:(BOOL)daemon NS_DESIGNATED_INITIALIZER;
///
/// Perform a full sync immediately. Non-blocking.
/// If a full sync is already running new requests will be dropped.
///
- (void)fullSync;
///
/// Perform a full sync seconds from now. Non-blocking.
/// If a full sync is already running new requests will be dropped.
///
- (void)fullSyncSecondsFromNow:(uint64_t)seconds;
@end

View File

@@ -0,0 +1,463 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCommandSyncManager.h"
@import SystemConfiguration;
#import <MOLAuthenticatingURLSession.h>
#import <MOLFCMClient/MOLFCMClient.h>
#import "SNTConfigurator.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncState.h"
#import "SNTLogging.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCSyncdInterface.h"
// Syncing time constant
const uint64_t kFullSyncInterval = 600;
@interface SNTCommandSyncManager () {
SCNetworkReachabilityRef _reachability;
}
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
@property(nonatomic) NSCache *ruleSyncCache;
@property MOLFCMClient *FCMClient;
@property(nonatomic) SNTXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@property(nonatomic) BOOL reachable;
@end
// Called when the network state changes
static void reachabilityHandler(
SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info) {
// Ensure state changes are processed in order.
dispatch_async(dispatch_get_main_queue(), ^{
SNTCommandSyncManager *commandSyncManager = (__bridge SNTCommandSyncManager *)info;
// Only call the setter when there is a change. This will filter out the redundant calls to this
// callback whenever the network interface states change.
if (commandSyncManager.reachable != (flags & kSCNetworkReachabilityFlagsReachable)) {
commandSyncManager.reachable = (flags & kSCNetworkReachabilityFlagsReachable);
}
});
}
@implementation SNTCommandSyncManager
#pragma mark init
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
self = [super init];
if (self) {
_daemonConn = daemonConn;
_daemon = daemon;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer
secondsFromNow:[SNTConfigurator configurator].FCMFullSyncInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
[self unlockAction:kFullSync];
}];
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
dispatch_source_set_timer(self.ruleSyncTimer,
DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTCommandSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.ruleSyncCache = self.ruleSyncCache;
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
LOGE(@"Rule download failed");
}
self.targetedRuleSync = NO;
[self unlockAction:kRuleSync];
}];
_dispatchLock = [[NSCache alloc] init];
_ruleSyncCache = [[NSCache alloc] init];
}
return self;
}
#pragma mark SNTSyncdXPC protocol methods
- (void)postEventToSyncServer:(SNTStoredEvent *)event {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc]
initWithState:[self createSyncState]];
if (event && [p uploadEvents:@[event]]) {
LOGD(@"Event upload complete");
} else {
LOGE(@"Event upload failed");
}
}
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply((self.FCMClient.FCMToken != nil));
}
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
if ([self.FCMClient.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Continue with the current FCMToken");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
self.FCMClient = [[MOLFCMClient alloc] initWithFCMToken:syncState.FCMToken
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || [message isEqual:@{}]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.connectionErrorHandler = ^(NSError *error) {
STRONGIFY(self);
LOGE(@"FCM connection error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"%@", log);
};
[self.FCMClient connect];
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSData *messageData = [self extractMessageDataFrom:FCMmessage];
if (!messageData) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSError *error;
NSDictionary *actionMessage = [NSJSONSerialization JSONObjectWithData:messageData
options:0
error:&error];
if (!actionMessage) {
LOGD(@"Unable to parse push notification message value: %@", error);
return;
}
// Store the file name and hash in a cache. When the rule is actually added, use the cache
// to build a user notification.
NSString *fileHash = actionMessage[@"file_hash"];
NSString *fileName = actionMessage[@"file_name"];
if (fileName && fileHash) {
[self.ruleSyncCache setObject:fileName forKey:fileHash];
}
NSString *action = actionMessage[@"action"];
if (action) {
LOGD(@"Push notification action: %@ received", action);
} else {
LOGD(@"Push notification message contains no action");
}
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetMachineID = actionMessage[@"target_host_id"];
if (![targetMachineID isKindOfClass:[NSNull class]] &&
[targetMachineID.lowercaseString isEqualToString:machineID.lowercaseString]) {
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds =
arc4random_uniform((u_int32_t)[SNTConfigurator configurator].FCMGlobalRuleLeeway);
LOGD(@"Staggering rule download, %u second delay for this machine", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
[self fullSync];
} else if ([action isEqualToString:kLogSync]) {
[self fullSync];
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
- (NSData *)extractMessageDataFrom:(NSDictionary *)FCMmessage {
if (![FCMmessage[@"data"] isKindOfClass:[NSDictionary class]]) return nil;
if (![FCMmessage[@"data"][@"blob"] isKindOfClass:[NSString class]]) return nil;
return [FCMmessage[@"data"][@"blob"] dataUsingEncoding:NSUTF8StringEncoding];
}
#pragma mark sync timer control
- (void)fullSync {
[self fullSyncSecondsFromNow:0];
}
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kFullSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
return;
}
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)ruleSync {
[self ruleSyncSecondsFromNow:0];
}
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kRuleSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
return;
}
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
}
- (void)rescheduleTimerQueue:(dispatch_source_t)timerQueue secondsFromNow:(uint64_t)seconds {
uint64_t interval = seconds * NSEC_PER_SEC;
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
}
#pragma mark syncing chain
- (void)preflight {
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
// Clean up reachability if it was started for a non-network error
[self stopReachability];
// Start listening for push notifications with a full sync every kFullSyncFCMInterval or
// revert to full syncing every kFullSyncInterval.
if (syncState.daemon && syncState.FCMToken) {
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCMToken not provided. Sync every %llu min.", kFullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
}
if (syncState.uploadLogURL) {
return [self logUploadWithSyncState:syncState];
} else {
return [self eventUploadWithSyncState:syncState];
}
} else {
if (!syncState.daemon) {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
LOGE(@"Preflight failed, will try again once %@ is reachable",
[[SNTConfigurator configurator] syncBaseURL].absoluteString);
[self startReachability];
}
}
- (void)logUploadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
return [self eventUploadWithSyncState:syncState];
}
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
} else {
LOGE(@"Event upload failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
if (!syncState.daemon) exit(0);
} else {
LOGE(@"Postflight failed");
if (!syncState.daemon) exit(1);
}
}
#pragma mark internal helpers
- (dispatch_source_t)createSyncTimerWithBlock:(void (^)())block {
dispatch_source_t timerQueue = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_source_set_event_handler(timerQueue, block);
dispatch_resume(timerQueue);
return timerQueue;
}
- (SNTCommandSyncState *)createSyncState {
// Gather some data needed during some sync stages
SNTCommandSyncState *syncState = [[SNTCommandSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
if (syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (!syncState.daemon) exit(1);
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
syncState.machineID = config.machineID;
if (syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
if (!syncState.daemon) exit(1);
}
syncState.machineOwner = config.machineOwner;
if (syncState.machineOwner.length == 0) {
syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
[[self.daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
syncState.xsrfToken = token;
}];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateFile]) {
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
} else if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
return syncState;
}
- (void)lockAction:(NSString *)action {
[self.dispatchLock setObject:@YES forKey:action];
}
- (void)unlockAction:(NSString *)action {
[self.dispatchLock removeObjectForKey:action];
}
- (BOOL)checkLockAction:(NSString *)action {
return ([self.dispatchLock objectForKey:action] == nil);
}
#pragma mark reachability methods
- (void)setReachable:(BOOL)reachable {
_reachable = reachable;
if (reachable) {
[self stopReachability];
[self fullSync];
}
}
// Start listening for network state changes on a background thread
- (void)startReachability {
if (_reachability) return;
const char *nodename = [[SNTConfigurator configurator] syncBaseURL].host.UTF8String;
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, nodename);
SCNetworkReachabilityContext context = {
.info = (__bridge void *)self
};
if (SCNetworkReachabilitySetCallback(_reachability, reachabilityHandler, &context)) {
SCNetworkReachabilitySetDispatchQueue(
_reachability, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
} else {
[self stopReachability];
}
}
// Stop listening for network state changes
- (void)stopReachability {
if (_reachability) {
SCNetworkReachabilitySetDispatchQueue(_reachability, NULL);
if (_reachability) CFRelease(_reachability);
_reachability = NULL;
}
}
@end

View File

@@ -12,14 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@import Foundation;
@interface SNTCommandSyncLogUpload : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncPostflight : SNTCommandSyncStage
@end

View File

@@ -0,0 +1,82 @@
/// 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 "SNTCommandSyncPostflight.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncPostflight
- (NSURL *)stageURL {
NSString *stageName = [@"postflight" stringByAppendingFormat:@"/%@", self.syncState.machineID];
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
}
- (BOOL)sync {
NSDictionary *r = [self performRequest:[self requestWithDictionary:nil]];
dispatch_group_t group = dispatch_group_create();
void (^replyBlock)() = ^{
dispatch_group_leave(group);
};
// Set client mode if it changed
if (self.syncState.clientMode) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setClientMode:self.syncState.clientMode
reply:replyBlock];
}
// Update backoff interval
NSString *backoffInterval = r[kBackoffInterval];
if (backoffInterval) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue]
reply:replyBlock];
}
// Remove clean sync flag if we did a clean sync
if (self.syncState.cleanSync) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setSyncCleanRequired:NO reply:replyBlock];
}
// Update whitelist/blacklist regexes
if (self.syncState.whitelistRegex) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setWhitelistPathRegex:self.syncState.whitelistRegex
reply:replyBlock];
}
if (self.syncState.blacklistRegex) {
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setBlacklistPathRegex:self.syncState.blacklistRegex
reply:replyBlock];
}
// Update last sync success
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:replyBlock];
// Wait for dispatch group
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
return YES;
}
@end

View File

@@ -12,14 +12,9 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@import Foundation;
@interface SNTCommandSyncPostflight : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
#import "SNTCommandSyncStage.h"
@interface SNTCommandSyncPreflight : SNTCommandSyncStage
@end

View File

@@ -0,0 +1,105 @@
/// 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 "SNTCommandSyncPreflight.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTConfigurator.h"
#import "SNTSystemInfo.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncPreflight
- (NSURL *)stageURL {
NSString *stageName = [@"preflight" stringByAppendingFormat:@"/%@", self.syncState.machineID];
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
}
- (BOOL)sync {
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
requestDict[kSerialNumber] = [SNTSystemInfo serialNumber];
requestDict[kHostname] = [SNTSystemInfo longHostname];
requestDict[kOSVer] = [SNTSystemInfo osVersion];
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kPrimaryUser] = self.syncState.machineOwner;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
requestDict[kBinaryRuleCount] = @(binary);
requestDict[kCertificateRuleCount] = @(certificate);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
switch (cm) {
case SNTClientModeMonitor:
requestDict[kClientMode] = kClientModeMonitor; break;
case SNTClientModeLockdown:
requestDict[kClientMode] = kClientModeLockdown; break;
default: break;
}
dispatch_group_leave(group);
}];
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
// If user requested it or we've never had a successful sync, try from a clean slate.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] ||
[[SNTConfigurator configurator] syncCleanRequired]) {
LOGD(@"Clean sync requested by user");
requestDict[kRequestCleanSync] = @YES;
}
NSURLRequest *req = [self requestWithDictionary:requestDict];
NSDictionary *resp = [self performRequest:req];
if (!resp) return NO;
self.syncState.FCMToken = resp[kFCMToken];
self.syncState.eventBatchSize = [resp[kBatchSize] intValue] ?: 50;
self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]];
if ([resp[kClientMode] isEqual:kClientModeMonitor]) {
self.syncState.clientMode = SNTClientModeMonitor;
} else if ([resp[kClientMode] isEqual:kClientModeLockdown]) {
self.syncState.clientMode = SNTClientModeLockdown;
}
if ([resp[kWhitelistRegex] isKindOfClass:[NSString class]]) {
self.syncState.whitelistRegex = resp[kWhitelistRegex];
}
if ([resp[kBlacklistRegex] isKindOfClass:[NSString class]]) {
self.syncState.blacklistRegex = resp[kBlacklistRegex];
}
if ([resp[kCleanSync] boolValue]) {
LOGD(@"Clean sync requested by server");
self.syncState.cleanSync = YES;
}
return YES;
}
@end

View File

@@ -0,0 +1,20 @@
/// 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 "SNTCommandSyncStage.h"
@interface SNTCommandSyncRuleDownload : SNTCommandSyncStage
@end

View File

@@ -0,0 +1,130 @@
/// 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 "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTRule.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#include "SNTLogging.h"
@implementation SNTCommandSyncRuleDownload
- (NSURL *)stageURL {
NSString *stageName = [@"ruledownload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
}
- (BOOL)sync {
self.syncState.downloadedRules = [NSMutableArray array];
return [self ruleDownloadWithCursor:nil];
}
- (BOOL)ruleDownloadWithCursor:(NSString *)cursor {
NSDictionary *requestDict = (cursor ? @{kCursor : cursor} : @{});
NSDictionary *resp = [self performRequest:[self requestWithDictionary:requestDict]];
if (!resp) return NO;
for (NSDictionary *rule in resp[kRules]) {
SNTRule *r = [self ruleFromDictionary:rule];
if (r) [self.syncState.downloadedRules addObject:r];
}
if (resp[kCursor]) {
return [self ruleDownloadWithCursor:resp[kCursor]];
}
if (!self.syncState.downloadedRules.count) return YES;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block NSError *error;
[[self.daemonConn remoteObjectProxy] databaseRuleAddRules:self.syncState.downloadedRules
cleanSlate:self.syncState.cleanSync
reply:^(NSError *e) {
error = e;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
if (error) {
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
return NO;
}
sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] setRuleSyncLastSuccess:[NSDate date] reply:^{
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
if (self.syncState.targetedRuleSync) {
for (SNTRule *r in self.syncState.downloadedRules) {
NSString *fileName = [[self.syncState.ruleSyncCache objectForKey:r.shasum] copy];
[self.syncState.ruleSyncCache removeObjectForKey:r.shasum];
if (fileName) {
NSString *message = [NSString stringWithFormat:@"%@ can now be run", fileName];
[[self.daemonConn remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
}
}
}
return YES;
}
- (SNTRule *)ruleFromDictionary:(NSDictionary *)dict {
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
SNTRule *newRule = [[SNTRule alloc] init];
newRule.shasum = dict[kRuleSHA256];
if (newRule.shasum.length != 64) return nil;
NSString *policyString = dict[kRulePolicy];
if ([policyString isEqual:kRulePolicyWhitelist]) {
newRule.state = SNTRuleStateWhitelist;
} else if ([policyString isEqual:kRulePolicyBlacklist]) {
newRule.state = SNTRuleStateBlacklist;
} else if ([policyString isEqual:kRulePolicySilentBlacklist]) {
newRule.state = SNTRuleStateSilentBlacklist;
} else if ([policyString isEqual:kRulePolicyRemove]) {
newRule.state = SNTRuleStateRemove;
} else {
return nil;
}
NSString *ruleTypeString = dict[kRuleType];
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
newRule.type = SNTRuleTypeBinary;
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
newRule.type = SNTRuleTypeCertificate;
} else {
return nil;
}
NSString *customMsg = dict[kRuleCustomMsg];
if (customMsg.length) {
newRule.customMsg = customMsg;
}
return newRule;
}
@end

View File

@@ -0,0 +1,74 @@
/// 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;
@class SNTCommandSyncState;
@class SNTXPCConnection;
@interface SNTCommandSyncStage : NSObject
@property(readonly, nonnull) NSURLSession *urlSession;
@property(readonly, nonnull) SNTCommandSyncState *syncState;
@property(readonly, nonnull) SNTXPCConnection *daemonConn;
/**
Initialize this stage. Designated initializer.
@param syncState A holder for state used across requests
*/
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)state NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)init NS_UNAVAILABLE;
/**
Performs this sync stage.
@return YES if sync was successful.
*/
- (BOOL)sync;
/**
The URL for this stage.
@return The NSURL for this stage.
*/
- (nonnull NSURL *)stageURL;
#pragma mark Internal Helpers
/**
Creates an NSMutableURLRequest pointing at the URL for this stage and containing the JSON-encoded
data passed in as a dictionary.
@param dictionary The values to POST to the server.
*/
- (nullable NSMutableURLRequest *)requestWithDictionary:(nullable NSDictionary *)dictionary;
/**
Perform the passed in request and attempt to parse the response as JSON into a dictionary.
@param request The request to perform
@param timeout The number of seconds to allow the request to run before timing out.
@return A populated dictionary if the response data was JSON, an empty dictionary if not and nil
if the request failed for any reason.
*/
- (nullable NSDictionary *)performRequest:(nonnull NSURLRequest *)request
timeout:(NSTimeInterval)timeout;
/** Convenience version of performRequest:timeout: using a 30s timeout. */
- (nullable NSDictionary *)performRequest:(nonnull NSURLRequest *)request;
@end

View File

@@ -0,0 +1,196 @@
/// 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 "SNTCommandSyncStage.h"
#import "NSData+Zlib.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTLogging.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCConnection.h"
@interface SNTCommandSyncStage ()
@property(readwrite) NSURLSession *urlSession;
@property(readwrite) SNTCommandSyncState *syncState;
@property(readwrite) SNTXPCConnection *daemonConn;
@property BOOL xsrfFetched;
@end
@implementation SNTCommandSyncStage
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)syncState {
self = [super init];
if (self) {
_syncState = syncState;
_urlSession = syncState.session;
_daemonConn = syncState.daemonConn;
}
return self;
}
- (BOOL)sync {
[self doesNotRecognizeSelector:_cmd]; __builtin_unreachable();
}
- (NSString *)stageURL {
[self doesNotRecognizeSelector:_cmd]; __builtin_unreachable();
}
- (NSMutableURLRequest *)requestWithDictionary:(NSDictionary *)dictionary {
NSData *requestBody = [NSData data];
if (dictionary) {
NSError *error;
requestBody = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error];
if (error) {
LOGD(@"Failed to encode JSON request: %@", error);
return nil;
}
}
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[self stageURL]];
[req setHTTPMethod:@"POST"];
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[req setValue:self.syncState.xsrfToken forHTTPHeaderField:kXSRFToken];
NSData *compressed = [requestBody zlibCompressed];
if (compressed) {
requestBody = compressed;
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
}
[req setHTTPBody:requestBody];
return req;
}
- (NSDictionary *)performRequest:(NSURLRequest *)request timeout:(NSTimeInterval)timeout {
NSHTTPURLResponse *response;
NSError *error;
NSData *data = [self performRequest:request timeout:timeout response:&response error:&error];
// If the original request failed, attempt to get a new XSRF token and try again.
// Unfortunately some servers cause NSURLSession to return 'client cert required' or
// 'could not parse response' when a 403 occurs and SSL cert auth is enabled.
if ((response.statusCode == 403 ||
error.code == NSURLErrorClientCertificateRequired ||
error.code == NSURLErrorCannotParseResponse) &&
[self fetchXSRFToken]) {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setValue:self.syncState.xsrfToken forHTTPHeaderField:kXSRFToken];
return [self performRequest:mutableRequest timeout:timeout];
}
if (response.statusCode != 200) {
long code;
NSString *errStr;
if (response.statusCode > 0) {
code = response.statusCode;
errStr = [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode];
} else {
code = (long)error.code;
errStr = error.localizedDescription;
}
LOGE(@"HTTP Response: %ld %@", code, errStr);
return nil;
}
if (data.length == 0) return @{};
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[self stripXssi:data]
options:0
error:&error];
if (error) LOGD(@"Failed to decode JSON response: %@", error);
return dict ?: @{};
}
- (NSDictionary *)performRequest:(NSURLRequest *)request {
return [self performRequest:request timeout:30];
}
#pragma mark Internal Helpers
/**
Perform a data request and capture the returned data, response and error objects.
@param request, The request to perform
@param timeout, The number of seconds to wait before cancelling the request
@param response, Return the response details
@param error, Return the error details
@returns data, The HTTP body of the response
*/
- (NSData *)performRequest:(NSURLRequest *)request
timeout:(NSTimeInterval)timeout
response:(out NSHTTPURLResponse **)response
error:(out NSError **)error {
__block NSData *_data;
__block NSHTTPURLResponse *_response;
__block NSError *_error;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
_response = (NSHTTPURLResponse *)response;
}
_data = data;
_error = error;
dispatch_semaphore_signal(sema);
}];
[task resume];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timeout))) {
[task cancel];
}
if (response) *response = _response;
if (error) *error = _error;
return _data;
}
- (NSData *)stripXssi:(NSData *)data {
static const char xssi[3] = { ']', ')', '}' };
if (data.length < 3 || strncmp(data.bytes, xssi, 3)) return data;
return [data subdataWithRange:NSMakeRange(3, data.length - 3)];
}
- (BOOL)fetchXSRFToken {
BOOL success = NO;
if (!self.xsrfFetched) { // only fetch token once per session
self.xsrfFetched = YES;
NSString *stageName = [@"xsrf" stringByAppendingFormat:@"/%@", self.syncState.machineID];
NSURL *u = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:u];
[request setHTTPMethod:@"POST"];
NSHTTPURLResponse *response;
[self performRequest:request timeout:10 response:&response error:NULL];
if (response.statusCode == 200) {
NSDictionary *headers = [response allHeaderFields];
[[self.daemonConn remoteObjectProxy] setXsrfToken:headers[kXSRFToken] reply:^{}];
self.syncState.xsrfToken = headers[kXSRFToken];
LOGD(@"Retrieved new XSRF token");
success = YES;
} else {
LOGD(@"Failed to retrieve XSRF token");
}
};
return success;
}
@end

View File

@@ -0,0 +1,73 @@
/// 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 "SNTCommonEnums.h"
@class SNTXPCConnection;
/// An instance of this class is passed to each stage of the sync process for storing data
/// that might be needed in later stages.
@interface SNTCommandSyncState : NSObject
/// Configured session to use for requests.
@property NSURLSession *session;
/// Connection to the daemon control interface.
@property SNTXPCConnection *daemonConn;
/// The base API URL.
@property NSURL *syncBaseURL;
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// A FCM token to subscribe to push notifications.
@property(copy) NSString *FCMToken;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@property(copy) NSString *machineOwner;
/// Settings sent from server during preflight that are set during postflight.
@property SNTClientMode clientMode;
@property NSString *whitelistRegex;
@property NSString *blacklistRegex;
/// Clean sync flag, if True, all existing rules should be deleted before inserting any new rules.
@property BOOL cleanSync;
/// Batch size for uploading events.
@property NSUInteger eventBatchSize;
/// Log upload URL sent from server. If set, LogUpload phase needs to happen.
@property NSURL *uploadLogURL;
/// Array of bundle paths to find binaries for.
@property NSArray *bundleBinaryRequests;
/// Rules downloaded from server.
@property NSMutableArray *downloadedRules;
/// Returns YES if the santactl session is running as a daemon, returns NO otherwise.
@property BOOL daemon;
/// Returns YES if the session is targeted for this machine, returns NO otherwise.
@property BOOL targetedRuleSync;
/// Reference to the sync manager's ruleSyncCache. Used to lookup binary names for notifications.
@property(weak) NSCache *ruleSyncCache;
@end

View File

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

View File

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

View File

@@ -55,9 +55,12 @@ static NSMutableDictionary *registeredCommands;
+ (NSString *)helpForCommandWithName:(NSString *)commandName {
Class<SNTCommand> command = registeredCommands[commandName];
if (command) {
NSString *shortHelp = [command shortHelpText];
NSString *longHelp = [command longHelpText];
if (longHelp) {
return [NSString stringWithFormat:@"Help for '%@':\n%@", commandName, longHelp];
} else if (shortHelp) {
return [NSString stringWithFormat:@"Help for '%@':\n%@", commandName, shortHelp];
} else {
return @"This command does not have any help information.";
}
@@ -65,15 +68,16 @@ static NSMutableDictionary *registeredCommands;
return nil;
}
+ (SNTXPCConnection *)connectToDaemon {
+ (SNTXPCConnection *)connectToDaemonRequired:(BOOL)required {
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
if (required) {
daemonConn.invalidationHandler = ^{
printf("An error occurred communicating with the daemon, is it running?\n");
exit(1);
};
[daemonConn resume];
}
return daemonConn;
}
@@ -89,11 +93,7 @@ static NSMutableDictionary *registeredCommands;
exit(2);
}
SNTXPCConnection *daemonConn;
if ([command requiresDaemonConn]) {
daemonConn = [self connectToDaemon];
}
SNTXPCConnection *daemonConn = [self connectToDaemonRequired:[command requiresDaemonConn]];
[command runWithArguments:arguments daemonConnection:daemonConn];
// The command is responsible for quitting.

View File

@@ -1,180 +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 "SNTCommandController.h"
#include "SNTLogging.h"
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import "SNTFileInfo.h"
@interface SNTCommandFileInfo : NSObject<SNTCommand>
@end
@implementation SNTCommandFileInfo
REGISTER_COMMAND_NAME(@"fileinfo")
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return NO;
}
+ (NSString *)shortHelpText {
return @"Prints information about a file.";
}
+ (NSString *)longHelpText {
return (@"The details provided will be the same ones Santa uses to make a decision\n"
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
@"the type of file.");
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
NSString *filePath = [arguments firstObject];
if (!filePath) {
printf("Missing file path\n");
exit(1);
}
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
if (!fileInfo) {
printf("Invalid or empty file\n");
exit(1);
}
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
if (isatty(STDOUT_FILENO)) printf("Hashing...");
NSString *sha1, *sha256;
[fileInfo hashSHA1:&sha1 SHA256:&sha256];
if (isatty(STDOUT_FILENO)) printf("\r");
[self printKey:@"Path" value:fileInfo.path];
[self printKey:@"SHA-256" value:sha256];
[self printKey:@"SHA-1" value:sha1];
if (fileInfo.bundlePath) {
[self printKey:@"Bundle Name" value:fileInfo.bundleName];
[self printKey:@"Bundle Version" value:fileInfo.bundleVersion];
[self printKey:@"Bundle Version Str" value:fileInfo.bundleShortVersionString];
}
if (fileInfo.quarantineDataURL) {
[self printKey:@"Download Referer URL" value:fileInfo.quarantineRefererURL];
[self printKey:@"Download URL" value:fileInfo.quarantineDataURL];
[self printKey:@"Download Timestamp"
value:[dateFormatter stringFromDate:fileInfo.quarantineTimestamp]];
[self printKey:@"Download Agent" value:fileInfo.quarantineAgentBundleID];
}
NSArray *archs = [fileInfo architectures];
if (archs.count == 0) {
[self printKey:@"Type" value:[self humanReadableFileType:fileInfo]];
exit(0);
}
NSString *s = [NSString stringWithFormat:@"%@ (%@)",
[self humanReadableFileType:fileInfo],
[archs componentsJoinedByString:@", "]];
[self printKey:@"Type" value:s];
if ([fileInfo isMissingPageZero]) {
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
}
NSError *error;
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
if (!error) {
[self printKey:@"Code-signed" value:@"Yes"];
} else {
switch (error.code) {
case errSecCSUnsigned:
[self printKey:@"Code-signed" value:@"No"];
break;
case errSecCSSignatureFailed:
case errSecCSStaticCodeChanged:
case errSecCSSignatureNotVerifiable:
case errSecCSSignatureUnsupported:
[self printKey:@"Code-signed" value:@"Yes, but code/signatured changed/unverifiable"];
break;
case errSecCSResourceDirectoryFailed:
case errSecCSResourceNotSupported:
case errSecCSResourceRulesInvalid:
case errSecCSResourcesInvalid:
case errSecCSResourcesNotFound:
case errSecCSResourcesNotSealed:
[self printKey:@"Code-signed" value:@"Yes, but resources invalid"];
break;
case errSecCSReqFailed:
case errSecCSReqInvalid:
case errSecCSReqUnsupported:
[self printKey:@"Code-signed" value:@"Yes, but failed requirement validation"];
break;
case errSecCSInfoPlistFailed:
[self printKey:@"Code-signed" value:@"Yes, but can't validate as Info.plist is missing"];
break;
default: {
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
error.code];
[self printKey:@"Code-signed" value:val];
break;
}
}
}
if (csc.certificates) {
printf("Signing chain:\n");
[csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c,
unsigned long idx,
BOOL *stop) {
printf(" %2lu. %-20s: %s\n", idx + 1, "SHA-256", [c.SHA256 UTF8String]);
printf(" %-20s: %s\n", "SHA-1", [c.SHA1 UTF8String]);
printf(" %-20s: %s\n", "Common Name", [c.commonName UTF8String]);
printf(" %-20s: %s\n", "Organization", [c.orgName UTF8String]);
printf(" %-20s: %s\n", "Organizational Unit", [c.orgUnit UTF8String]);
printf(" %-20s: %s\n", "Valid From",
[[dateFormatter stringFromDate:c.validFrom] UTF8String]);
printf(" %-20s: %s\n", "Valid Until",
[[dateFormatter stringFromDate:c.validUntil] UTF8String]);
printf("\n");
}];
}
exit(0);
}
+ (void)printKey:(NSString *)key value:(NSString *)value {
if (!key || !value) return;
printf("%-21s: %s\n", [key UTF8String], [value UTF8String]);
}
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
if ([fi isScript]) return @"Script";
if ([fi isXARArchive]) return @"XAR Archive";
if ([fi isDylib]) return @"Dynamic Library";
if ([fi isKext]) return @"Kernel Extension";
if ([fi isFat]) return @"Fat Binary";
if ([fi isMachO]) return @"Thin Binary";
if ([fi isDMG]) return @"Disk Image";
return @"Unknown";
}
@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"
///

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