Compare commits

...

100 Commits

Author SHA1 Message Date
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
107 changed files with 4602 additions and 2545 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.DS_Store
Build
Dist
santa-*
Pods
Santa.xcodeproj/xcuserdata
Santa.xcodeproj/project.xcworkspace

View File

@@ -5,8 +5,8 @@ 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

42
Podfile
View File

@@ -7,34 +7,42 @@ 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
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,28 @@
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 (1.6):
- MOLCertificate (~> 1.3)
- OCMock (3.2.2)
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- OCMock (3.3)
DEPENDENCIES:
- FMDB
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- OCMock
SPEC CHECKSUMS:
FMDB: c1968bab3ab0aed38f66cb778ae1e7fa9a652b6e
MOLCertificate: a776221906b5a46dd1bd749d0682bef3ee68c1f5
MOLCodesignChecker: 34e60cc6beadabfb4762b6e5087e12837774f85f
OCMock: 18c9b7e67d4c2770e95bb77a9cc1ae0c91fe3835
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
COCOAPODS: 0.39.0
PODFILE CHECKSUM: bc456d69693ca262c781dbbde40529a9474b84b5
COCOAPODS: 1.0.1

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
@@ -136,7 +136,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,7 +1,6 @@
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'
@@ -56,7 +55,6 @@ desc "Clean"
task :clean => :init do
puts "Cleaning"
FileUtils.rm_rf(OUTPUT_PATH)
FileUtils.rm_rf(DIST_PATH)
end
# Build
@@ -111,24 +109,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,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<development version="6300" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
@@ -424,7 +424,7 @@ DQ
<connections>
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
</connections>
<point key="canvasLocation" x="112.5" y="302.5"/>
<point key="canvasLocation" x="302.5" y="304.5"/>
</window>
</objects>
<resources>

View File

@@ -14,7 +14,7 @@
/**
An NSTextField subclass that provides an accessiblity label equal to:
(self.toolTip + self.stringValue) where available. It also sets the
(self.toolTip + self.stringValue) where available. It also sets the
accessibilityRoleDescription to "label".
*/
@interface SNTAccessibleTextField : NSTextField

View File

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

View File

@@ -17,6 +17,7 @@
#import <SecurityInterface/SFCertificatePanel.h>
#import "MOLCertificate.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTFileInfo.h"
#import "SNTMessageWindow.h"
@@ -111,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
@@ -131,7 +123,7 @@
if (![key isEqualToString:@"event"]) {
return [NSSet setWithObject:@"event"];
} else {
return nil;
return [NSSet set];
}
}
@@ -150,39 +142,8 @@
}
- (NSAttributedString *)attributedCustomMessage {
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 (self.customMessage.length) {
message = self.customMessage;
} else if (self.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];
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

@@ -14,6 +14,8 @@
#import "SNTNotificationManager.h"
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@@ -54,6 +56,7 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
- (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 {
@@ -64,6 +67,30 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
#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.
NSPredicate *predicate =

View File

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

View File

@@ -0,0 +1,128 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTBlockMessage.h"
#import "SNTConfigurator.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@implementation SNTBlockMessage
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
customMessage:(NSString *)customMessage {
NSString *htmlHeader = @"<html><head><style>"
@"body {"
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
@" font-size: 13px;"
@" color: #666;"
@" text-align: center;"
@"}"
@"</style></head><body>";
NSString *htmlFooter = @"</body></html>";
NSString *message;
if (customMessage.length) {
message = customMessage;
} else if (event.decision == SNTEventStateBlockUnknown) {
message = [[SNTConfigurator configurator] unknownBlockMessage];
if (!message) {
message = @"The following application has been blocked from executing<br />"
@"because its trustworthiness cannot be determined.";
}
} else {
message = [[SNTConfigurator configurator] bannedBlockMessage];
if (!message) {
message = @"The following application has been blocked from executing<br />"
@"because it has been deemed malicious.";
}
}
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
#ifdef NSAppKitVersionNumber10_0
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
#else
NSString *strippedHTML = [self stringFromHTML:fullHTML];
if (!strippedHTML) {
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
}
return [[NSAttributedString alloc] initWithString:strippedHTML];
#endif
}
+ (NSString *)stringFromHTML:(NSString *)html {
NSError *error;
NSXMLDocument *xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
if (!xml && error.code == NSXMLParserEmptyDocumentError) {
html = [NSString stringWithFormat:@"<html><body>%@</body></html>", html];
xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
if (!xml) return html;
}
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
// replace <br> elements with a newline.
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
@"<xsl:output method='text'/>"
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
@"<xsl:template match='style'/>"
@"</xsl:stylesheet>";
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
if (error || ![data isKindOfClass:[NSData class]]) {
return html;
}
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *formatStr;
if (config.eventDetailBundleURL && event.fileBundleID) {
formatStr = config.eventDetailBundleURL;
} 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];
}
if (event.fileBundleID) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
withString:event.fileBundleID];
}
if (event.fileBundleVersionString) {
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
withString:event.fileBundleVersionString];
}
return [NSURL URLWithString:formatStr];
}
@end

View File

@@ -53,7 +53,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockCertificate = 7,
SNTEventStateBlockScope = 8,
SNTEventStateRelatedBinary = 9,
SNTEventStateBundleBinary = 9,
};
typedef NS_ENUM(NSInteger, SNTRuleTableError) {

View File

@@ -75,18 +75,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 +112,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
///

View File

@@ -44,9 +44,12 @@ 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";
@@ -192,6 +195,10 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kEventDetailURLKey];
}
- (NSString *)eventDetailBundleURL {
return self.configData[kEventDetailBundleURLKey];
}
- (NSString *)eventDetailText {
return self.configData[kEventDetailTextKey];
}
@@ -204,6 +211,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) {
@@ -303,7 +318,8 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
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;
}
@@ -313,7 +329,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

@@ -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]];
}
}
@@ -199,18 +224,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 (magic && strncmp("koly", magic, 4) == 0);
return (magic && memcmp("koly", magic, 4) == 0);
}
#pragma mark Page Zero
@@ -230,7 +255,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 +298,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 +336,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 {
@@ -444,7 +471,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 +485,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;

View File

@@ -14,7 +14,7 @@
///
/// 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 +24,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(strong) 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,57 @@
[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;
while ((fd = open([self.filePath fileSystemRepresentation], 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);
close(fd);
}
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);
});
const char *filePathCString = [weakSelf.filePath fileSystemRepresentation];
while ((fd = open(filePathCString, O_EVTONLY)) < 0) {
usleep(1000);
}
dispatch_source_set_registration_handler(self.source, ^{
STRONGIFY(self);
self.handler(0);
});
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);
dispatch_source_set_cancel_handler(self.source, ^{
STRONGIFY(self);
int fd = (int)dispatch_source_get_handle(self.source);
if (fd > 0) close(fd);
});
weakSelf.eventHandler();
};
dispatch_async(queue, self.internalCancelHandler);
dispatch_resume(self.source);
});
}
- (void)stopWatchingFile {
if (!self.monitoringSource) return;
if (!self.source) 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, ^{
int fd = (int)dispatch_source_get_handle(self.source);
dispatch_source_set_event_handler_f(self.source, NULL);
dispatch_source_set_cancel_handler(self.source, ^{
close(fd);
});
dispatch_source_cancel(self.monitoringSource);
self.monitoringSource = nil;
dispatch_source_cancel(self.source);
self.source = nil;
}
@end

View File

@@ -35,6 +35,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,6 +21,8 @@
#ifdef KERNEL
#include <IOKit/IOLib.h>
#ifdef DEBUG
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
#else // DEBUG

View File

@@ -52,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) {
@@ -86,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

@@ -35,10 +35,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.
///

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");
@@ -64,6 +65,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");

View File

@@ -40,12 +40,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>
@@ -79,7 +79,7 @@
/**
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.
*/
@@ -97,7 +97,7 @@
/**
A proxy to the object at the other end of the connection. (client)
@note If the connection to the server failed, this will be nil, so you can safely send messages
and rely on the invalidationHandler for handling the failure.
*/

View File

@@ -30,7 +30,7 @@
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 exporteed interface and
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>
@@ -45,10 +45,10 @@
@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;
@@ -62,7 +62,8 @@
self = [super init];
if (self) {
_listenerObject = listener;
_acceptedConnections = [NSMutableArray array];
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
@@ -76,6 +77,8 @@
if (self) {
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
if (!_currentConnection) return nil;
_validationInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
}
return self;
}
@@ -86,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;
}
@@ -107,8 +112,7 @@
// 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 =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
self.currentConnection.remoteObjectInterface = self.validationInterface;
self.currentConnection.interruptionHandler = self.invalidationHandler;
self.currentConnection.invalidationHandler = self.invalidationHandler;
[self.currentConnection resume];
@@ -139,7 +143,6 @@
// 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);
@@ -147,25 +150,17 @@
STRONGIFY(self)
STRONGIFY(connection);
[connection suspend];
[self.acceptedConnections addObject:connection];
WEAKIFY(connection);
connection.invalidationHandler = connection.interruptionHandler = ^{
STRONGIFY(connection);
[self.acceptedConnections removeObject:connection];
if (self.invalidationHandler) self.invalidationHandler();
};
connection.exportedInterface = self.exportedInterface;
connection.exportedObject = self.exportedObject;
[connection resume];
// The connection is now established.
if (self.acceptedHandler) self.acceptedHandler();
};
connection.exportedInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
connection.exportedInterface = self.validationInterface;
connection.exportedObject = ci;
[connection resume];
@@ -173,7 +168,8 @@
}
- (id)remoteObjectProxy {
if (self.currentConnection.remoteObjectInterface) {
if (self.currentConnection.remoteObjectInterface &&
self.currentConnection.remoteObjectInterface != self.validationInterface) {
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
[self.currentConnection invalidate];
}];
@@ -188,10 +184,6 @@
[self.currentConnection invalidate];
self.currentConnection = nil;
} else if (self.listenerObject) {
for (NSXPCConnection *conn in self.acceptedConnections) {
[conn invalidate];
}
[self.acceptedConnections removeAllObjects];
[self.listenerObject invalidate];
}
}

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "SNTCommonEnums.h"
#import "SNTKernelCommon.h"
@class SNTRule;
@class SNTStoredEvent;
@@ -28,6 +29,7 @@
///
- (void)cacheCount:(void (^)(int64_t))reply;
- (void)flushCache:(void (^)(BOOL))reply;
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply;
///
/// Database ops
@@ -41,6 +43,8 @@
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
///
/// Config ops
@@ -48,6 +52,8 @@
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
- (void)clientMode:(void (^)(SNTClientMode))reply;
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;

View File

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

View File

@@ -0,0 +1,273 @@
/// 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"
#define likely(x) __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)
#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) {
unlock(bucket);
return entry->value;
}
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
*/
T zero_ = {};
/**
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,31 +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.
#include "SantaCachedDecision.h"
OSDefineMetaClassAndStructors(SantaCachedDecision, OSObject);
uint64_t SantaCachedDecision::getMicrosecs() const {
return microsecs_;
}
santa_action_t SantaCachedDecision::getAction() const {
return action_;
}
void SantaCachedDecision::setAction(
const santa_action_t action, const uint64_t microsecs) {
action_ = action;
microsecs_ = microsecs;
}

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,23 +24,20 @@ 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;
@@ -49,15 +46,8 @@ bool SantaDecisionManager::init() {
}
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 +76,6 @@ void SantaDecisionManager::free() {
OSSafeReleaseNULL(decision_dataqueue_);
OSSafeReleaseNULL(log_dataqueue_);
OSSafeReleaseNULL(cached_decisions_);
OSSafeReleaseNULL(vnode_pid_map_);
super::free();
}
@@ -97,12 +85,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;
}
@@ -136,6 +124,7 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
}
bool SantaDecisionManager::ClientConnected() const {
if (client_pid_ <= 0) return false;
auto p = proc_find(client_pid_);
auto is_exiting = false;
if (p) {
@@ -168,14 +157,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,113 +193,67 @@ 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 (decision == ACTION_REQUEST_BINARY) {
auto 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_);
auto pending = OSDynamicCast(
SantaCachedDecision, cached_decisions_->getObject(identifier));
if (pending) {
pending->setAction(decision, microsecs);
}
lck_rw_unlock_exclusive(cached_decisions_lock_);
// 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);
}
}
void SantaDecisionManager::CacheCheck(const char *identifier) {
lck_rw_lock_shared(cached_decisions_lock_);
auto 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);
}
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 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)) {
auto 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 SantaDecisionManager::GetFromDaemon(santa_message_t *message, uint64_t identifier) {
auto return_action = ACTION_UNSET;
// 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)) {
@@ -324,13 +265,13 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
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);
return_action = GetFromCache(identifier);
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
} while (!RESPONSE_VALID(return_action) && ClientConnected());
@@ -338,7 +279,7 @@ 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;
}
@@ -353,7 +294,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
// Check to see if item is in cache
auto return_action = GetFromCache(vnode_id_str);
auto return_action = GetFromCache(vnode_id);
// If item was in cache return it.
if (RESPONSE_VALID(return_action)) return return_action;
@@ -365,12 +306,12 @@ santa_action_t SantaDecisionManager::FetchDecision(
path[0] = '\0';
}
auto 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));
return_action = GetFromDaemon(message, vnode_id_str);
return_action = GetFromDaemon(message, vnode_id);
delete message;
return return_action;
}
@@ -433,7 +374,7 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
// 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;
}
@@ -441,13 +382,11 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
case ACTION_RESPOND_ALLOW: {
auto proc = vfs_context_proc(ctx);
if (proc) {
auto 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;
}
@@ -473,25 +412,18 @@ void SantaDecisionManager::FileOpCallback(
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) {
auto 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_);
auto 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;
@@ -501,8 +433,9 @@ void SantaDecisionManager::FileOpCallback(
// Filter out modifications to locations that are definitely
// not useful or made by santad.
if (proc_selfpid() != client_pid_ &&
!strprefix(path, "/.") && !strprefix(path, "/dev")) {
auto message = NewMessage();
!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));
@@ -523,7 +456,9 @@ void SantaDecisionManager::FileOpCallback(
case KAUTH_FILEOP_DELETE:
message->action = ACTION_NOTIFY_DELETE;
break;
default: delete message; return;
default:
delete message;
return;
}
PostToLogQueue(message);
@@ -544,9 +479,6 @@ extern "C" int fileop_scope_callback(
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);
@@ -573,20 +505,31 @@ 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) {
if (action & KAUTH_VNODE_ACCESS || idata == nullptr) {
return KAUTH_RESULT_DEFER;
}
auto sdm = OSDynamicCast(
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
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;
if (action & KAUTH_VNODE_EXECUTE) {
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;
} else if (action & KAUTH_VNODE_WRITE_DATA) {
vnode_t vp = reinterpret_cast<vnode_t>(arg1);
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
sdm->IncrementListenerInvocations();
char path[MAXPATHLEN];
int pathlen = MAXPATHLEN;
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,178 +39,166 @@ 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.
///
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
///
/// The maximum number of milliseconds a cached allow message should be
/// considered valid.
///
static const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
///
/// While waiting for a response from the daemon, this is the number of
/// milliseconds to sleep for before checking the cache for a response.
///
/**
While waiting for a response from the daemon, this is the number of
milliseconds to sleep for before checking the cache for a response.
*/
static const uint32_t kRequestLoopSleepMilliseconds = 10;
///
/// Maximum number of entries in the in-kernel cache.
///
/// The maximum number of milliseconds a cached deny message should be considered valid.
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
/// Maximum number of entries in the in-kernel cache.
static const uint32_t kMaxCacheSize = 10000;
///
/// Maximum number of PostToDecisionQueue failures to allow.
///
/// Maximum number of PostToDecisionQueue failures to allow.
static const uint32_t kMaxDecisionQueueFailures = 10;
///
/// The maximum number of messages can be kept in
/// the decision data queue at any time.
///
/// The maximum number of messages can be kept in the decision data queue at any time.
static const uint32_t kMaxDecisionQueueEvents = 512;
///
/// The maximum number of messages can be kept
/// in the logging data queue at any time.
///
/// The maximum number of messages can be kept in the logging data queue at any time.
static const uint32_t kMaxLogQueueEvents = 1024;
/// Fetches a response from the cache, first checking to see if the
/// entry has expired.
santa_action_t GetFromCache(const char *identifier);
/**
Fetches a response from the daemon. Handles both daemon death
and failure to post messages to the daemon.
/// 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);
@param message The message to send to the daemon
@param identifier The vnode ID string for this request
@return santa_action_t The response for this request
*/
santa_action_t GetFromDaemon(santa_message_t *message, uint64_t identifier);
///
/// Fetches an execution decision for a file, first using the cache and then
/// by sending a message to the daemon and waiting until a response arrives.
/// If a daemon isn't connected, will allow execution and cache, logging
/// the path to the executed file.
///
/// @param cred The credential for this request.
/// @param vp The Vnode for this request.
/// @param vnode_id The ID for this vnode.
/// @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);
/**
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.
///
/// Posts the requested message to the decision data queue.
///
/// @param message The message to send
/// @return bool true if sending was successful.
///
@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);
/**
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.
///
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx,
const vnode_t vp) {
/**
Fetches the vnode_id for a given vnode.
@param ctx The VFS context to use.
@param vp The Vnode to get the ID for
@return uint64_t The Vnode ID as a 64-bit unsigned int.
*/
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) {
struct vnode_attr vap;
VATTR_INIT(&vap);
VATTR_WANTED(&vap, va_fsid);
@@ -220,21 +207,35 @@ class SantaDecisionManager : public OSObject {
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
}
///
/// Creates a new santa_message_t with some fields pre-filled.
///
static inline santa_message_t *NewMessage() {
/**
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_getuid();
message->gid = kauth_getgid();
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
///
/**
Returns the current system uptime in microseconds
*/
static inline uint64_t GetCurrentUptime() {
clock_sec_t sec;
clock_usec_t usec;
@@ -243,17 +244,15 @@ class SantaDecisionManager : public OSObject {
}
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_;
@@ -268,32 +267,44 @@ class SantaDecisionManager : public OSObject {
kauth_listener_t fileop_listener_;
};
///
/// 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

@@ -130,10 +130,7 @@ IOReturn SantaDriverClient::static_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);
decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ALLOW);
return kIOReturnSuccess;
}
@@ -149,10 +146,7 @@ IOReturn SantaDriverClient::static_allow_binary(
}
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);
decisionManager->AddToCache(vnode_id, ACTION_RESPOND_DENY);
return kIOReturnSuccess;
}
@@ -193,6 +187,20 @@ IOReturn SantaDriverClient::static_cache_count(
return target->cache_count(&(arguments->scalarOutput[0]));
}
IOReturn SantaDriverClient::check_cache(uint64_t vnode_id, uint64_t *output) {
*output = decisionManager->GetFromCache(vnode_id);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::static_check_cache(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments) {
if (!target) return kIOReturnBadArgument;
return target->check_cache(reinterpret_cast<uint64_t>(*arguments->scalarInput),
&(arguments->scalarOutput[0]));
}
#pragma mark Method Resolution
IOReturn SantaDriverClient::externalMethod(
@@ -203,7 +211,7 @@ 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] = {
static IOExternalMethodDispatch sMethods[kSantaUserClientNMethods] = {
{
reinterpret_cast<IOExternalMethodAction>(&SantaDriverClient::static_open),
0, // input scalar
@@ -242,6 +250,14 @@ IOReturn SantaDriverClient::externalMethod(
0,
1,
0
},
{
reinterpret_cast<IOExternalMethodAction>(
&SantaDriverClient::static_check_cache),
1,
0,
1,
0
}
};

View File

@@ -111,6 +111,14 @@ class com_google_SantaDriverClient : public IOUserClient {
void *reference,
IOExternalMethodArguments *arguments);
/// The daemon calls this to find out the status of a vnode_id in the cache.
/// Output will be a santa_action_t.
IOReturn check_cache(uint64_t vnode_id, uint64_t *output);
static IOReturn static_check_cache(
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
private:
com_google_SantaDriver *myProvider;
SantaDecisionManager *decisionManager;

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

@@ -19,6 +19,9 @@
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import "SNTFileInfo.h"
#import "SNTRule.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@interface SNTCommandFileInfo : NSObject<SNTCommand>
@end
@@ -100,11 +103,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
}
// Code signature state
NSError *error;
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
if (!error) {
[self printKey:@"Code-signed" value:@"Yes"];
} else {
if (error) {
switch (error.code) {
case errSecCSUnsigned:
[self printKey:@"Code-signed" value:@"No"];
@@ -113,7 +115,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
case errSecCSStaticCodeChanged:
case errSecCSSignatureNotVerifiable:
case errSecCSSignatureUnsupported:
[self printKey:@"Code-signed" value:@"Yes, but code/signatured changed/unverifiable"];
[self printKey:@"Code-signed" value:@"Yes, but code/signature changed/unverifiable"];
break;
case errSecCSResourceDirectoryFailed:
case errSecCSResourceNotSupported:
@@ -133,12 +135,63 @@ REGISTER_COMMAND_NAME(@"fileinfo")
break;
default: {
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
error.code];
error.code];
[self printKey:@"Code-signed" value:val];
break;
}
}
} else if (csc.signatureFlags & kSecCodeSignatureAdhoc) {
[self printKey:@"Code-signed" value:@"Yes, but ad-hoc"];
} else {
[self printKey:@"Code-signed" value:@"Yes"];
}
// Binary rule state
__block SNTRule *r;
dispatch_group_t group = dispatch_group_create();
[daemonConn resume];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseBinaryRuleForSHA256:sha256 reply:^(SNTRule *rule) {
if (rule) r = rule;
dispatch_group_leave(group);
}];
NSString *leafCertSHA = [[csc.certificates firstObject] SHA256];
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] databaseCertificateRuleForSHA256:leafCertSHA
reply:^(SNTRule *rule) {
if (!r && rule) r = rule;
dispatch_group_leave(group);
}];
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self printKey:@"Rule" value:@"Cannot communicate with daemon"];
} else {
NSString *output;
switch (r.state) {
case SNTRuleStateWhitelist:
output = @"Whitelisted";
if (isatty(STDOUT_FILENO)) {
output = @"\033[32mWhitelisted\033[0m";
}
[self printKey:@"Rule" value:output];
break;
case SNTRuleStateBlacklist:
case SNTRuleStateSilentBlacklist:
output = @"Blacklisted";
if (isatty(STDOUT_FILENO)) {
output = @"\033[31mBlacklisted\033[0m";
}
[self printKey:@"Rule" value:output];
break;
default:
output = @"None";
if (isatty(STDOUT_FILENO)) {
output = @"\033[33mNone\033[0m";
}
[self printKey:@"Rule" value:output];
}
}
// Signing chain
if (csc.certificates.count) {
printf("Signing chain:\n");
@@ -168,11 +221,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
if ([fi isScript]) return @"Script";
if ([fi isXARArchive]) return @"XAR Archive";
if ([fi isExecutable]) return @"Executable";
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 isXARArchive]) return @"XAR Archive";
if ([fi isDMG]) return @"Disk Image";
return @"Unknown";
}

View File

@@ -23,7 +23,9 @@
@implementation SNTCommandFlushCache
#ifdef DEBUG
REGISTER_COMMAND_NAME(@"flushcache")
#endif
+ (BOOL)requiresRoot {
return YES;

View File

@@ -14,7 +14,8 @@
#import "SNTCommandController.h"
#import "SNTAuthenticatingURLSession.h"
#import <MOLAuthenticatingURLSession.h>
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
@@ -28,8 +29,6 @@
#import "SNTXPCControlInterface.h"
@interface SNTCommandSync : NSObject<SNTCommand>
@property NSURLSession *session;
@property SNTXPCConnection *daemonConn;
@property SNTCommandSyncState *syncState;
@end
@@ -50,50 +49,73 @@ REGISTER_COMMAND_NAME(@"sync")
}
+ (NSString *)longHelpText {
return nil;
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 {
SNTConfigurator *config = [SNTConfigurator configurator];
// Ensure we have no privileges
if (!DropRootPrivileges()) {
LOGE(@"Failed to drop root privileges. Exiting.");
exit(1);
}
SNTConfigurator *config = [SNTConfigurator configurator];
SNTCommandSync *s = [[self alloc] init];
// Gather some data needed during some sync stages
s.syncState = [[SNTCommandSyncState alloc] init];
s.syncState.syncBaseURL = config.syncBaseURL;
if (s.syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
exit(1);
} else if (![s.syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
s.syncState.machineID = config.machineID;
if (s.syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
exit(1);
}
s.syncState.machineOwner = config.machineOwner;
if (s.syncState.machineOwner.length == 0) {
s.syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
[[daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
s.syncState.xsrfToken = token;
}];
// 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]];
SNTCommandSync *s = [[self alloc] init];
SNTAuthenticatingURLSession *authURLSession = [[SNTAuthenticatingURLSession alloc] init];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
NSError *error = nil;
NSData *rootsData = [NSData dataWithContentsOfFile:[config syncServerAuthRootsFile]
options:0
error:&error];
authURLSession.serverRootsPemData = rootsData;
if (!rootsData) {
LOGE(@"Couldn't open server root certificate file %@ with error: %@.",
[config syncServerAuthRootsFile],
[error localizedDescription]);
exit(1);
}
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
@@ -108,135 +130,108 @@ REGISTER_COMMAND_NAME(@"sync")
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
s.session = [authURLSession session];
s.daemonConn = daemonConn;
// Gather some data needed during some sync stages
s.syncState = [[SNTCommandSyncState alloc] init];
s.syncState.syncBaseURL = config.syncBaseURL;
if (!s.syncState.syncBaseURL) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
exit(1);
} else if (![s.syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
s.syncState.machineID = config.machineID;
if ([s.syncState.machineID length] == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
exit(1);
}
s.syncState.machineOwner = config.machineOwner;
if ([s.syncState.machineOwner length] == 0) {
s.syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
s.syncState.session = [authURLSession session];
s.syncState.daemonConn = daemonConn;
if ([arguments containsObject:@"singleevent"]) {
NSUInteger idx = [arguments indexOfObject:@"singleevent"];
idx++;
NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1;
if (idx >= arguments.count) {
LOGI(@"singleevent takes an argument");
exit(1);
}
NSString *obj = arguments[idx];
if (obj.length != 64) {
LOGI(@"singleevent passed without SHA-256 as next argument");
exit(1);
}
[s eventUploadSingleEvent:obj];
return [s eventUploadSingleEvent:obj];
} else {
[s preflight];
return [s preflight];
}
}
- (void)preflight {
[SNTCommandSyncPreflight performSyncInSession:self.session
syncState:self.syncState
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
if (self.syncState.uploadLogURL) {
[self logUpload];
} else {
[self eventUpload];
}
} else {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
}];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
if (self.syncState.uploadLogURL) {
return [self logUpload];
} else {
return [self eventUpload];
}
} else {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
}
- (void)logUpload {
[SNTCommandSyncLogUpload performSyncInSession:self.session
syncState:self.syncState
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
} else {
LOGE(@"Log upload failed, continuing anyway");
}
[self eventUpload];
}];
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
return [self eventUpload];
}
- (void)eventUpload {
[SNTCommandSyncEventUpload performSyncInSession:self.session
syncState:self.syncState
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
[self ruleDownload];
} else {
LOGE(@"Event upload failed, aborting run");
exit(1);
}
}];
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownload];
} else {
LOGE(@"Event upload failed, aborting run");
exit(1);
}
}
- (void)eventUploadSingleEvent:(NSString *)sha256 {
[SNTCommandSyncEventUpload uploadSingleEventWithSHA256:sha256
session:self.session
syncState:self.syncState
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
exit(0);
} else {
LOGW(@"Event upload failed");
exit(1);
}
}];
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p syncSingleEventWithSHA256:sha256]) {
LOGD(@"Event upload complete");
exit(0);
} else {
LOGE(@"Event upload failed");
exit(1);
}
}
- (void)ruleDownload {
[SNTCommandSyncRuleDownload performSyncInSession:self.session
syncState:self.syncState
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
[self postflight];
} else {
LOGE(@"Rule download failed, aborting run");
exit(1);
}
}];
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
if (self.syncState.bundleBinaryRequests.count) {
return [self eventUploadBundleBinaries];
}
return [self postflight];
} else {
LOGE(@"Rule download failed, aborting run");
exit(1);
}
}
- (void)eventUploadBundleBinaries {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p syncBundleEvents]) {
LOGD(@"Event Upload bundle binaries complete");
} else {
LOGW(@"Event Upload bundle binary search failed");
}
return [self postflight];
}
- (void)postflight {
[SNTCommandSyncPostflight performSyncInSession:self.session
syncState:self.syncState
daemonConn:self.daemonConn
completionHandler:^(BOOL success) {
if (success) {
LOGI(@"Sync completed successfully");
exit(0);
} else {
LOGE(@"Postflight failed");
exit(1);
}
}];
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
exit(0);
} else {
LOGE(@"Postflight failed");
exit(1);
}
}
@end

View File

@@ -12,10 +12,7 @@
/// 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;
extern NSString *const kXSRFToken;
extern NSString *const kSerialNumber;
extern NSString *const kHostname;
@@ -32,6 +29,8 @@ 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 kEvents;
extern NSString *const kFileSHA256;
@@ -49,10 +48,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 +70,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;

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,8 @@ 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 kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -51,10 +50,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 +72,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";

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 "SNTCommandSyncStage.h"
@interface SNTCommandSyncPreflight : NSObject
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256;
- (BOOL)syncBundleEvents;
@end

View File

@@ -28,124 +28,73 @@
@implementation SNTCommandSyncEventUpload
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[kURLEventUpload stringByAppendingString:syncState.machineID]
relativeToURL:syncState.syncBaseURL];
[[daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
if ([events count] == 0) {
handler(YES);
} else {
[self uploadEventsFromArray:events
toURL:url
inSession:session
batchSize:syncState.eventBatchSize
daemonConn:daemonConn
completionHandler:handler];
}
}];
- (NSURL *)stageURL {
NSString *stageName = [@"eventupload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
}
+ (void)uploadSingleEventWithSHA256:(NSString *)SHA256
session:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[kURLEventUpload stringByAppendingString:syncState.machineID]
relativeToURL:syncState.syncBaseURL];
[[daemonConn remoteObjectProxy] databaseEventForSHA256:SHA256 reply:^(SNTStoredEvent *event) {
if (!event) {
handler(YES);
return;
- (BOOL)sync {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
if (events.count) {
[self uploadEvents:events];
}
[self uploadEventsFromArray:@[ event ]
toURL:url
inSession:session
batchSize:1
daemonConn:daemonConn
completionHandler:handler];
dispatch_semaphore_signal(sema);
}];
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
+ (void)uploadEventsFromArray:(NSArray *)events
toURL:(NSURL *)url
inSession:(NSURLSession *)session
batchSize:(NSUInteger)batchSize
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256 {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] databaseEventForSHA256:sha256 reply:^(SNTStoredEvent *e) {
if (e) {
[self uploadEvents:@[ e ]];
}
dispatch_semaphore_signal(sema);
}];
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)syncBundleEvents {
NSMutableArray *newEvents = [NSMutableArray array];
for (NSString *bundlePath in self.syncState.bundleBinaryRequests) {
[newEvents addObjectsFromArray:[self findRelatedBinaries:bundlePath]];
}
return [self uploadEvents:newEvents];
}
- (BOOL)uploadEvents:(NSArray *)events {
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
NSMutableArray *eventIds = [NSMutableArray arrayWithCapacity:events.count];
NSMutableDictionary *eventIds = [NSMutableDictionary dictionaryWithCapacity:events.count];
for (SNTStoredEvent *event in events) {
[uploadEvents addObject:[self dictionaryForEvent:event]];
[eventIds addObject:event.idx];
if (event.fileBundleID) {
NSArray *relatedBinaries = [self findRelatedBinaries:event];
[uploadEvents addObjectsFromArray:relatedBinaries];
}
if (eventIds.count >= batchSize) break;
eventIds[event.idx] = @YES;
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
}
NSDictionary *uploadReq = @{kEvents : uploadEvents};
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
if (!r) return NO;
NSData *requestBody;
@try {
requestBody = [NSJSONSerialization dataWithJSONObject:uploadReq options:0 error:nil];
} @catch (NSException *exception) {
LOGE(@"Failed to parse event(s) into JSON");
LOGD(@"Parsing error: %@", [exception reason]);
// Keep track of bundle search requests
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
LOGI(@"Uploaded %lu events", uploadEvents.count);
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allKeys]];
// 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];
}
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSData *compressed = [requestBody zlibCompressed];
if (compressed) {
requestBody = compressed;
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
}
[req setHTTPBody:requestBody];
[[session dataTaskWithRequest:req 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 events", eventIds.count);
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:eventIds];
NSArray *nextEvents = [events subarrayWithRange:NSMakeRange(eventIds.count,
events.count - eventIds.count)];
if (nextEvents.count == 0) {
handler(YES);
} else {
[self uploadEventsFromArray:nextEvents
toURL:url
inSession:session
batchSize:batchSize
daemonConn:daemonConn
completionHandler:handler];
}
}
}] resume];
return YES;
}
+ (NSDictionary *)dictionaryForEvent:(SNTStoredEvent *)event {
- (NSDictionary *)dictionaryForEvent:(SNTStoredEvent *)event {
#define ADDKEY(dict, key, value) if (value) dict[key] = value
NSMutableDictionary *newEvent = [NSMutableDictionary dictionary];
@@ -170,11 +119,12 @@
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
break;
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
case SNTEventStateRelatedBinary: ADDKEY(newEvent, kDecision, kDecisionRelatedBinary); 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);
@@ -208,26 +158,26 @@
#undef ADDKEY
}
+ (NSArray *)findRelatedBinaries:(SNTStoredEvent *)event {
// Find binaries within a bundle given the bundle's path
// Searches for 10 minutes, creating new events.
- (NSArray *)findRelatedBinaries:(NSString *)path {
SNTFileInfo *requestedPath = [[SNTFileInfo alloc] initWithPath:path];
// Prevent processing the same bundle twice.
static NSMutableDictionary *previouslyProcessedBundles;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
previouslyProcessedBundles = [NSMutableDictionary dictionary];
});
if (previouslyProcessedBundles[event.fileBundleID]) return nil;
previouslyProcessedBundles[event.fileBundleID] = @YES;
if (previouslyProcessedBundles[requestedPath.bundleIdentifier]) return nil;
previouslyProcessedBundles[requestedPath.bundleIdentifier] = @YES;
NSMutableArray *relatedEvents = [NSMutableArray array];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
__block BOOL shouldCancel = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
SNTFileInfo *originalFile = [[SNTFileInfo alloc] initWithPath:event.filePath];
NSString *bundlePath = originalFile.bundlePath;
originalFile = nil; // release originalFile early.
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:bundlePath];
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
NSString *file;
while (file = [dirEnum nextObject]) {
@@ -235,21 +185,19 @@
if (shouldCancel) break;
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
file = [bundlePath stringByAppendingPathComponent:file];
// Don't record the binary that triggered this event as a related binary.
if ([file isEqual:event.filePath]) continue;
file = [path stringByAppendingPathComponent:file];
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:file];
if (fi.isExecutable) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.filePath = fi.path;
se.fileSHA256 = fi.SHA256;
se.decision = SNTEventStateRelatedBinary;
se.fileBundleID = event.fileBundleID;
se.fileBundleName = event.fileBundleName;
se.fileBundleVersion = event.fileBundleVersion;
se.fileBundleVersionString = event.fileBundleVersionString;
se.decision = SNTEventStateBundleBinary;
se.fileBundleID = fi.bundleIdentifier;
se.fileBundleName = fi.bundleName;
se.fileBundlePath = fi.bundlePath;
se.fileBundleVersion = fi.bundleVersion;
se.fileBundleVersionString = fi.bundleShortVersionString;
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
se.signingChain = cs.certificates;
@@ -262,11 +210,10 @@
dispatch_semaphore_signal(sema);
});
// Give the search up to 5s per event to run.
// This might need tweaking if it seems to slow down syncing or misses too much to be useful.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
// Give the search up to 10m per bundle to run.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 600))) {
shouldCancel = YES;
LOGD(@"Timed out while searching for related events. Bundle ID: %@", event.fileBundleID);
LOGD(@"Timed out while searching for related events at path %@", path);
}
return relatedEvents;

View File

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

View File

@@ -15,49 +15,40 @@
#import "SNTCommandSyncLogUpload.h"
#import "NSData+Zlib.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

@@ -12,14 +12,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@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,7 @@
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@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,90 @@
/// 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_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
requestDict[kBinaryRuleCount] = @(binary);
requestDict[kCertificateRuleCount] = @(certificate);
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, 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]) {
requestDict[kRequestCleanSync] = @YES;
}
NSURLRequest *req = [self requestWithDictionary:requestDict];
NSDictionary *resp = [self performRequest:req];
if (!resp) return NO;
self.syncState.eventBatchSize = [resp[kBatchSize] intValue];
if (self.syncState.eventBatchSize == 0) {
self.syncState.eventBatchSize = 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]) {
self.syncState.cleanSync = YES;
}
return YES;
}
@end

View File

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

View File

@@ -0,0 +1,111 @@
/// 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;
}
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
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,72 @@
/// 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.
@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,195 @@
/// 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;
@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 {
__block BOOL success = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // only fetch token once per session
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

@@ -14,31 +14,46 @@
#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
/// The base API URL
/// 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;
/// Machine identifier and owner
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@property(copy) NSString *machineOwner;
/// Clean sync flag, sent from server. If True, all existing rules
/// should be deleted before inserting any new rules.
/// 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;
/// New client mode sent from server
@property SNTClientMode newClientMode;
/// Batch size for uploading events.
@property NSUInteger eventBatchSize;
/// Batch size for uploading events, sent from server
@property int32_t eventBatchSize;
/// Log upload URL sent from server
/// Log upload URL sent from server. If set, LogUpload phase needs to happen.
@property NSURL *uploadLogURL;
/// Rules downloaded from server
/// Array of bundle paths to find binaries for.
@property NSArray *bundleBinaryRequests;
/// Rules downloaded from server.
@property NSMutableArray *downloadedRules;
@end

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,84 +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.
///
/// An authenticating NSURLSession, which can do both pinned verification of the SSL server
/// and handle client certificate authentication from the keychain.
///
@interface SNTAuthenticatingURLSession : NSObject<NSURLSessionDelegate>
///
/// The underlying session. Pass this session to NSURLRequest methods.
///
@property(readonly, nonatomic) NSURLSession *session;
///
/// If set, this is the user-agent to send with requests, otherwise remains the default
/// CFNetwork-based name.
///
@property(copy, nonatomic) NSString *userAgent;
///
/// If set to YES, this session refuses redirect requests. Defaults to NO.
///
@property(nonatomic) BOOL refusesRedirects;
///
/// If set, the server that we connect to _must_ match this string. Redirects to other
/// hosts will not be allowed.
///
@property(copy, nonatomic) NSString *serverHostname;
///
/// This should be PEM data containing one or more certificates to use to verify the server's
/// certificate chain. This will override the trusted roots in the System Roots.
///
@property(copy, nonatomic) NSData *serverRootsPemData;
///
/// If set and client certificate authentication is needed, the pkcs#12 file will be loaded
///
@property(copy, nonatomic) NSString *clientCertFile;
///
/// If set and client certificate authentication is needed, the password being used for
/// loading the clientCertFile
///
@property(copy, nonatomic) NSString *clientCertPassword;
///
/// If set and client certificate authentication is needed, will search the keychain for a
/// certificate matching this common name and use that for authentication
/// @note Not case sensitive
/// @note If multiple matching certificates are found, the first one is used.
/// @note If this property is not set and neither is |clientCertIssuerCn|, the allowed issuers
/// provided by the server will be used to find a matching certificate.
///
@property(copy, nonatomic) NSString *clientCertCommonName;
///
/// If set and client certificate authentication is needed, will search the keychain for a
/// certificate issued by an issuer with this name and use that for authentication.
///
/// @note Not case sensitive
/// @note If multiple matching certificates are found, the first one is used.
/// @note If this property is not set and neither is |clientCertCommonName|, the allowed issuers
/// provided by the server will be used to find a matching certificate.
///
@property(copy, nonatomic) NSString *clientCertIssuerCn;
/// Designated initializer
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
@end

View File

@@ -1,376 +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 "SNTAuthenticatingURLSession.h"
#import "MOLCertificate.h"
#import "SNTDERDecoder.h"
#import "SNTLogging.h"
@interface SNTAuthenticatingURLSession ()
@property(readwrite) NSURLSession *session;
@property NSURLSessionConfiguration *sessionConfig;
@end
@implementation SNTAuthenticatingURLSession
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (self) {
_sessionConfig = configuration;
}
return self;
}
- (instancetype)init {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
[config setTLSMinimumSupportedProtocol:kTLSProtocol12];
[config setHTTPShouldUsePipelining:YES];
return [self initWithSessionConfiguration:config];
}
#pragma mark Session Fetching
- (NSURLSession *)session {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:self.sessionConfig
delegate:self
delegateQueue:nil];
}
return _session;
}
#pragma mark User Agent property
- (NSString *)userAgent {
return self.sessionConfig.HTTPAdditionalHeaders[@"User-Agent"];
}
- (void)setUserAgent:(NSString *)userAgent {
NSMutableDictionary *addlHeaders = [self.sessionConfig.HTTPAdditionalHeaders mutableCopy];
if (!addlHeaders) addlHeaders = [NSMutableDictionary dictionary];
addlHeaders[@"User-Agent"] = userAgent;
self.sessionConfig.HTTPAdditionalHeaders = [addlHeaders copy];
_session = nil;
}
#pragma mark NSURLSessionDelegate methods
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
if (challenge.previousFailureCount > 0) {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
if (self.serverHostname && ![self.serverHostname isEqual:protectionSpace.host]) {
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
if (![protectionSpace.protocol isEqual:NSURLProtectionSpaceHTTPS]) {
LOGE(@"%@ is not a secure protocol", protectionSpace.protocol);
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
if (!protectionSpace.receivesCredentialSecurely) {
LOGE(@"Secure authentication or protocol cannot be established.");
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
NSString *authMethod = [protectionSpace authenticationMethod];
if (authMethod == NSURLAuthenticationMethodClientCertificate) {
NSURLCredential *cred = [self clientCredentialForProtectionSpace:protectionSpace];
if (cred) {
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
return;
} else {
LOGW(@"Server asked for client authentication but no usable client certificate found.");
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
return;
}
} else if (authMethod == NSURLAuthenticationMethodServerTrust) {
NSURLCredential *cred = [self serverCredentialForProtectionSpace:protectionSpace];
if (cred) {
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
return;
} else {
LOGE(@"Unable to verify server identity.");
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
return;
}
}
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler {
if (self.refusesRedirects) {
LOGD(@"Rejected redirection to: %@", request.URL);
[task cancel]; // without this, the connection hangs until timeout!?!
completionHandler(NULL);
} else {
completionHandler(request);
}
}
#pragma mark Private Helpers for URLSession:didReceiveChallenge:completionHandler:
///
/// Handles the process of locating a valid client certificate for authentication.
/// Operates in one of four modes, depending on the configuration in config.plist
///
/// Mode 1: if syncClientAuthCertificateFile is set, use the identity in the pkcs file
/// Mode 2: if syncClientAuthCertificateCn is set, look for an identity in the keychain with a
/// matching common name and return it.
/// Mode 3: if syncClientAuthCertificateIssuer is set, look for an identity in the keychain with a
/// matching issuer common name and return it.
/// Mode 4: use the list of issuer details sent down by the server to find an identity in the
/// keychain.
///
/// If a valid identity cannot be found, returns nil.
///
- (NSURLCredential *)clientCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
__block OSStatus err = errSecSuccess;
__block SecIdentityRef foundIdentity = NULL;
if (self.clientCertFile) {
foundIdentity = [self identityFromFile:self.clientCertFile password:self.clientCertPassword];
} else {
CFArrayRef cfResults = NULL;
SecItemCopyMatching((__bridge CFDictionaryRef) @{
(id)kSecClass : (id)kSecClassCertificate,
(id)kSecReturnRef : @YES,
(id)kSecMatchLimit : (id)kSecMatchLimitAll
}, (CFTypeRef *)&cfResults);
NSArray *results = CFBridgingRelease(cfResults);
NSMutableArray *allCerts = [[MOLCertificate certificatesFromArray:results] mutableCopy];
if (self.clientCertCommonName) {
foundIdentity = [self identityByFilteringArray:allCerts
commonName:self.clientCertCommonName
issuerCommonName:nil
issuerCountryName:nil
issuerOrgName:nil
issuerOrgUnit:nil];
} else if (self.clientCertIssuerCn) {
foundIdentity = [self identityByFilteringArray:allCerts
commonName:nil
issuerCommonName:self.clientCertIssuerCn
issuerCountryName:nil
issuerOrgName:nil
issuerOrgUnit:nil];
} else {
for (NSData *allowedIssuer in protectionSpace.distinguishedNames) {
SNTDERDecoder *decoder = [[SNTDERDecoder alloc] initWithData:allowedIssuer];
if (!decoder) {
LOGW(@"Unable to decode allowed distinguished name.");
continue;
}
foundIdentity = [self identityByFilteringArray:allCerts
commonName:nil
issuerCommonName:decoder.commonName
issuerCountryName:decoder.countryName
issuerOrgName:decoder.organizationName
issuerOrgUnit:decoder.organizationalUnit];
if (foundIdentity) break;
}
}
}
if (foundIdentity) {
SecCertificateRef certificate = NULL;
err = SecIdentityCopyCertificate(foundIdentity, &certificate);
MOLCertificate *clientCert = [[MOLCertificate alloc] initWithSecCertificateRef:certificate];
if (certificate) CFRelease(certificate);
LOGD(@"Client Trust: Valid client identity %@.", clientCert);
NSURLCredential *cred =
[NSURLCredential credentialWithIdentity:foundIdentity
certificates:nil
persistence:NSURLCredentialPersistenceForSession];
return cred;
} else {
LOGD(@"Client Trust: No valid identity found.");
return nil;
}
}
///
/// Handles the process of evaluating the server's certificate chain.
/// Operates in one of three modes, depending on the configuration in config.plist
///
/// Mode 1: if syncServerAuthRootsData is set, evaluates the server's certificate chain contains
/// one of the certificates in the PEM data in the config plist.
/// Mode 2: if syncServerAuthRootsFile is set, evaluates the server's certificate chain contains
/// one of the certificates in the PEM data in the file specified.
/// Mode 3: evaluates the server's certificate chain is trusted by the keychain.
///
/// If the server's certificate chain does not evaluate for any reason, returns nil.
///
- (NSURLCredential *)serverCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
SecTrustRef serverTrust = protectionSpace.serverTrust;
if (serverTrust == NULL) {
LOGD(@"Server Trust: No server trust information available");
return nil;
}
OSStatus err = errSecSuccess;
if (self.serverRootsPemData) {
NSString *pemStrings = [[NSString alloc] initWithData:self.serverRootsPemData
encoding:NSASCIIStringEncoding];
NSArray *certs = [MOLCertificate certificatesFromPEM:pemStrings];
// Make a new array of the SecCertificateRef's from the MOLCertificate's.
NSMutableArray *certRefs = [[NSMutableArray alloc] initWithCapacity:certs.count];
for (MOLCertificate *cert in certs) {
[certRefs addObject:(id)cert.certRef];
}
// Set this array of certs as the anchors to trust.
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)certRefs);
if (err != errSecSuccess) {
LOGD(@"Server Trust: Could not set anchor certificates: %d", err);
return nil;
}
}
// Evaluate the server's cert chain.
SecTrustResultType result = kSecTrustResultInvalid;
err = SecTrustEvaluate(serverTrust, &result);
if (err != errSecSuccess) {
LOGD(@"Server Trust: Unable to evaluate certificate chain for server: %d", err);
return nil;
}
// Print details about the server's leaf certificate.
SecCertificateRef firstCert = SecTrustGetCertificateAtIndex(serverTrust, 0);
if (firstCert) {
MOLCertificate *cert = [[MOLCertificate alloc] initWithSecCertificateRef:firstCert];
LOGD(@"Server Trust: Server leaf cert: %@", cert);
}
// Having a trust level "unspecified" by the user is the usual result, described at
// https://developer.apple.com/library/mac/qa/qa1360
if (result != kSecTrustResultProceed && result != kSecTrustResultUnspecified) {
LOGD(@"Server Trust: Server isn't trusted. SecTrustResultType: %d", result);
return nil;
}
return [NSURLCredential credentialForTrust:serverTrust];
}
/**
Given an array of MOLCertificate objects and some properties, filter the array
repeatedly until an identity is found that fulfills the signing chain.
*/
- (SecIdentityRef)identityByFilteringArray:(NSArray *)array
commonName:(NSString *)commonName
issuerCommonName:(NSString *)issuerCommonName
issuerCountryName:(NSString *)issuerCountryName
issuerOrgName:(NSString *)issuerOrgName
issuerOrgUnit:(NSString *)issuerOrgUnit {
NSMutableArray *predicates = [NSMutableArray arrayWithCapacity:4];
if (commonName) {
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.commonName == %@",
commonName]];
}
if (issuerCommonName) {
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerCommonName == %@",
issuerCommonName]];
}
if (issuerCountryName) {
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerCountryName == %@",
issuerCountryName]];
}
if (issuerOrgName) {
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerOrgName == %@",
issuerOrgName]];
}
if (issuerOrgUnit) {
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerOrgUnit == %@",
issuerOrgUnit]];
}
NSCompoundPredicate *andPreds = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
NSArray *filteredCerts = [array filteredArrayUsingPredicate:andPreds];
if (!filteredCerts.count) return NULL;
for (MOLCertificate *cert in filteredCerts) {
SecIdentityRef identityRef = NULL;
OSStatus status = SecIdentityCreateWithCertificate(NULL, cert.certRef, &identityRef);
if (status == errSecSuccess) {
return identityRef;
} else {
// Avoid infinite recursion from self-signed certs
if ((cert.commonName && [cert.commonName isEqual:cert.issuerCommonName]) &&
(cert.countryName && [cert.countryName isEqual:cert.issuerCountryName]) &&
(cert.orgName && [cert.orgName isEqual:cert.issuerOrgName]) &&
(cert.orgUnit && [cert.orgUnit isEqual:cert.issuerOrgUnit])) {
continue;
}
return [self identityByFilteringArray:array
commonName:nil
issuerCommonName:cert.commonName
issuerCountryName:cert.countryName
issuerOrgName:cert.orgName
issuerOrgUnit:cert.orgUnit];
}
}
return NULL;
}
- (SecIdentityRef)identityFromFile:(NSString *)file password:(NSString *)password {
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:file options:0 error:&error];
if (error) {
LOGD(@"Client Trust: Couldn't open client certificate %@: %@",
self.clientCertFile,
[error localizedDescription]);
return nil;
}
NSDictionary *options = (password ? @{(__bridge id)kSecImportExportPassphrase : password} : @{});
CFArrayRef cfIdentities;
OSStatus err = SecPKCS12Import(
(__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &cfIdentities);
NSArray *identities = CFBridgingRelease(cfIdentities);
if (err != errSecSuccess) {
LOGD(@"Client Trust: Couldn't load client certificate %@: %d", self.clientCertFile, err);
return nil;
}
return (__bridge SecIdentityRef)identities[0][(__bridge id)kSecImportItemIdentity];
}
@end

View File

@@ -1,31 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@interface SNTCommandSyncEventUpload : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
+ (void)uploadSingleEventWithSHA256:(NSString *)SHA256
session:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -1,70 +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 "SNTCommandSyncPostflight.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncPostflight
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[kURLPostflight stringByAppendingString:syncState.machineID]
relativeToURL:syncState.syncBaseURL];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[[session dataTaskWithRequest:req 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 {
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (syncState.newClientMode) {
[[daemonConn remoteObjectProxy] setClientMode:syncState.newClientMode reply:^{}];
}
NSString *backoffInterval = r[kBackoffInterval];
if (backoffInterval) {
[[daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue] reply:^{}];
}
if (syncState.cleanSync) {
[[daemonConn remoteObjectProxy] setSyncCleanRequired:NO reply:^{}];
}
// Update last sync success
[[daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:^{}];
handler(YES);
}
}] resume];
}
@end

View File

@@ -1,106 +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 "SNTCommandSyncPreflight.h"
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
#import "NSData+Zlib.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTConfigurator.h"
#import "SNTSystemInfo.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@implementation SNTCommandSyncPreflight
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[kURLPreflight stringByAppendingString:syncState.machineID]
relativeToURL:syncState.syncBaseURL];
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
requestDict[kSerialNumber] = [SNTSystemInfo serialNumber];
requestDict[kHostname] = [SNTSystemInfo shortHostname];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kOSVer] = [SNTSystemInfo osVersion];
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
requestDict[kPrimaryUser] = syncState.machineOwner;
// 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]) {
requestDict[kRequestCleanSync] = @YES;
}
NSData *requestBody = [NSJSONSerialization dataWithJSONObject:requestDict
options:0
error:nil];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSData *compressed = [requestBody zlibCompressed];
if (compressed) {
requestBody = compressed;
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
}
[req setHTTPBody:requestBody];
[[session dataTaskWithRequest:req 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 {
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
syncState.eventBatchSize = [r[kBatchSize] intValue];
syncState.uploadLogURL = [NSURL URLWithString:r[kUploadLogsURL]];
if ([r[kClientMode] isEqual:kClientModeMonitor]) {
syncState.newClientMode = SNTClientModeMonitor;
} else if ([r[kClientMode] isEqual:kClientModeLockdown]) {
syncState.newClientMode = SNTClientModeLockdown;
}
if ([r[kWhitelistRegex] isKindOfClass:[NSString class]]) {
[[daemonConn remoteObjectProxy] setWhitelistPathRegex:r[kWhitelistRegex] reply:^{}];
}
if ([r[kBlacklistRegex] isKindOfClass:[NSString class]]) {
[[daemonConn remoteObjectProxy] setBlacklistPathRegex:r[kBlacklistRegex] reply:^{}];
}
if ([r[kCleanSync] boolValue]) {
syncState.cleanSync = YES;
}
handler(YES);
}
}] resume];
}
@end

View File

@@ -1,25 +0,0 @@
/// Copyright 2015 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTCommandSyncState;
@class SNTXPCConnection;
@interface SNTCommandSyncRuleDownload : NSObject
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler;
@end

View File

@@ -1,149 +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 "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncState.h"
#import "SNTRule.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#include "SNTLogging.h"
@implementation SNTCommandSyncRuleDownload
+ (void)performSyncInSession:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[kURLRuleDownload stringByAppendingString:syncState.machineID]
relativeToURL:syncState.syncBaseURL];
[self ruleDownloadWithCursor:nil
url:url
session:session
syncState:syncState
daemonConn:daemonConn
completionHandler:handler];
}
+ (void)ruleDownloadWithCursor:(NSString *)cursor
url:(NSURL *)url
session:(NSURLSession *)session
syncState:(SNTCommandSyncState *)syncState
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSDictionary *requestDict = (cursor ? @{kCursor : cursor} : @{});
if (!syncState.downloadedRules) {
syncState.downloadedRules = [NSMutableArray array];
}
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPBody:[NSJSONSerialization dataWithJSONObject:requestDict
options:0
error:nil]];
[req setHTTPMethod:@"POST"];
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[[session dataTaskWithRequest:req 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 {
NSDictionary *resp = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if (!resp) {
LOGE(@"Failed to decode server's response");
handler(NO);
return;
}
NSArray *receivedRules = resp[kRules];
for (NSDictionary *rule in receivedRules) {
SNTRule *r = [self ruleFromDictionary:rule];
if (r) [syncState.downloadedRules addObject:r];
}
if (resp[kCursor]) {
[self ruleDownloadWithCursor:resp[kCursor]
url:url
session:session
syncState:syncState
daemonConn:daemonConn
completionHandler:handler];
} else {
if (syncState.downloadedRules.count) {
[[daemonConn remoteObjectProxy] databaseRuleAddRules:syncState.downloadedRules
cleanSlate:syncState.cleanSync
reply:^(NSError *error) {
if (!error) {
LOGI(@"Added %lu rule(s)", syncState.downloadedRules.count);
handler(YES);
} else {
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
LOGD(@"Failure reason: %@", error.localizedFailureReason);
handler(NO);
}
}];
} else {
handler(YES);
}
}
}
}] resume];
}
+ (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

@@ -1,35 +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.
///
/// This is a simple ASN.1 decoder that utilizes Apple's SecAsn1Decode
/// to parse the @c distinguishedNames property of NSURLProtectionSpace.
///
@interface SNTDERDecoder : NSObject
@property(readonly) NSString *commonName;
@property(readonly) NSString *organizationName;
@property(readonly) NSString *organizationalUnit;
@property(readonly) NSString *countryName;
///
/// Designated initializer.
///
/// @param data one of the objects in the
/// NSURLProtectionSpace.distinguishedNames array
/// @return nil if decoding fails to find any expected objects
///
- (instancetype)initWithData:(NSData *)data;
@end

View File

@@ -1,217 +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 "SNTDERDecoder.h"
#import <Security/SecAsn1Coder.h>
#import <Security/SecAsn1Templates.h>
@interface SNTDERDecoder ()
@property NSDictionary *decodedObjects;
@end
@implementation SNTDERDecoder
#pragma mark Init
- (instancetype)initWithData:(NSData *)data {
self = [super init];
if (self) {
if (!data) return nil;
_decodedObjects = [self decodeData:data];
if (!_decodedObjects || [_decodedObjects count] == 0) return nil;
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (NSString *)description {
return [NSString stringWithFormat:@"/C=%@/O=%@/OU=%@/CN=%@",
self.countryName,
self.organizationName,
self.organizationalUnit,
self.commonName];
}
#pragma mark Accessors
- (NSString *)commonName {
return self.decodedObjects[(__bridge id)kSecOIDCommonName];
}
- (NSString *)organizationName {
return self.decodedObjects[(__bridge id)kSecOIDOrganizationName];
}
- (NSString *)organizationalUnit {
return self.decodedObjects[(__bridge id)kSecOIDOrganizationalUnitName];
}
- (NSString *)countryName {
return self.decodedObjects[(__bridge id)kSecOIDCountryName];
}
#pragma mark Private
/**
* The DER data provided by NSURLProtectionSpace.distinguishedNames looks like
* this:
*
* SEQUENCE {
* SET {
* SEQUENCE {
* OBJECT IDENTIFIER (2 5 4 6)
* PrintableString 'US'
* }
* }
* SET {
* SEQUENCE {
* OBJECT IDENTIFIER (2 5 4 10)
* PrintableString 'Megaco Inc'
* }
* }
* }
*
* This method assumes the passed in data will be in that format. If it isn't,
* the DER decoding will fail and this method will return nil.
**/
- (NSDictionary *)decodeData:(NSData *)data {
typedef struct {
SecAsn1Oid oid;
SecAsn1Item value;
} OIDKeyValue;
static const SecAsn1Template kOIDValueTemplate[] = {
{SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OIDKeyValue)},
{SEC_ASN1_OBJECT_ID, offsetof(OIDKeyValue, oid), NULL, 0},
{SEC_ASN1_ANY_CONTENTS, offsetof(OIDKeyValue, value), NULL, 0},
{0, 0, NULL, 0}};
typedef struct {
OIDKeyValue **vals;
} OIDKeyValueList;
static const SecAsn1Template kSetOfOIDValueTemplate[] = {
{SEC_ASN1_SET_OF, 0, kOIDValueTemplate, sizeof(OIDKeyValueList)},
{0, 0, NULL, 0}};
typedef struct {
OIDKeyValueList **lists;
} OIDKeyValueListSeq;
static const SecAsn1Template kSequenceOfSetOfOIDValueTemplate[] = {
{SEC_ASN1_SEQUENCE_OF, 0, kSetOfOIDValueTemplate, sizeof(OIDKeyValueListSeq)},
{0, 0, NULL, 0}};
OSStatus err = errSecSuccess;
SecAsn1CoderRef coder;
err = SecAsn1CoderCreate(&coder);
if (err != errSecSuccess) return nil;
OIDKeyValueListSeq a;
err = SecAsn1Decode(coder,
data.bytes,
data.length,
kSequenceOfSetOfOIDValueTemplate,
&a);
if (err != errSecSuccess) {
SecAsn1CoderRelease(coder);
return nil;
}
// The data is decoded but now it's in a number of embedded structs.
// Massage that into a nice dictionary of OID->String pairs.
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
OIDKeyValueList *anAttr;
for (NSUInteger i = 0; (anAttr = a.lists[i]); ++i) {
OIDKeyValue *keyValue = anAttr->vals[0];
// Sanity check
if (keyValue->value.Length > data.length) {
SecAsn1CoderRelease(coder);
return nil;
}
// Get the string value. First try creating as a UTF-8 string. If that fails,
// fallback to trying as an ASCII string. If it still doesn't work, continue on
// to the next value.
NSString *valueString;
valueString = [[NSString alloc] initWithBytes:keyValue->value.Data
length:keyValue->value.Length
encoding:NSUTF8StringEncoding];
if (!valueString) {
valueString = [[NSString alloc] initWithBytes:keyValue->value.Data
length:keyValue->value.Length
encoding:NSASCIIStringEncoding];
}
if (!valueString) continue;
// The OID is still encoded, so we need to decode it.
NSString *objectId = [SNTDERDecoder decodeOIDWithBytes:keyValue->oid.Data
length:keyValue->oid.Length];
// Add to the dictionary
dict[objectId] = valueString;
}
SecAsn1CoderRelease(coder);
return dict;
}
/**
* Decodes an ASN.1 Object Identifier into a string separated by periods.
* See http://msdn.microsoft.com/en-us/library/bb540809(v=vs.85).aspx for
* details of the encoding.
**/
+ (NSString *)decodeOIDWithBytes:(unsigned char *)bytes length:(NSUInteger)length {
NSMutableArray *objectId = [NSMutableArray array];
BOOL inVariableLengthByte = NO;
NSUInteger variableLength = 0;
for (NSUInteger i = 0; i < length; ++i) {
if (i == 0) {
// The first byte is actually two values, the top 4 bits are the first value * 40
// and the bottom 4 bits are the second value.
[objectId addObject:@((NSUInteger)bytes[i] / 40)];
[objectId addObject:@((NSUInteger)bytes[i] % 40)];
} else {
// The remaining bytes are encoded with Variable Length Quantity.
unsigned char byte = bytes[i];
if (byte & 0x80) {
inVariableLengthByte = YES;
NSUInteger a = (NSUInteger)(byte & ~0x80);
variableLength = variableLength << 7;
variableLength += a;
} else if (inVariableLengthByte) {
NSUInteger a = (NSUInteger)(byte & ~0x80);
variableLength = variableLength << 7;
variableLength += a;
inVariableLengthByte = NO;
[objectId addObject:@(variableLength)];
variableLength = 0;
} else {
[objectId addObject:@((NSUInteger)byte)];
}
}
}
return [objectId componentsJoinedByString:@"."];
}
@end

View File

@@ -29,7 +29,7 @@
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
// Lock this database from other processes
[db executeQuery:@"PRAGMA locking_mode = EXCLUSIVE;"];
[[db executeQuery:@"PRAGMA locking_mode = EXCLUSIVE;"] close];
uint32_t newVersion = 0;
@@ -61,7 +61,7 @@
@"WHERE (shasum=? OR shasum=?) AND state=?",
self.santadCertSHA, self.launchdCertSHA, @(SNTRuleStateWhitelist)];
if (ruleCount != 2) {
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
self.santadCertSHA, @(SNTRuleStateWhitelist), @(SNTRuleTypeCertificate)];
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",

View File

@@ -14,6 +14,7 @@
#import "SNTApplication.h"
#import <DiskArbitration/DiskArbitration.h>
#include <sys/stat.h>
#include <sys/types.h>
@@ -33,6 +34,7 @@
#import "SNTXPCControlInterface.h"
@interface SNTApplication ()
@property DASessionRef diskArbSession;
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@property SNTExecutionController *execController;
@@ -78,12 +80,34 @@
_controlConnection.exportedObject = dc;
[_controlConnection resume];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath handler:^{
[[SNTConfigurator configurator] reloadConfigData];
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^(unsigned long data) {
if (data & DISPATCH_VNODE_ATTRIB) {
const char *cPath = [kDefaultConfigFilePath fileSystemRepresentation];
struct stat fileStat;
stat(cPath, &fileStat);
int mask = S_IRWXU | S_IRWXG | S_IRWXO;
int desired = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (fileStat.st_uid != 0 || fileStat.st_gid != 0 || (fileStat.st_mode & mask) != desired) {
LOGD(@"Config file permissions changed, fixing.");
chown(cPath, 0, 0);
chmod(cPath, desired);
}
} else {
LOGD(@"Config file changed, reloading.");
[[SNTConfigurator configurator] reloadConfigData];
// Ensure config file remains root:wheel 0644
chown([kDefaultConfigFilePath fileSystemRepresentation], 0, 0);
chmod([kDefaultConfigFilePath fileSystemRepresentation], 0644);
// Flush cache if client just went into lockdown.
SNTClientMode newMode = [[SNTConfigurator configurator] clientMode];
if (origMode != newMode) {
origMode = newMode;
if (newMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCache];
}
}
}
}];
_eventLog = [[SNTEventLog alloc] init];
@@ -105,6 +129,7 @@
[self performSelectorInBackground:@selector(beginListeningForDecisionRequests) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForLogRequests) withObject:nil];
[self performSelectorInBackground:@selector(beginListeningForDiskMounts) withObject:nil];
}
- (void)beginListeningForDecisionRequests {
@@ -121,7 +146,7 @@
}
case ACTION_REQUEST_BINARY: {
dispatch_async(exec_queue, ^{
[self.execController validateBinaryWithMessage:message];
[_execController validateBinaryWithMessage:message];
});
break;
}
@@ -138,7 +163,7 @@
dispatch_queue_t log_queue = dispatch_queue_create(
"com.google.santad.log_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(
log_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
log_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
[self.driverManager listenForLogRequests:^(santa_message_t message) {
@autoreleasepool {
@@ -152,14 +177,14 @@
NSRegularExpression *re = [[SNTConfigurator configurator] fileChangesRegex];
NSString *path = @(message.path);
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
[self.eventLog logFileModification:message];
[_eventLog logFileModification:message];
}
});
break;
}
case ACTION_NOTIFY_EXEC: {
dispatch_async(log_queue, ^{
[self.eventLog logAllowedExecution:message];
[_eventLog logAllowedExecution:message];
});
break;
}
@@ -171,4 +196,38 @@
}];
}
- (void)beginListeningForDiskMounts {
dispatch_queue_t disk_queue = dispatch_queue_create(
"com.google.santad.disk_queue", DISPATCH_QUEUE_SERIAL);
_diskArbSession = DASessionCreate(NULL);
DASessionSetDispatchQueue(_diskArbSession, disk_queue);
DARegisterDiskAppearedCallback(
_diskArbSession, NULL, diskAppearedCallback, (__bridge void *)self);
DARegisterDiskDescriptionChangedCallback(
_diskArbSession, NULL, NULL, diskDescriptionChangedCallback, (__bridge void *)self);
DARegisterDiskDisappearedCallback(
_diskArbSession, NULL, diskDisappearedCallback, (__bridge void *)self);
}
void diskAppearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
[app.eventLog logDiskAppeared:props];
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (props[@"DAVolumePath"]) [app.eventLog logDiskAppeared:props];
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
[app.eventLog logDiskDisappeared:props];
}
@end

View File

@@ -33,6 +33,7 @@ double watchdogCPUPeak = 0;
double watchdogRAMPeak = 0;
@interface SNTDaemonControlController ()
@property NSString *_syncXsrfToken;
@property dispatch_source_t syncTimer;
@end
@@ -90,6 +91,10 @@ double watchdogRAMPeak = 0;
reply([self.driverManager flushCache]);
}
- (void)checkCacheForVnodeID:(uint64_t)vnodeID withReply:(void (^)(santa_action_t))reply {
reply([self.driverManager checkCache:vnodeID]);
}
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply {
@@ -129,6 +134,14 @@ double watchdogRAMPeak = 0;
[[SNTDatabaseController eventTable] deleteEventsWithIds:ids];
}
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] binaryRuleForSHA256:sha256]);
}
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] certificateRuleForSHA256:sha256]);
}
#pragma mark Config Ops
- (void)clientMode:(void (^)(SNTClientMode))reply {
@@ -136,7 +149,19 @@ double watchdogRAMPeak = 0;
}
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply {
[[SNTConfigurator configurator] setClientMode:mode];
if ([[SNTConfigurator configurator] clientMode] != mode) {
[[SNTConfigurator configurator] setClientMode:mode];
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:mode];
}
reply();
}
- (void)xsrfToken:(void (^)(NSString *))reply {
reply(self._syncXsrfToken);
}
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply {
self._syncXsrfToken = token;
reply();
}

View File

@@ -55,4 +55,9 @@
///
- (BOOL)flushCache;
///
/// Check the kernel cache for a VnodeID
///
-(santa_action_t)checkCache:(uint64_t)vnodeID;
@end

View File

@@ -98,13 +98,9 @@ static const int MAX_DELAY = 15;
withCallback:(void (^)(santa_message_t))callback {
kern_return_t kr;
mach_port_t receivePort = 0;
IODataQueueMemory *queueMemory = NULL;
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
// Allocate a mach port to receive notifactions from the IODataQueue
if (!(receivePort = IODataQueueAllocateNotificationPort())) {
mach_port_t receivePort = IODataQueueAllocateNotificationPort();
if (receivePort == MACH_PORT_NULL) {
LOGD(@"Failed to allocate notification port");
return;
}
@@ -118,15 +114,16 @@ static const int MAX_DELAY = 15;
}
// This will call clientMemoryForType() inside our user client class.
kr = IOConnectMapMemory(self.connection, type, mach_task_self(),
&address, &size, kIOMapAnywhere);
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
kr = IOConnectMapMemory(self.connection, type, mach_task_self(), &address, &size, kIOMapAnywhere);
if (kr != kIOReturnSuccess) {
LOGD(@"Failed to map memory for type %d: %d", type, kr);
mach_port_destroy(mach_task_self(), receivePort);
return;
}
queueMemory = (IODataQueueMemory *)address;
IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
do {
while (IODataQueueDataAvailable(queueMemory)) {
@@ -151,14 +148,14 @@ static const int MAX_DELAY = 15;
- (kern_return_t)postToKernelAction:(santa_action_t)action forVnodeID:(uint64_t)vnodeId {
switch (action) {
case ACTION_RESPOND_ALLOW:
return IOConnectCallScalarMethod(self.connection,
return IOConnectCallScalarMethod(_connection,
kSantaUserClientAllowBinary,
&vnodeId,
1,
0,
0);
case ACTION_RESPOND_DENY:
return IOConnectCallScalarMethod(self.connection,
return IOConnectCallScalarMethod(_connection,
kSantaUserClientDenyBinary,
&vnodeId,
1,
@@ -173,7 +170,7 @@ static const int MAX_DELAY = 15;
uint32_t input_count = 1;
uint64_t cache_count = 0;
IOConnectCallScalarMethod(self.connection,
IOConnectCallScalarMethod(_connection,
kSantaUserClientCacheCount,
0,
0,
@@ -183,8 +180,21 @@ static const int MAX_DELAY = 15;
}
- (BOOL)flushCache {
return IOConnectCallScalarMethod(self.connection,
return IOConnectCallScalarMethod(_connection,
kSantaUserClientClearCache, 0, 0, 0, 0) == KERN_SUCCESS;
}
- (santa_action_t)checkCache:(uint64_t)vnodeID {
uint32_t input_count = 1;
uint64_t vnode_action = 0;
IOConnectCallScalarMethod(_connection,
kSantaUserClientCheckCache,
&vnodeID,
1,
&vnode_action,
&input_count);
return (santa_action_t)vnode_action;
}
@end

View File

@@ -21,6 +21,9 @@
///
@interface SNTEventLog : NSObject
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
- (void)logFileModification:(santa_message_t)message;
- (void)saveDecisionDetails:(SNTCachedDecision *)cd;

View File

@@ -28,6 +28,7 @@
@interface SNTEventLog ()
@property NSMutableDictionary *detailStore;
@property dispatch_queue_t detailStoreQueue;
@end
@implementation SNTEventLog
@@ -36,18 +37,22 @@
self = [super init];
if (self) {
_detailStore = [NSMutableDictionary dictionaryWithCapacity:10000];
_detailStoreQueue = dispatch_queue_create("com.google.santad.detail_store",
DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)saveDecisionDetails:(SNTCachedDecision *)cd {
self.detailStore[@(cd.vnodeId)] = cd;
dispatch_sync(_detailStoreQueue, ^{
_detailStore[@(cd.vnodeId)] = cd;
});
}
- (void)logFileModification:(santa_message_t)message {
NSString *action, *path, *newpath, *sha256, *outStr;
NSString *action, *newpath;
path = @(message.path);
NSString *path = @(message.path);
switch (message.action) {
case ACTION_NOTIFY_DELETE: {
@@ -71,38 +76,30 @@
}
case ACTION_NOTIFY_WRITE: {
action = @"WRITE";
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:path];
if (fileInfo.fileSize < 1024 * 1024) {
sha256 = fileInfo.SHA256;
} else {
sha256 = @"(too large)";
}
break;
}
default: action = @"UNKNOWN"; break;
}
outStr = [NSString stringWithFormat:@"action=%@|path=%@", action, [self sanitizeString:path]];
// init the string with 2k capacity to avoid reallocs
NSMutableString *outStr = [NSMutableString stringWithCapacity:2048];
[outStr appendFormat:@"action=%@|path=%@", action, [self sanitizeString:path]];
if (newpath) {
outStr = [outStr stringByAppendingFormat:@"|newpath=%@", [self sanitizeString:newpath]];
[outStr appendFormat:@"|newpath=%@", [self sanitizeString:newpath]];
}
char ppath[PATH_MAX] = "(null)";
proc_pidpath(message.pid, ppath, PATH_MAX);
NSString *user, *group;
const char *user = "";
const char *group = "";
struct passwd *pw = getpwuid(message.uid);
if (pw) user = @(pw->pw_name);
if (pw) user = pw->pw_name;
struct group *gr = getgrgid(message.gid);
if (gr) group = @(gr->gr_name);
outStr = [outStr stringByAppendingFormat:(@"|pid=%d|ppid=%d|process=%s|processpath=%s|"
@"uid=%d|user=%@|gid=%d|group=%@"),
message.pid, message.ppid, message.pname, ppath,
message.uid, user, message.gid, group];
if (sha256) {
outStr = [outStr stringByAppendingFormat:@"|sha256=%@", sha256];
}
if (gr) group = gr->gr_name;
[outStr appendFormat:@"|pid=%d|ppid=%d|process=%s|processpath=%s|uid=%d|user=%s|gid=%d|group=%s",
message.pid, message.ppid, message.pname, ppath,
message.uid, user, message.gid, group];
LOGI(@"%@", outStr);
}
@@ -111,33 +108,37 @@
}
- (void)logAllowedExecution:(santa_message_t)message {
SNTCachedDecision *cd = self.detailStore[@(message.vnode_id)];
__block SNTCachedDecision *cd;
dispatch_sync(_detailStoreQueue, ^{
cd = _detailStore[@(message.vnode_id)];
});
[self logExecution:message withDecision:cd];
}
- (void)logExecution:(santa_message_t)message withDecision:(SNTCachedDecision *)cd {
NSString *d, *r, *args, *outLog;
NSString *d, *r;
BOOL logArgs = NO;
switch (cd.decision) {
case SNTEventStateAllowBinary:
d = @"ALLOW";
r = @"BINARY";
args = [self argsForPid:message.pid];
logArgs = YES;
break;
case SNTEventStateAllowCertificate:
d = @"ALLOW";
r = @"CERTIFICATE";
args = [self argsForPid:message.pid];
logArgs = YES;
break;
case SNTEventStateAllowScope:
d = @"ALLOW";
r = @"SCOPE";
args = [self argsForPid:message.pid];
logArgs = YES;
break;
case SNTEventStateAllowUnknown:
d = @"ALLOW";
r = @"UNKNOWN";
args = [self argsForPid:message.pid];
logArgs = YES;
break;
case SNTEventStateBlockBinary:
d = @"DENY";
@@ -158,28 +159,31 @@
default:
d = @"ALLOW";
r = @"NOTRUNNING";
args = [self argsForPid:message.pid];
logArgs = YES;
break;
}
outLog = [NSString stringWithFormat:@"action=EXEC|decision=%@|reason=%@", d, r];
// init the string with 4k capacity to avoid reallocs
NSMutableString *outLog = [[NSMutableString alloc] initWithCapacity:4096];
[outLog appendFormat:@"action=EXEC|decision=%@|reason=%@", d, r];
if (cd.decisionExtra) {
outLog = [outLog stringByAppendingFormat:@"|explain=%@", cd.decisionExtra];
[outLog appendFormat:@"|explain=%@", cd.decisionExtra];
}
outLog = [outLog stringByAppendingFormat:@"|sha256=%@|path=%@|args=%@", cd.sha256,
[self sanitizeString:@(message.path)],
[self sanitizeString:args]];
[outLog appendFormat:@"|sha256=%@|path=%@", cd.sha256, [self sanitizeString:@(message.path)]];
if (logArgs) {
[self addArgsForPid:message.pid toString:outLog];
}
if (cd.certSHA256) {
outLog = [outLog stringByAppendingFormat:@"|cert_sha256=%@|cert_cn=%@", cd.certSHA256,
[self sanitizeString:cd.certCommonName]];
[outLog appendFormat:@"|cert_sha256=%@|cert_cn=%@", cd.certSHA256,
[self sanitizeString:cd.certCommonName]];
}
if (cd.quarantineURL) {
outLog = [outLog stringByAppendingFormat:@"|quarantine_url=%@",
[self sanitizeString:cd.quarantineURL]];
[outLog appendFormat:@"|quarantine_url=%@", [self sanitizeString:cd.quarantineURL]];
}
NSString *user, *group;
@@ -188,76 +192,261 @@
struct group *gr = getgrgid(message.gid);
if (gr) group = @(gr->gr_name);
outLog = [outLog stringByAppendingFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@",
message.pid, message.ppid, message.uid, user,
message.gid, group];
[outLog appendFormat:@"|pid=%d|ppid=%d|uid=%d|user=%@|gid=%d|group=%@",
message.pid, message.ppid, message.uid, user,
message.gid, group];
LOGI(@"%@", outLog);
}
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
if (![diskProperties[@"DAVolumeMountable"] boolValue]) return;
NSString *dmgPath = @"";
NSString *serial = @"";
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
dmgPath = [self diskImageForDevice:diskProperties[@"DADevicePath"]];
} else {
serial = [self serialForDevice:diskProperties[@"DADevicePath"]];
serial = [serial stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}
NSString *model = [NSString stringWithFormat:@"%@ %@",
diskProperties[@"DADeviceVendor"] ?: @"",
diskProperties[@"DADeviceModel"] ?: @""];
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
LOGI(@"action=DISKAPPEAR|mount=%@|volume=%@|bsdname=%@|fs=%@|model=%@|serial=%@|bus=%@|dmgpath=%@",
[diskProperties[@"DAVolumePath"] path] ?: @"",
diskProperties[@"DAVolumeName"] ?: @"",
diskProperties[@"DAMediaBSDName"] ?: @"",
diskProperties[@"DAVolumeKind"] ?: @"",
model ?: @"",
serial,
diskProperties[@"DADeviceProtocol"] ?: @"",
dmgPath);
}
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
if (![diskProperties[@"DAVolumeMountable"] boolValue]) return;
LOGI(@"action=DISKDISAPPEAR|mount=%@|volume=%@|bsdname=%@",
[diskProperties[@"DAVolumePath"] path] ?: @"",
diskProperties[@"DAVolumeName"] ?: @"",
diskProperties[@"DAMediaBSDName"]);
}
#pragma mark Helpers
/**
Sanitizes a given string if necessary, otherwise returns the original.
*/
- (NSString *)sanitizeString:(NSString *)inStr {
inStr = [inStr stringByReplacingOccurrencesOfString:@"|" withString:@"<pipe>"];
inStr = [inStr stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
inStr = [inStr stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
NSUInteger length = [inStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if (length < 1) return inStr;
NSString *ret = [self sanitizeCString:inStr.UTF8String ofLength:length];
if (ret) {
return ret;
}
return inStr;
}
- (NSString *)argsForPid:(pid_t)pid {
int mib[3];
/**
Sanitize the given C-string, replacing |, \n and \r characters.
// Get size of buffer required to store process arguments.
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
int argmax;
size_t size = sizeof(argmax);
@return a new NSString with the replaced contents, if necessary, otherwise nil.
*/
- (NSString *)sanitizeCString:(const char *)str ofLength:(NSUInteger)length {
NSUInteger bufOffset = 0, strOffset = 0;
char c = 0;
char *buf = NULL;
BOOL shouldFree = NO;
if (sysctl(mib, 2, &argmax, &size, NULL, 0) == -1) return nil;
// Loop through the string one character at a time, looking for the characters
// we want to remove.
for (const char *p = str; (c = *p) != 0; ++p) {
if (c == '|' || c == '\n' || c == '\r') {
if (!buf) {
// If string size * 6 is more than 64KiB use malloc, otherwise use stack space.
if (length * 6 > 64 * 1024) {
buf = malloc(length * 6);
shouldFree = YES;
} else {
buf = alloca(length * 6);
}
}
// Create buffer to store args
NSMutableData *argsdata = [NSMutableData dataWithCapacity:argmax];
char *argsdatabytes = (char *)argsdata.mutableBytes;
// Copy from the last offset up to the character we just found into the buffer
ptrdiff_t diff = p - str;
memcpy(buf + bufOffset, str + strOffset, diff - strOffset);
// Fetch args
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
size = (size_t)argmax;
if (sysctl(mib, 3, argsdatabytes, &size, NULL, 0) == -1) return nil;
// Update the buffer and string offsets
bufOffset += diff - strOffset;
strOffset = diff + 1;
// Get argc
int argc;
memcpy(&argc, argsdatabytes, sizeof(argc));
// Get pointer to beginning of string space
char *cp = (char *)argsdatabytes + sizeof(argc);
// Skip over exec_path
for (; cp < &argsdatabytes[size]; ++cp) {
if (*cp == '\0') {
cp++;
break;
// Replace the found character and advance the buffer offset
switch (c) {
case '|':
memcpy(buf + bufOffset, "<pipe>", 6);
bufOffset += 6;
break;
case '\n':
memcpy(buf + bufOffset, "\\n", 2);
bufOffset += 2;
break;
case '\r':
memcpy(buf + bufOffset, "\\r", 2);
bufOffset += 2;
break;
}
}
}
// Skip trailing NULL bytes
for (; cp < &argsdatabytes[size]; ++cp) {
if (*cp != '\0') break;
if (strOffset > 0 && strOffset < length) {
// Copy any characters from the last match to the end of the string into the buffer.
memcpy(buf + bufOffset, str + strOffset, length - strOffset);
bufOffset += length - strOffset;
}
// Loop over the argv array, stripping newlines in each arg and putting in a new array.
NSMutableArray *args = [NSMutableArray arrayWithCapacity:argc];
for (int i = 0; i < argc; ++i) {
NSString *arg = @(cp);
if (arg) [args addObject:arg];
if (buf) {
// Only return a new string if there were matches
NSString *ret = [[NSString alloc] initWithBytes:buf
length:bufOffset
encoding:NSUTF8StringEncoding];
if (shouldFree) {
free(buf);
}
// Move the pointer past this string and the terminator at the end.
cp += strlen(cp) + 1;
return ret;
}
return nil;
}
/**
Use sysctl to get the arguments for a PID, appended to the given string.
*/
- (void)addArgsForPid:(pid_t)pid toString:(NSMutableString *)str {
size_t argsSizeEstimate = 0, argsSize = 0, index = 0;
// Use stack space up to 128KiB.
const size_t MAX_STACK_ALLOC = 128 * 1024;
char *bytes = alloca(MAX_STACK_ALLOC);
BOOL shouldFree = NO;
int mib[] = {CTL_KERN, KERN_PROCARGS2, pid};
// Get estimated length of arg array
if (sysctl(mib, 3, NULL, &argsSizeEstimate, NULL, 0) < 0) return;
argsSize = argsSizeEstimate + 512;
// If this is larger than our allocated stack space, alloc from heap.
if (argsSize > MAX_STACK_ALLOC) {
bytes = malloc(argsSize);
shouldFree = YES;
}
// Return the args as a space-separated list
return [args componentsJoinedByString:@" "];
// Get the args. If this fails, free if necessary and return.
if (sysctl(mib, 3, bytes, &argsSize, NULL, 0) != 0 || argsSize >= argsSizeEstimate + 512) {
if (shouldFree) {
free(bytes);
}
return;
}
// Get argc, set index to the end of argc
int argc = 0;
memcpy(&argc, &bytes[0], sizeof(argc));
index = sizeof(argc);
// Skip past end of executable path and trailing NULLs
for (; index < argsSize; ++index) {
if (bytes[index] == '\0') {
++index;
break;
}
}
for (; index < argsSize; ++index) {
if (bytes[index] != '\0') break;
}
// Save the beginning of the arguments
size_t stringStart = index;
// Replace all NULLs with spaces up until the first environment variable
int replacedNulls = 0;
for (; index < argsSize; ++index) {
if (bytes[index] == '\0') {
++replacedNulls;
if (replacedNulls == argc) break;
bytes[index] = ' ';
}
}
// Potentially sanitize the args string.
NSString *sanitized = [self sanitizeCString:&bytes[stringStart] ofLength:index - stringStart];
if (sanitized) {
[str appendFormat:@"|args=%@", sanitized];
} else {
[str appendFormat:@"|args=%s", &bytes[stringStart]];
}
if (shouldFree) {
free(bytes);
}
}
/**
Given an IOKit device path (like those provided by DiskArbitration), find the disk
image path by looking up the device in the IOKit registry and getting its properties.
This is largely the same as the way hdiutil gathers info for the "info" command.
*/
- (NSString *)diskImageForDevice:(NSString *)devPath {
devPath = [devPath stringByDeletingLastPathComponent];
if (!devPath.length) return nil;
io_registry_entry_t device = IORegistryEntryFromPath(kIOMasterPortDefault, devPath.UTF8String);
CFMutableDictionaryRef deviceProperties = NULL;
IORegistryEntryCreateCFProperties(device, &deviceProperties, kCFAllocatorDefault, kNilOptions);
NSDictionary *properties = CFBridgingRelease(deviceProperties);
IOObjectRelease(device);
NSData *pathData = properties[@"image-path"];
NSString *result = [[NSString alloc] initWithData:pathData encoding:NSUTF8StringEncoding];
return result;
}
/**
Given an IOKit device path (like those provided by DiskArbitration), find the device serial number,
if there is one. This has only really been tested with USB and internal devices.
*/
- (NSString *)serialForDevice:(NSString *)devPath {
if (!devPath.length) return nil;
NSString *serial;
io_registry_entry_t device = IORegistryEntryFromPath(kIOMasterPortDefault, devPath.UTF8String);
while (!serial && device) {
CFMutableDictionaryRef deviceProperties = NULL;
IORegistryEntryCreateCFProperties(device, &deviceProperties, kCFAllocatorDefault, kNilOptions);
NSDictionary *properties = CFBridgingRelease(deviceProperties);
if (properties[@"Serial Number"]) {
serial = properties[@"Serial Number"];
} else if (properties[@"kUSBSerialNumberString"]) {
serial = properties[@"kUSBSerialNumberString"];
}
if (serial) {
IOObjectRelease(device);
break;
}
io_registry_entry_t parent;
IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent);
IOObjectRelease(device);
device = parent;
}
return serial;
}
@end

View File

@@ -34,12 +34,6 @@
///
@interface SNTExecutionController : NSObject
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@property SNTEventTable *eventTable;
@property SNTNotificationQueue *notifierQueue;
@property SNTRuleTable *ruleTable;
- (instancetype)initWithDriverManager:(SNTDriverManager *)driverManager
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable

View File

@@ -22,6 +22,7 @@
#import "MOLCertificate.h"
#import "MOLCodesignChecker.h"
#import "SNTBlockMessage.h"
#import "SNTCachedDecision.h"
#import "SNTCommonEnums.h"
#import "SNTConfigurator.h"
@@ -35,6 +36,17 @@
#import "SNTRuleTable.h"
#import "SNTStoredEvent.h"
@interface SNTExecutionController ()
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@property SNTEventTable *eventTable;
@property SNTNotificationQueue *notifierQueue;
@property SNTRuleTable *ruleTable;
@property NSMutableDictionary *uploadBackoff;
@property dispatch_queue_t eventQueue;
@end
@implementation SNTExecutionController
#pragma mark Initializers
@@ -52,6 +64,9 @@
_notifierQueue = notifierQueue;
_eventLog = eventLog;
_uploadBackoff = [NSMutableDictionary dictionaryWithCapacity:128];
_eventQueue = dispatch_queue_create("com.google.santad.event_upload", DISPATCH_QUEUE_SERIAL);
// This establishes the XPC connection between libsecurity and syspolicyd.
// Not doing this causes a deadlock as establishing this link goes through xpcproxy.
(void)[[MOLCodesignChecker alloc] initWithSelf];
@@ -62,7 +77,7 @@
#pragma mark Binary Validation
- (SNTEventState)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
SNTRule *rule = [self.ruleTable binaryRuleForSHA256:cd.sha256];
SNTRule *rule = [_ruleTable binaryRuleForSHA256:cd.sha256];
if (rule) {
switch (rule.state) {
case SNTRuleStateWhitelist:
@@ -76,7 +91,7 @@
}
}
rule = [self.ruleTable certificateRuleForSHA256:cd.certSHA256];
rule = [_ruleTable certificateRuleForSHA256:cd.certSHA256];
if (rule) {
switch (rule.state) {
case SNTRuleStateWhitelist:
@@ -115,11 +130,13 @@
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
if (!binInfo) {
LOGW(@"Failed to read file %@: %@", binInfo.path, fileInfoError.localizedDescription);
[self.driverManager postToKernelAction:ACTION_RESPOND_ALLOW
forVnodeID:message.vnode_id];
[_driverManager postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:message.vnode_id];
return;
}
// PrinterProxy workaround, see description above the method for more details.
if ([self printerProxyWorkaround:binInfo]) return;
// Get codesigning info about the file.
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path];
@@ -134,10 +151,10 @@
// Save decision details for logging the execution later.
santa_action_t action = [self actionForEventState:cd.decision];
if (action == ACTION_RESPOND_ALLOW) [self.eventLog saveDecisionDetails:cd];
if (action == ACTION_RESPOND_ALLOW) [_eventLog saveDecisionDetails:cd];
// Send the decision to the kernel.
[self.driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
[_driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary &&
@@ -154,44 +171,61 @@
se.ppid = @(message.ppid);
se.parentName = @(message.pname);
// Bundle data
se.fileBundleID = [binInfo bundleIdentifier];
se.fileBundleName = [binInfo bundleName];
se.fileBundlePath = [binInfo bundlePath];
if ([binInfo bundleShortVersionString]) {
se.fileBundleVersionString = [binInfo bundleShortVersionString];
}
if ([binInfo bundleVersion]) {
se.fileBundleVersion = [binInfo bundleVersion];
}
// User data
struct passwd *user = getpwuid(message.uid);
if (user) {
se.executingUser = @(user->pw_name);
}
if (user) se.executingUser = @(user->pw_name);
NSArray *loggedInUsers, *currentSessions;
[self loggedInUsers:&loggedInUsers sessions:&currentSessions];
se.currentSessions = currentSessions;
se.loggedInUsers = loggedInUsers;
// Quarantine data
se.quarantineDataURL = binInfo.quarantineDataURL;
se.quarantineRefererURL = binInfo.quarantineRefererURL;
se.quarantineTimestamp = binInfo.quarantineTimestamp;
se.quarantineAgentBundleID = binInfo.quarantineAgentBundleID;
[self.eventTable addStoredEvent:se];
dispatch_async(_eventQueue, ^{
[_eventTable addStoredEvent:se];
});
// If binary was blocked, do the needful
if (action != ACTION_RESPOND_ALLOW) {
[self.eventLog logDeniedExecution:cd withMessage:message];
[_eventLog logDeniedExecution:cd withMessage:message];
// So the server has something to show the user straight away, initiate an event
// upload for the blocked binary rather than waiting for the next sync.
[self initiateEventUploadForEvent:se];
dispatch_async(_eventQueue, ^{
[self initiateEventUploadForEvent:se];
});
if (!cd.silentBlock) {
[self.notifierQueue addEvent:se customMessage:cd.customMsg];
// Let the user know what happened, both on the terminal and in the GUI.
NSAttributedString *s = [SNTBlockMessage attributedBlockMessageForEvent:se
customMessage:cd.customMsg];
NSString *msg = [NSString stringWithFormat:@"\033[1mSanta\033[0m\n\n%@\n\n", s.string];
msg = [msg stringByAppendingFormat:@"\033[1mPath:\033[0m %@\n"
@"\033[1mIdentifier:\033[0m %@\n"
@"\033[1mParent:\033[0m %@ (%@)\n\n",
se.filePath, se.fileSHA256, se.parentName, se.ppid];
NSURL *detailURL = [SNTBlockMessage eventDetailURLForEvent:se];
if (detailURL) {
msg = [msg stringByAppendingFormat:@"%@\n\n", detailURL.absoluteString];
}
[self printMessage:msg toTTYForPID:message.ppid];
[_notifierQueue addEvent:se customMessage:cd.customMsg];
}
}
}
@@ -236,6 +270,47 @@
return nil;
}
/**
Workaround for issue with PrinterProxy.app.
Every time a new printer is added to the machine, a copy of the PrinterProxy.app is copied from
the Print.framework to ~/Library/Printers with the name of the printer as the name of the app.
The binary inside is changed slightly (in a way that is unique to the printer name) and then
re-signed with an adhoc signature. I don't know why this is done but it seems that the binary
itself doesn't need to be changed as copying the old one back in-place seems to work,
so that's what we do.
If this workaround is applied the decision request is not responded to as the existing request
is invalidated when the file is closed which will trigger a brand new request coming from the
kernel.
@param fi, SNTFileInfo object for the binary being executed.
@return YES if the workaround was applied, NO otherwise.
*/
- (BOOL)printerProxyWorkaround:(SNTFileInfo *)fi {
if ([fi.path hasSuffix:@"/Contents/MacOS/PrinterProxy"] &&
[fi.path containsString:@"Library/Printers"]) {
NSString *proxyPath = (@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
@"Frameworks/Print.framework/Versions/Current/Plugins/PrinterProxy.app/"
@"Contents/MacOS/PrinterProxy");
SNTFileInfo *proxyFi = [[SNTFileInfo alloc] initWithPath:proxyPath];
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyPath];
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
[outFh writeData:[inFh readDataToEndOfFile]];
[inFh closeFile];
[outFh truncateFileAtOffset:[outFh offsetInFile]];
[outFh synchronizeFile];
[outFh closeFile];
LOGW(@"PrinterProxy workaround applied to %@", fi.path);
return YES;
}
return NO;
}
- (void)initiateEventUploadForEvent:(SNTStoredEvent *)event {
// The event upload is skipped if the full path is equal to that of santactl so that
// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
@@ -244,14 +319,23 @@
![[SNTConfigurator configurator] syncBaseURL] ||
[[SNTConfigurator configurator] syncBackOff]) return;
// The event upload is skipped if an event upload has been initiated for it in the
// last 10 minutes.
NSDate *backoff = self.uploadBackoff[event.fileSHA256];
NSDate *now = [NSDate date];
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
self.uploadBackoff[event.fileSHA256] = now;
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "singleevent",
[event.fileSHA256 UTF8String], NULL));
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog",
"singleevent", [event.fileSHA256 UTF8String], NULL));
}
}
@@ -273,6 +357,22 @@
}
}
- (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid {
if (pid < 2) return; // don't bother even looking for launchd.
struct proc_bsdinfo taskInfo = {};
if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &taskInfo, sizeof(taskInfo)) < 1) {
return;
}
NSString *devPath = [NSString stringWithFormat:@"/dev/%s", devname(taskInfo.e_tdev, S_IFCHR)];
int fd = open(devPath.UTF8String, O_WRONLY | O_NOCTTY);
@try {
NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
[fh writeData:[msg dataUsingEncoding:NSUTF8StringEncoding]];
} @catch (NSException *) { /* do nothing */ }
}
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];

View File

@@ -35,7 +35,7 @@ void *watchdogThreadFunction(__unused void *idata) {
pthread_setname_np("com.google.santa.watchdog");
// Number of seconds to wait between checks.
const int timeInterval = 60;
const int timeInterval = 30;
// Amount of CPU usage to trigger warning, as a percentage averaged over timeInterval
// santad's usual CPU usage is 0-3% but can occasionally spike if lots of processes start at once.

View File

@@ -16,9 +16,14 @@
#import <Foundation/Foundation.h>
#import <IOKit/IODataQueueClient.h>
#include <cmath>
#include <ctime>
#include <iostream>
#include <mach/mach.h>
#include <numeric>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <vector>
#include "SNTKernelCommon.h"
@@ -152,6 +157,13 @@
TFAILINFO("KR: %d", kr);
}
TPASS();
TSTART("Refuses second client");
kr = IOConnectCallMethod(self.connection, kSantaUserClientOpen, 0, 0, 0, 0, 0, 0, 0, 0);
if (kr != kIOReturnExclusiveAccess) {
TFAIL();
}
TPASS();
}
#pragma mark - Listener
@@ -312,7 +324,7 @@
/// Tests that a write to a cached vnode will invalidate the cached response for that file
- (void)invalidatesCacheTests {
TSTART("Invalidates cache correctly");
TSTART("Invalidates cache for manually closed FDs");
// Copy the ls binary to a new file
NSFileManager *fm = [NSFileManager defaultManager];
@@ -369,6 +381,49 @@
}
}
- (void)invalidatesCacheAutoCloseTest {
TSTART("Invalidates cache for auto-closed FDs");
// Check invalidations when kernel auto-closes descriptors
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm copyItemAtPath:@"/bin/pwd" toPath:@"invalidacachetest_tmp" error:nil]) {
TFAILINFO("Failed to create temp file");
}
// Launch the new file to put it in the cache
NSTask *pwd = [self taskWithPath:@"invalidacachetest_tmp"];
[pwd launch];
[pwd waitUntilExit];
// Exit if this fails with a useful message.
if ([pwd terminationStatus] != 0) {
TFAILINFO("Second launch of test binary failed");
}
// Replace file contents
NSDictionary *attrs = [fm attributesOfItemAtPath:@"/bin/ed" error:NULL];
NSTask *dd = [self taskWithPath:@"/bin/dd"];
dd.arguments = @[ @"if=/bin/ed",
@"of=invalidacachetest_tmp",
@"bs=1",
[NSString stringWithFormat:@"count=%@", attrs[NSFileSize]]
];
[dd launch];
[dd waitUntilExit];
// And try running the temp file again. If it succeeds, the test failed.
NSTask *ed = [self taskWithPath:@"invalidacachetest_tmp"];
@try {
[ed launch];
[ed waitUntilExit];
TFAILINFO("Launched after file closed");
} @catch (NSException *exception) {
TPASS();
} @finally {
[fm removeItemAtPath:@"invalidacachetest_tmp" error:nil];
}
}
/// Tests the clear cache function works correctly
- (void)clearCacheTests {
TSTART("Can clear cache");
@@ -452,6 +507,48 @@
TPASS();
}
- (void)testCachePerformance {
TSTART("Test cache performance");
// Execute echo 100 times, saving the time taken for each run
std::vector<std::clock_t> times;
for (int i = 0; i < 100; ++i) {
printf("\033[s"); // save cursor position
printf("%d/%d", i + 1, 100);
auto start = std::clock();
NSTask *t = [[NSTask alloc] init];
t.launchPath = @"/bin/echo";
t.standardOutput = [NSPipe pipe];
[t launch];
[t waitUntilExit];
if (i > 5) times.push_back(std::clock() - start);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
// Sort and remove first 10 and last 10 entries.
std::sort(times.begin(), times.end());
times.erase(times.begin(), times.begin()+10);
times.erase(times.end()-10, times.end());
// Calculate mean
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size();
// Calculate stdev
double accum = 0.0;
std::for_each(times.begin(), times.end(), [&](const double d) {
accum += (d - mean) * (d - mean);
});
double stdev = sqrt(accum / (times.size()-1));
if (mean > 1000 || stdev > 150) {
TFAILINFO("μ: %-3.2f σ: %-3.2f", mean, stdev);
} else {
TPASSINFO("μ: %-3.2f σ: %-3.2f", mean, stdev);
}
}
#pragma mark - Main
- (void)runTests {
@@ -472,8 +569,12 @@
[self receiveAndBlockTests];
[self receiveAndCacheTests];
[self invalidatesCacheTests];
[self invalidatesCacheAutoCloseTest];
[self clearCacheTests];
[self blocksDeniedTracedBinaries];
printf("\n-> Performance tests:\033[m\n");
[self testCachePerformance];
[self handlesLotsOfBinaries];
printf("\nAll tests passed.\n\n");

View File

@@ -0,0 +1,505 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>25</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>30</integer>
</dict>
</array>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>29</integer>
</dict>
<key>currentSessions</key>
<dict>
<key>CF$UID</key>
<integer>26</integer>
</dict>
<key>decision</key>
<dict>
<key>CF$UID</key>
<integer>19</integer>
</dict>
<key>executingUser</key>
<dict>
<key>CF$UID</key>
<integer>16</integer>
</dict>
<key>filePath</key>
<dict>
<key>CF$UID</key>
<integer>5</integer>
</dict>
<key>fileSHA256</key>
<dict>
<key>CF$UID</key>
<integer>4</integer>
</dict>
<key>idx</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
<key>loggedInUsers</key>
<dict>
<key>CF$UID</key>
<integer>23</integer>
</dict>
<key>occurrenceDate</key>
<dict>
<key>CF$UID</key>
<integer>17</integer>
</dict>
<key>parentName</key>
<dict>
<key>CF$UID</key>
<integer>22</integer>
</dict>
<key>pid</key>
<dict>
<key>CF$UID</key>
<integer>20</integer>
</dict>
<key>ppid</key>
<dict>
<key>CF$UID</key>
<integer>21</integer>
</dict>
<key>signingChain</key>
<dict>
<key>CF$UID</key>
<integer>6</integer>
</dict>
</dict>
<integer>14887</integer>
<string>ff98fa0c0a1095fedcbe4d388a9760e71399a5c3c017a847ffa545663b57929a</string>
<string>/usr/bin/yes</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>15</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>7</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>11</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>13</integer>
</dict>
</array>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>10</integer>
</dict>
<key>certData</key>
<dict>
<key>CF$UID</key>
<integer>8</integer>
</dict>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>9</integer>
</dict>
<key>NS.data</key>
<data>
MIIFOzCCBCOgAwIBAgIIKtpxuqe9F58wDQYJKoZIhvcNAQEFBQAw
fzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAk
BgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMw
MQYDVQQDDCpBcHBsZSBDb2RlIFNpZ25pbmcgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTMwNDEyMjIzNDM1WhcNMjEwNDEyMjIz
NDM1WjBWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5j
LjEXMBUGA1UECwwOQXBwbGUgU29mdHdhcmUxGTAXBgNVBAMMEFNv
ZnR3YXJlIFNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQC/MLh0mE+uBguklG4xVG0J0TyjsDkQqdDmqmAiXdPk
hKJAQZBkxmA9kWHaUqhFJ54sZMvkHqgkClI6s9PsFkF4wZ7RBuZ4
JWMI89/KQeYd/jXpUVwTFYvp0Z1xe9HJqkuemdqPwCm4L5BvpLtl
j4Bq1z1obeR4wqUSL/gy6X7JXVyMPhYgG9denRuGLQj3vBmkTQ5B
pErbaxqARVAEqUyNFQfqaie9u4iePD+yUjmX47fI61RSmIovI1Zl
5ekq2VG0I/oE3ffroN/VmvJeCPFfh/CxR2x1sbGM0RPjesHsYkF0
poM08fladGQ5P1luzyzAYIMpPOfeT18N85M5XzCNAgMBAAGjggHi
MIIB3jAdBgNVHQ4EFgQUxu0+Svsu6D8T1aAVs13Z57P3aDUwDAYD
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBSOaabEd0JOBKVWQpxRH4ba
0iCPCTCCARwGA1UdIASCARMwggEPMIIBCwYJKoZIhvdjZAUBMIH9
MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0
aWZpY2F0ZWF1dGhvcml0eTCBwwYIKwYBBQUHAgIwgbYMgbNSZWxp
YW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2Us
IGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBw
cmFjdGljZSBzdGF0ZW1lbnRzLjA1BgNVHR8ELjAsMCqgKKAmhiRo
dHRwOi8vY3JsLmFwcGxlLmNvbS9jb2Rlc2lnbmluZy5jcmwwDgYD
VR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA8G
CSqGSIb3Y2QGFgQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFfUxSFX
GxTaEjIsEQUMBA+VqtTi+vLEbWjeUlINInPIhXMd71FO8IpJsGiU
ZVEi3/1AjzW0aEBSuyWOzPrOfBJW2MDQVQW1SrG1YfyVfJFectEo
tB0rbdpLZ58F/ObnWUpDXh97hDe+/rqKKzMFlFCDuP6a2wO7jWLy
GW13k+N1zzZZMV4IbV0BHGVTUpN2eJwXCxAelLw2kFZLRC6Y2aYx
ofAcZpSZVHMTtVE4vCSioDA7emWHrMC8FfReMMedoyoE38TPR4Rt
n/3/RcOgGaw8u62PlPm5x8hxNhHt6AG6tHVIgqQqUxoFBZudxkcb
9eggcqAbS+W+ZPw4DZr/Q0E=
</data>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSMutableData</string>
<string>NSData</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSMutableData</string>
</dict>
<dict>
<key>$classes</key>
<array>
<string>MOLCertificate</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>MOLCertificate</string>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>10</integer>
</dict>
<key>certData</key>
<dict>
<key>CF$UID</key>
<integer>12</integer>
</dict>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>9</integer>
</dict>
<key>NS.data</key>
<data>
MIIEDjCCAvagAwIBAgIBITANBgkqhkiG9w0BAQUFADBiMQswCQYD
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
DUFwcGxlIFJvb3QgQ0EwHhcNMTExMDI0MTczOTQxWhcNMjYxMDI0
MTczOTQxWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUg
SW5jLjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkxMzAxBgNVBAMMKkFwcGxlIENvZGUgU2lnbmluZyBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKKoEXH/DvkLa/glDZiBXWtZvVobibPn5e7O
OZgjNTlInyGrJ9nunCDwZDgIawynz9xQth0GxFvxXRqbVGWGcy9i
5Ti9ARBkcm18aUdhnBAFJuPrhcIsJNxqwj+I/MysKUyhSXkRmnV2
5R640NIJtExTePvfGHahj6SpMsqRp7b6l705qs0bUBGIq2rt62bK
IEusOy3vqufWyYgtacKkKmEv24cC86EhuUyfDvj52S3KcgR/Ha5u
+j+Is8yjQO4XhxhRlrzP5C2twulZTl0cZTMnA6pno5Mkh8eHeQK5
XZizDu7NaQg+jEiSJLJt1zC+z9jkyKeXgdAeI9w4mV9h/oUCAwEA
AaOBsTCBrjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB
BQUHAwMwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjmmmxHdC
TgSlVkKcUR+G2tIgjwkwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40u
QKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5h
cHBsZS5jb20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUF
AAOCAQEAcHOt9lIVarcVGN6pKtGddpsesmmWx8LD4SvQ7wddcPja
PFpIR9s5bIDKc95iG7c6yqNaHuOH2iVKk5vvcxCTc13j9J1+3g+B
9qmZwVhunPSJAL7PT/8C0w789fP0choysconDt6o05mPauaZ+2HJ
T/IXsRhn8DDAxgruyESBpIm78XlBw+6uyGtnfMxsSYZMAtPTam4Y
nPhcOMgwh5ow2mcouOKaedqfpTsfUWI7IvF+U3waC8PwTdxJRPKI
iM46W7md6bK3W1KnxtVYiXK32MyzqBgdUJc/Hdpqrji/e3kxvmO5
94WFF+ltisTiGJQv129SpZmx3USbB3CSiCZ32w==
</data>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>10</integer>
</dict>
<key>certData</key>
<dict>
<key>CF$UID</key>
<integer>14</integer>
</dict>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>9</integer>
</dict>
<key>NS.data</key>
<data>
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYD
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
DUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5
MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmE
Les2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRr
EdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b
8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6ws
IG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CW
QYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMU
ZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4w
ggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggr
BgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2Ev
MIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2Vy
dGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z
IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9s
aWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVu
dHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J2
0ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNii
Pvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3
iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP
3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5
MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJ
BzewdXUh
</data>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSMutableArray</string>
<string>NSArray</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSMutableArray</string>
</dict>
<string>root</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>18</integer>
</dict>
<key>NS.time</key>
<real>485894498.53763503</real>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSDate</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSDate</string>
</dict>
<integer>6</integer>
<integer>11196</integer>
<integer>10760</integer>
<string>bash</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>25</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>24</integer>
</dict>
</array>
</dict>
<string>foouser</string>
<dict>
<key>$classes</key>
<array>
<string>NSArray</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSArray</string>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>25</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>27</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>28</integer>
</dict>
</array>
</dict>
<string>foouser@console</string>
<string>foouser@ttys000</string>
<dict>
<key>$classes</key>
<array>
<string>SNTStoredEvent</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>SNTStoredEvent</string>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>29</integer>
</dict>
<key>currentSessions</key>
<dict>
<key>CF$UID</key>
<integer>38</integer>
</dict>
<key>decision</key>
<dict>
<key>CF$UID</key>
<integer>35</integer>
</dict>
<key>executingUser</key>
<dict>
<key>CF$UID</key>
<integer>24</integer>
</dict>
<key>filePath</key>
<dict>
<key>CF$UID</key>
<integer>33</integer>
</dict>
<key>fileSHA256</key>
<dict>
<key>CF$UID</key>
<integer>32</integer>
</dict>
<key>idx</key>
<dict>
<key>CF$UID</key>
<integer>31</integer>
</dict>
<key>loggedInUsers</key>
<dict>
<key>CF$UID</key>
<integer>37</integer>
</dict>
<key>occurrenceDate</key>
<dict>
<key>CF$UID</key>
<integer>34</integer>
</dict>
<key>parentName</key>
<dict>
<key>CF$UID</key>
<integer>22</integer>
</dict>
<key>pid</key>
<dict>
<key>CF$UID</key>
<integer>36</integer>
</dict>
<key>ppid</key>
<dict>
<key>CF$UID</key>
<integer>21</integer>
</dict>
</dict>
<integer>14888</integer>
<string>2b3a38f4e4e981afc517b0c7903ffe2f79193c67e2329dd77cf2e74b046ca3a7</string>
<string>/usr/local/Cellar/hub/2.2.3/bin/hub</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>18</integer>
</dict>
<key>NS.time</key>
<real>485894568.92822498</real>
</dict>
<integer>1</integer>
<integer>11427</integer>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>25</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>24</integer>
</dict>
</array>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>25</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>27</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>28</integer>
</dict>
</array>
</dict>
</array>
<key>$top</key>
<dict>
<key>root</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$version</key>
<integer>100000</integer>
</dict>
</plist>

View File

@@ -0,0 +1,454 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>28</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
</array>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>36</integer>
</dict>
<key>currentSessions</key>
<dict>
<key>CF$UID</key>
<integer>29</integer>
</dict>
<key>decision</key>
<dict>
<key>CF$UID</key>
<integer>24</integer>
</dict>
<key>executingUser</key>
<dict>
<key>CF$UID</key>
<integer>21</integer>
</dict>
<key>fileBundleID</key>
<dict>
<key>CF$UID</key>
<integer>8</integer>
</dict>
<key>fileBundleName</key>
<dict>
<key>CF$UID</key>
<integer>6</integer>
</dict>
<key>fileBundlePath</key>
<dict>
<key>CF$UID</key>
<integer>7</integer>
</dict>
<key>fileBundleVersion</key>
<dict>
<key>CF$UID</key>
<integer>9</integer>
</dict>
<key>fileBundleVersionString</key>
<dict>
<key>CF$UID</key>
<integer>10</integer>
</dict>
<key>filePath</key>
<dict>
<key>CF$UID</key>
<integer>5</integer>
</dict>
<key>fileSHA256</key>
<dict>
<key>CF$UID</key>
<integer>4</integer>
</dict>
<key>idx</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
<key>loggedInUsers</key>
<dict>
<key>CF$UID</key>
<integer>27</integer>
</dict>
<key>occurrenceDate</key>
<dict>
<key>CF$UID</key>
<integer>22</integer>
</dict>
<key>parentName</key>
<dict>
<key>CF$UID</key>
<integer>26</integer>
</dict>
<key>pid</key>
<dict>
<key>CF$UID</key>
<integer>25</integer>
</dict>
<key>ppid</key>
<dict>
<key>CF$UID</key>
<integer>24</integer>
</dict>
<key>quarantineAgentBundleID</key>
<dict>
<key>CF$UID</key>
<integer>35</integer>
</dict>
<key>quarantineDataURL</key>
<dict>
<key>CF$UID</key>
<integer>32</integer>
</dict>
<key>quarantineRefererURL</key>
<dict>
<key>CF$UID</key>
<integer>33</integer>
</dict>
<key>quarantineTimestamp</key>
<dict>
<key>CF$UID</key>
<integer>34</integer>
</dict>
<key>signingChain</key>
<dict>
<key>CF$UID</key>
<integer>11</integer>
</dict>
</dict>
<integer>14896</integer>
<string>9dce073ce17dc86cf08182bf1b2c9adff5a5bd7a9b133a963b611daa2dc0f68c</string>
<string>/Applications/Paw.app/Contents/MacOS/Paw</string>
<string>Paw</string>
<string>/Applications/Paw.app</string>
<string>com.luckymarmot.Paw</string>
<string>2003004001</string>
<string>2.3.4</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>20</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>12</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>16</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>18</integer>
</dict>
</array>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>15</integer>
</dict>
<key>certData</key>
<dict>
<key>CF$UID</key>
<integer>13</integer>
</dict>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>14</integer>
</dict>
<key>NS.data</key>
<data>
MIIFaTCCBFGgAwIBAgIIbgRYjgjmV00wDQYJKoZIhvcNAQELBQAw
eTEtMCsGA1UEAwwkRGV2ZWxvcGVyIElEIENlcnRpZmljYXRpb24g
QXV0aG9yaXR5MSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UE
BhMCVVMwHhcNMTIxMjA1MDUzMDIwWhcNMTcxMjA2MDUzMDIwWjCB
ijEaMBgGCgmSJomT8ixkAQEMCjg0NTk5Ukw1OEExMTAvBgNVBAMM
KERldmVsb3BlciBJRCBBcHBsaWNhdGlvbjogTWljaGEgTWF6YWhl
cmkxEzARBgNVBAsMCjg0NTk5Ukw1OEExFzAVBgNVBAoMDk1pY2hh
IE1hemFoZXJpMQswCQYDVQQGEwJGUjCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAODR8cTAZZCZb/8+/fgVfM3Mu3dr0ga0
ry+CoHEZLuWiiHEPbcalOp13DyC1x9bYY341dr2TJcrVFhIRg/bi
3arx9U3AQl60W4Ybo223jwdnt5FPK+WuXg2kL1OmE8Aqd9sKWcX0
QlLVuVF4HjiKukD44qRMyP8vAxLFPIqPFizw1m1qNSaMa66JaKos
hDJOT2WgpLEdPTsxw8U1NMXLVnh+V8Ax+BAjMsIPOE6V7P36TUyt
L2J/YgcIdiYo/kr05ATmfQEEk/E680gFAXX2z5L8sxZbKMwPyhJZ
antw7u9ygHH/XnL850gcxXr7ggOKjgcJGhlBRpb+j9AIXj4nHl0C
AwEAAaOCAeEwggHdMD4GCCsGAQUFBwEBBDIwMDAuBggrBgEFBQcw
AYYiaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AtZGV2aWQwMTAd
BgNVHQ4EFgQU4EU6eGBHWGVUVOnfZpSf28AGQfkwDAYDVR0TAQH/
BAIwADAfBgNVHSMEGDAWgBRXF+2iz9x8mKEQ4Py+hy0s8uMXVDCC
AQ4GA1UdIASCAQUwggEBMIH+BgkqhkiG92NkBQEwgfAwKAYIKwYB
BQUHAgEWHGh0dHA6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EwgcMG
CCsGAQUFBwICMIG2DIGzUmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZp
Y2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2NlcHRhbmNlIG9m
IHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5k
IGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kg
YW5kIGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4w
DgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMD
MBMGCiqGSIb3Y2QGAQ0BAf8EAgUAMA0GCSqGSIb3DQEBCwUAA4IB
AQAzGxszYVEE5wCR4OnFvk8Ea1A4/uyy8jjQ/loMJXIbeJ436IqX
uc+6eCVIbkM+X4c5SEogq0td7NCkupgJUAuxEY2LCD/N3E5sDHaW
09qBEV+hVBSF1+mwigLSLexaGgH9lQBNabbwjgkloFpFCdDDRtyT
lUhC0Mo1jcjrFH9jfH76yn66SgY4oSnU1r/TiJFdt0a1e6Jbhc4U
L9r/mhJFYYjl811DZQUjBrdqnGq9KP6mU6B8MHzay9jUb0vgbFqQ
cxoWNKr7fW5U/oHZKAh1LtechJFairDpIsXr9euIOq4UHHjqQ1R7
zFBVaYHXWcyNjk9UCQq2Ejjw6NkAr4AA
</data>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSMutableData</string>
<string>NSData</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSMutableData</string>
</dict>
<dict>
<key>$classes</key>
<array>
<string>MOLCertificate</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>MOLCertificate</string>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>15</integer>
</dict>
<key>certData</key>
<dict>
<key>CF$UID</key>
<integer>17</integer>
</dict>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>14</integer>
</dict>
<key>NS.data</key>
<data>
MIIEBDCCAuygAwIBAgIIGHqpqMKWIQwwDQYJKoZIhvcNAQELBQAw
YjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAk
BgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYw
FAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEyMDIwMTIyMTIxNVoX
DTI3MDIwMTIyMTIxNVoweTEtMCsGA1UEAwwkRGV2ZWxvcGVyIElE
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQLDB1BcHBs
ZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBw
bGUgSW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCJdk8GW5pB7qUjKwKjX9dzP8A1sIuECj8G
JH+nlT/rTw6Tr7QO0Mg+5W0Ysx/oiUe/1wkI5P9WmCkV55SduTWj
Cs20wOHiYPTK7Cl4RWlpYGtfipL8niPmOsIiszFPHLrytjRZQu6w
qQIDGJEEtrN4LjMfgEUNRW+7Dlpbfzrn2AjXCw4ybfuGNuRsq8QR
inCEJqqfRNHxuMZ7lBebSPcLWBa6I8WfFTl+yl3DMl8P4FJ/QOq+
rAhklVvJGpzlgMofakQcbD7EsCYfHex7r16gaj1HqVgSMT8gdiht
HRywwk4RaSaLy9bQEYLJTg/xVnTQ2QhLZniiq6yn4tJMh1nJAgMB
AAGjgaYwgaMwHQYDVR0OBBYEFFcX7aLP3HyYoRDg/L6HLSzy4xdU
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70
a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDovL2Ny
bC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGGMBAG
CiqGSIb3Y2QGAgYEAgUAMA0GCSqGSIb3DQEBCwUAA4IBAQBCOXRr
odzGpI83KoyzHQpEvJUsf7xZuKxh+weQkjK51L87wVA5akR0ouxb
H3Dlqt1LbBwjcS1f0cWTvu6binBlgp0W4xoQF4ktqM39DHhYSQwo
fzPuAHobtHastrW7T9+oG53IGZdKC1ZnL8I+trPEgzrwd210xC4j
Ue6apQNvYPSlSKcGwrta4h8fRkV+5Jf1JxC3ICJyb3LaxlB1xT0l
j12jAOmfNoxIOY+zO+qQgC6VmmD0eM70DgpTPqL6T9geroSVjTK8
Vk2J6XgY4KyaQrp6RhuEoonOFOiI0ViL9q5WxCwFKkWvC9lLqQIP
NKyIx2FViUTJJ3MH7oLlTvVw
</data>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>15</integer>
</dict>
<key>certData</key>
<dict>
<key>CF$UID</key>
<integer>19</integer>
</dict>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>14</integer>
</dict>
<key>NS.data</key>
<data>
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYD
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
DUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5
MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmE
Les2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRr
EdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b
8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6ws
IG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CW
QYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMU
ZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4w
ggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggr
BgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2Ev
MIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2Vy
dGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z
IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9s
aWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVu
dHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J2
0ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNii
Pvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3
iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP
3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5
MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJ
BzewdXUh
</data>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSMutableArray</string>
<string>NSArray</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSMutableArray</string>
</dict>
<string>rah</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>23</integer>
</dict>
<key>NS.time</key>
<real>485897711.079467</real>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSDate</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSDate</string>
</dict>
<integer>1</integer>
<integer>16331</integer>
<string>launchd</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>28</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>21</integer>
</dict>
</array>
</dict>
<dict>
<key>$classes</key>
<array>
<string>NSArray</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSArray</string>
</dict>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>28</integer>
</dict>
<key>NS.objects</key>
<array>
<dict>
<key>CF$UID</key>
<integer>30</integer>
</dict>
<dict>
<key>CF$UID</key>
<integer>31</integer>
</dict>
</array>
</dict>
<string>rah@ttys000</string>
<string>rah@console</string>
<string>https://d3hevc2w7wq7nj.cloudfront.net/paw/Paw-2.3.4-2003004001.zip</string>
<string>https://luckymarmot.com/paw</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>23</integer>
</dict>
<key>NS.time</key>
<real>485897668</real>
</dict>
<string>com.google.Chrome</string>
<dict>
<key>$classes</key>
<array>
<string>SNTStoredEvent</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>SNTStoredEvent</string>
</dict>
</array>
<key>$top</key>
<dict>
<key>root</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$version</key>
<integer>100000</integer>
</dict>
</plist>

View File

@@ -0,0 +1 @@
{"whitelist_regex": null, "client_mode": "MONITOR", "blacklist_regex": null, "batch_size": 100}

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